초기커밋

This commit is contained in:
2025-05-01 07:20:41 +09:00
commit 98bb2e3c5c
2747 changed files with 646947 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
using MongoDB.Driver;
namespace UGQDataAccess;
public class BaseRepository<TCollection> where TCollection : class
{
protected IMongoDatabase _mongoDatabase;
protected IMongoCollection<TCollection> Collection { get; }
protected BaseRepository(IMongoClient mongoClient, string databaseName, string collectionName)
{
_mongoDatabase = mongoClient.GetDatabase(databaseName);
this.Collection = _mongoDatabase.GetCollection<TCollection>(collectionName);
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UGQDataAccess;
public struct DateRange
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
public static class DataRangeHelper
{
public static DateRange ThisYear(DateTime date)
{
DateRange range = new DateRange();
range.Start = new DateTime(date.Year, 1, 1);
range.End = range.Start.AddYears(1).AddSeconds(-1);
return range;
}
public static DateRange LastYear(DateTime date)
{
DateRange range = new DateRange();
range.Start = new DateTime(date.Year - 1, 1, 1);
range.End = range.Start.AddYears(1).AddSeconds(-1);
return range;
}
public static DateRange ThisMonth(DateTime date)
{
DateRange range = new DateRange();
range.Start = new DateTime(date.Year, date.Month, 1);
range.End = range.Start.AddMonths(1).AddSeconds(-1);
return range;
}
public static DateRange LastMonth(DateTime date)
{
DateRange range = new DateRange();
range.Start = (new DateTime(date.Year, date.Month, 1)).AddMonths(-1);
range.End = range.Start.AddMonths(1).AddSeconds(-1);
return range;
}
public static DateRange ThisWeek(DateTime date)
{
DateRange range = new DateRange();
range.Start = date.Date.AddDays(-(int)date.DayOfWeek);
range.End = range.Start.AddDays(7).AddSeconds(-1);
return range;
}
public static DateRange LastWeek(DateTime date)
{
DateRange range = ThisWeek(date);
range.Start = range.Start.AddDays(-7);
range.End = range.End.AddDays(-7);
return range;
}
public static DateRange Today(DateTime date)
{
DateRange range = new DateRange();
range.Start = new DateTime(date.Year, date.Month, date.Day);
range.End = range.Start.AddDays(1).AddSeconds(-1);
return range;
}
public static DateRange Yesterday(DateTime date)
{
DateRange range = Today(date);
range.Start = range.Start.AddDays(-1);
range.End = range.End.AddDays(-1);
return range;
}
}

View File

@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseIntermediateOutputPath>..\..\obj\AnyCPU\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
<BaseOutputPath>..\..\bin\</BaseOutputPath>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,79 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
using ServerCommon;
using UGQDataAccess.Settings;
using UGQDataAccess.Repository;
using UGQDataAccess.Service;
using UGQDatabase.Models;
namespace UGQDataAccess.Extensions;
public static class UGQDataAccessExtentions
{
public static async Task<IServiceCollection> AddUGQDataAccess(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<UGQDatabaseSettings>(configuration.GetSection("UGQDatabase"));
services.AddSingleton<IMongoClient>(s => {
var UGQDatabase = configuration.GetSection("UGQDatabase").Get<UGQDatabaseSettings>();
if (UGQDatabase == null)
throw new Exception("mongodb config error");
var settings = MongoClientSettings.FromConnectionString(UGQDatabase.ConnectionString);
settings.MinConnectionPoolSize = UGQDatabase.MinConnectionPoolSize;
settings.MaxConnectionPoolSize = UGQDatabase.MaxConnectionPoolSize;
settings.WaitQueueTimeout = TimeSpan.FromSeconds(UGQDatabase.WaitQueueTimeoutSecs);
return new MongoClient(settings);
});
services.AddSingleton<QuestAcceptedRepository>();
services.AddSingleton<QuestCompletedRepository>();
services.AddSingleton<QuestAbortedRepository>();
services.AddSingleton<QuestIdSequenceRepository>();
services.AddSingleton<QuestContentRepository>();
services.AddSingleton<QuestDialogRepository>();
services.AddSingleton<AccountRepository>();
services.AddSingleton<LikeRepository>();
services.AddSingleton<BookmarkRepository>();
services.AddSingleton<NpcNameRepository>();
services.AddSingleton<CreatorPointHistoryRepository>();
services.AddSingleton<GameQuestDataRepository>();
services.AddSingleton<ReportRepository>();
services.AddSingleton<AdminAccountRepository>();
services.AddSingleton<AdminService>();
services.AddSingleton<AccountService>();
services.AddSingleton<QuestEditorService>();
services.AddSingleton<InGameService>();
services.AddSingleton<UgqMetaGenerateService>();
services.AddSingleton<ReserveAccountGradeRepository>();
var provider = services.BuildServiceProvider();
var questIdSequenceRepository = provider.GetService<QuestIdSequenceRepository>();
if (questIdSequenceRepository != null)
await questIdSequenceRepository.init();
return services;
}
}

View File

@@ -0,0 +1,42 @@
using ServerCommon;
using UGQDatabase.Models;
using ServerCore; using ServerBase;
namespace UGQDataAccess.Logs;
public static class UgqApiBusinessLogger
{
public static void initBusinessLog()
{
BusinessLogger.setup(new NLogAppender()
, new JsonText()
, LogTransToOutputType.TransToMultyLine);
Log.getLogger().info($"Success ServerLogicBase.onInitBusinessLog()");
}
public static ServerErrorCode collectLogs(LogAction logAction, AccountEntity accountEntity, List<ILogInvoker> invokers)
{
UgqLogAccount ugqLogAccount = new UgqLogAccount(accountEntity);
return BusinessLogger.collectLogs(logAction, ugqLogAccount, invokers);
}
public static ServerErrorCode collectLogs(LogAction logAction, string userGuid, List<ILogInvoker> invokers)
{
UgqLogAccount ugqLogAccount = new UgqLogAccount(userGuid);
return BusinessLogger.collectLogs(logAction, ugqLogAccount, invokers);
}
public static ServerErrorCode collectLogs(LogAction logAction, AdminAccountEntity accountEntity, List<ILogInvoker> invokers)
{
UgqLogAdmin ugqLogAdmin = new UgqLogAdmin(accountEntity);
return BusinessLogger.collectLogs(logAction, ugqLogAdmin, invokers);
}
public static ServerErrorCode collectLogs(LogAction logAction, string userGuid, string nickname, List<ILogInvoker> invokers)
{
UgqLogAccount ugqLogAccount = new UgqLogAccount(userGuid, nickname);
return BusinessLogger.collectLogs(logAction, ugqLogAccount, invokers);
}
}

View File

@@ -0,0 +1,71 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ServerCore;
using ServerBase;
using ServerCommon;
using ServerCommon.BusinessLogDomain;
using MetaAssets;
using UGQDatabase.Models;
namespace UGQDataAccess.Logs;
public class UgqLogAccount : IWithLogActor
{
string? m_userGuid;
string? m_nickname;
public UgqLogAccount(AccountEntity entity)
{
m_userGuid = entity.UserGuid;
m_nickname = entity.Nickname;
}
public UgqLogAccount(string userGuid)
{
m_userGuid = userGuid;
}
public UgqLogAccount(string userGuid, string nickname)
{
m_userGuid = userGuid;
m_nickname = nickname;
}
public ILogActor toLogActor()
{
var log_info = new UgqApiUserActorLog();
log_info.initLogInfo(
ServerType.UgqApi,
m_userGuid ?? string.Empty,
m_nickname ?? string.Empty
);
return log_info;
}
}
public class UgqLogAdmin : IWithLogActor
{
string m_username;
public UgqLogAdmin(AdminAccountEntity entity)
{
m_username = entity.Username;
}
public ILogActor toLogActor()
{
var log_info = new UgqApiAdminLog();
log_info.initLogInfo(
ServerType.UgqApi,
m_username
);
return log_info;
}
}

View File

@@ -0,0 +1,201 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ServerCore;
using ServerBase;
using ServerCommon;
using ServerCommon.BusinessLogDomain;
using MetaAssets;
namespace UGQDataAccess.Logs;
public class UgqApiLoginBusinessLog : ILogInvokerEx
{
private UgqApiLoginLogInfo m_info;
public UgqApiLoginBusinessLog(UgqApiLoginType type) : base(LogDomainType.UgqApi)
{
m_info = new UgqApiLoginLogInfo(this, type);
}
public override bool hasLog()
{
return true;
}
protected override void fillup(ref BusinessLog.LogBody body)
{
body.append(m_info);
}
}
public class UgqApiLogoutBusinessLog : ILogInvokerEx
{
private UgqApiLogoutLogInfo m_info;
public UgqApiLogoutBusinessLog() : base(LogDomainType.UgqApi)
{
m_info = new UgqApiLogoutLogInfo(this);
}
public override bool hasLog()
{
return true;
}
protected override void fillup(ref BusinessLog.LogBody body)
{
body.append(m_info);
}
}
public class UgqApiQuestCraeteBusinessLog : ILogInvokerEx
{
private UgqApiQuestCraeteLogInfo m_info;
public UgqApiQuestCraeteBusinessLog(string quest_content_id, string admin_username) : base(LogDomainType.UgqApi)
{
m_info = new UgqApiQuestCraeteLogInfo(this, quest_content_id, admin_username);
}
public override bool hasLog()
{
return true;
}
protected override void fillup(ref BusinessLog.LogBody body)
{
body.append(m_info);
}
}
public class UgqApiAddSlotBusinessLog : ILogInvokerEx
{
private UgqApiAddSlotLogInfo m_info;
public UgqApiAddSlotBusinessLog(int additional_slots, string admin_username) : base(LogDomainType.UgqApi)
{
m_info = new UgqApiAddSlotLogInfo(this, additional_slots, admin_username);
}
public override bool hasLog()
{
return true;
}
protected override void fillup(ref BusinessLog.LogBody body)
{
body.append(m_info);
}
}
public class UgqApiChangeStateBusinessLog : ILogInvokerEx
{
private UgqApiChangeStateLogInfo m_info;
public UgqApiChangeStateBusinessLog(string quest_content_id, string from_state, string to_state,
long quest_id, long revision, string admin_username) : base(LogDomainType.UgqApi)
{
m_info = new UgqApiChangeStateLogInfo(this, quest_content_id,
from_state, to_state,
quest_id, revision, admin_username);
}
public override bool hasLog()
{
return true;
}
protected override void fillup(ref BusinessLog.LogBody body)
{
body.append(m_info);
}
}
public class UgqApiCreatorPointBusinessLog : ILogInvokerEx
{
private UgqApiCreatorPointLogInfo m_info;
public UgqApiCreatorPointBusinessLog(double amount, long questId, long revision, string admin_username,
UgqCreatorPointReason reason, string reason_detail) : base(LogDomainType.UgqApi)
{
m_info = new UgqApiCreatorPointLogInfo(this, amount, questId, revision, admin_username, reason, reason_detail);
}
public override bool hasLog()
{
return true;
}
protected override void fillup(ref BusinessLog.LogBody body)
{
body.append(m_info);
}
}
public class UgqApiAdminLoginBusinessLog : ILogInvokerEx
{
private UgqApiAdminLoginLogInfo m_info;
public UgqApiAdminLoginBusinessLog(string username) : base(LogDomainType.UgqApi)
{
m_info = new UgqApiAdminLoginLogInfo(this, username);
}
public override bool hasLog()
{
return true;
}
protected override void fillup(ref BusinessLog.LogBody body)
{
body.append(m_info);
}
}
public class UgqApiEmptyLoginBusinessLog : ILogInvokerEx
{
private UgqApiEmptyLogInfo m_info;
public UgqApiEmptyLoginBusinessLog() : base(LogDomainType.UgqApi)
{
m_info = new UgqApiEmptyLogInfo(this);
}
public override bool hasLog()
{
return true;
}
protected override void fillup(ref BusinessLog.LogBody body)
{
body.append(m_info);
}
}
public class EmptyBusinessWithLogActor : IWithLogActor
{
public ILogActor toLogActor()
{
var log_info = new EmptyBusinessLogActor();
return log_info;
}
}
public class EmptyBusinessLogActor : ILogActor
{
public DateTime m_date { get; set; } = DateTimeHelper.Current;
public EmptyBusinessLogActor()
{
m_date = DateTimeHelper.Current;
}
public EmptyBusinessLogActor(EmptyBusinessLogActor logInfo)
{
m_date = logInfo.m_date;
}
}

View File

@@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
using System.Drawing;
using UGQDataAccess.Repository.Models;
using UGQDataAccess.Repository.Query;
namespace UGQDataAccess.Repository;
public enum AccountSortType
{
CreatedAtAsc,
CreatedAtDesc,
}
public class AccountRepository : BaseRepository<AccountEntity>
{
private const string CollectionName = "Account";
public AccountRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<AccountEntity?> getByAccountId(string accountId)
{
var filter = Builders<AccountEntity>.Filter
.Eq(x => x.AccountId, accountId);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task<AccountEntity?> get(string userGuid)
{
var filter = Builders<AccountEntity>.Filter
.Eq(x => x.UserGuid, userGuid);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task<long> getAllCount()
{
var builder = Builders<AccountEntity>.Filter;
var filter = builder.Empty;
var result = await Collection.Find(filter).CountDocumentsAsync();
return result;
}
public async Task<AccountEntity> getOrInsertForGameUser(string userGuid)
{
var filter = Builders<AccountEntity>.Filter
.Eq(x => x.UserGuid, userGuid);
var update = Builders<AccountEntity>.Update
.SetOnInsert(x => x.UserGuid, userGuid)
.SetOnInsert(x => x.Nickname, "")
.SetOnInsert(x => x.GradeType, UgqGradeType.Amature)
.SetOnInsert(x => x.AdditionalSlotCount, 0)
.SetOnInsert(x => x.UpdatedAt, DateTime.UtcNow)
.SetOnInsert(x => x.CreatedAt, DateTime.UtcNow);
var option = new FindOneAndUpdateOptions<AccountEntity>
{
IsUpsert = true,
ReturnDocument = ReturnDocument.After
};
return await Collection.FindOneAndUpdateAsync(filter, update, option);
}
public async Task<AccountEntity> getOrInsert(string userGuid, string nickname, string accountId)
{
var filter = Builders<AccountEntity>.Filter
.Eq(x => x.UserGuid, userGuid);
var update = Builders<AccountEntity>.Update
.SetOnInsert(x => x.UserGuid, userGuid)
.Set(x => x.Nickname, nickname)
.Set(x => x.AccountId, accountId)
.SetOnInsert(x => x.AdditionalSlotCount, 0)
.SetOnInsert(x => x.GradeType, UgqGradeType.Amature)
.SetOnInsert(x => x.UpdatedAt, DateTime.UtcNow)
.SetOnInsert(x => x.CreatedAt, DateTime.UtcNow);
var option = new FindOneAndUpdateOptions<AccountEntity>
{
IsUpsert = true,
ReturnDocument = ReturnDocument.After
};
return await Collection.FindOneAndUpdateAsync(filter, update, option);
}
public async Task<AccountEntity?> modifyAccountGrade(string userGuid, UgqGradeType grade)
{
var filter = Builders<AccountEntity>.Filter
.Eq(x => x.UserGuid, userGuid);
var update = Builders<AccountEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.GradeType, grade);
var options = new FindOneAndUpdateOptions<AccountEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<AccountEntity?> promoteAccountGrade(string userGuid, UgqGradeType before, UgqGradeType after)
{
var filterBuilder = Builders<AccountEntity>.Filter;
var filter = filterBuilder.Eq(x => x.UserGuid, userGuid) &
(filterBuilder.Exists(x => x.GradeType, false) | filterBuilder.Eq(x => x.GradeType, before));
var update = Builders<AccountEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.GradeType, after);
var options = new FindOneAndUpdateOptions<AccountEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<AccountEntity?> addSlotCount(string userGuid, int? slotCountVersion, int addCount)
{
var filterBuilder = Builders<AccountEntity>.Filter;
var filter = filterBuilder.Eq(x => x.UserGuid, userGuid) &
filterBuilder.Eq(x => x.SlotCountVersion, slotCountVersion);
var update = Builders<AccountEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Inc(x => x.AdditionalSlotCount, addCount)
.Inc(x => x.SlotCountVersion, 1);
var options = new FindOneAndUpdateOptions<AccountEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<AccountEntity?> incCreatorPoint(string userGuid, int? creatorPointVersion, double amount)
{
var filterBuilder = Builders<AccountEntity>.Filter;
var filter = filterBuilder.Eq(x => x.UserGuid, userGuid) &
filterBuilder.Eq(x => x.CreatorPointVersion, creatorPointVersion);
var update = Builders<AccountEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Inc(x => x.CreatorPoint, amount)
.Inc(x => x.CreatorPointVersion, 1);
var options = new FindOneAndUpdateOptions<AccountEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<AccountEntity?> saveRefreshToken(string userGuid, string refreshToken, DateTime? refreshTokenExpryTime)
{
var filter = Builders<AccountEntity>.Filter
.Eq(x => x.UserGuid, userGuid);
var updateBuilder = Builders<AccountEntity>.Update;
var update = Builders<AccountEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.RefreshToken, refreshToken);
if(refreshTokenExpryTime != null)
update = updateBuilder.Combine(update, updateBuilder.Set(x => x.RefreshTokenExpiryTime, refreshTokenExpryTime));
var options = new FindOneAndUpdateOptions<AccountEntity>
{
ReturnDocument = ReturnDocument.After,
};
var updated = await Collection.FindOneAndUpdateAsync(filter, update, options);
return updated;
}
public async Task<AccountEntity?> deleteRefreshToken(string userGuid)
{
var filter = Builders<AccountEntity>.Filter
.Eq(x => x.UserGuid, userGuid);
var updateBuilder = Builders<AccountEntity>.Update;
var update = Builders<AccountEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.RefreshToken, "")
.Set(x => x.RefreshTokenExpiryTime, DateTime.Now);
var options = new FindOneAndUpdateOptions<AccountEntity>
{
ReturnDocument = ReturnDocument.After,
};
var updated = await Collection.FindOneAndUpdateAsync(filter, update, options);
return updated;
}
public async Task<AllAccountQueryResult> getAccounts(int pageNumber, int pageSize, string? serachText, AccountSortType sortType)
{
pageNumber = pageNumber < 1 ? 1 : pageNumber;
var filter = Builders<AccountEntity>.Filter.Empty;
var sortBuilder = Builders<AccountItemResult>.Sort;
SortDefinition<AccountItemResult> sort;
switch (sortType)
{
case AccountSortType.CreatedAtDesc:
sort = Builders<AccountItemResult>.Sort.Descending("CreatedAt");
break;
case AccountSortType.CreatedAtAsc:
sort = Builders<AccountItemResult>.Sort.Ascending("CreatedAt");
break;
default:
sort = Builders<AccountItemResult>.Sort.Descending("CreatedAt");
break;
}
var countFacet = AggregateFacet.Create("count",
new EmptyPipelineDefinition<AccountItemResult>()
.Count()
);
var pagingFacet = AggregateFacet.Create("paging",
new EmptyPipelineDefinition<AccountItemResult>()
.Sort(sort)
.Skip((pageNumber - 1) * pageSize)
.Limit(pageSize)
);
var pipeline = AccountQuery.allAccountPipeline<AccountItemResult>(
filter,
AccountQuery.AccountItemResultProjection)
.Facet(countFacet, pagingFacet);
var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()
?.FirstOrDefault()
?.Count;
var paging = aggregation.First()
.Facets.First(x => x.Name == "paging")
.Output<AccountItemResult>();
int totalPages = 0;
if (count != null)
totalPages = (int)Math.Ceiling((double)count / pageSize);
var result = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
return new AllAccountQueryResult
{
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = totalPages,
Items = paging.ToList(),
};
}
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
using System.Drawing;
using UGQDataAccess.Repository.Models;
using UGQDataAccess.Repository.Query;
namespace UGQDataAccess.Repository;
public class AdminAccountRepository : BaseRepository<AdminAccountEntity>
{
private const string CollectionName = "AdminAccount";
public AdminAccountRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<AdminAccountEntity?> get(string username)
{
var filter = Builders<AdminAccountEntity>.Filter
.Eq(x => x.Username, username);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task<AdminAccountEntity> signup(string username, string password, UGQAccountRole role)
{
var filter = Builders<AdminAccountEntity>.Filter
.Eq(x => x.Username, username);
var update = Builders<AdminAccountEntity>.Update
.SetOnInsert(x => x.Username, username)
.SetOnInsert(x => x.Password, password)
.SetOnInsert(x => x.Role, role)
.SetOnInsert(x => x.UpdatedAt, DateTime.UtcNow)
.SetOnInsert(x => x.CreatedAt, DateTime.UtcNow);
var option = new FindOneAndUpdateOptions<AdminAccountEntity>
{
IsUpsert = true,
ReturnDocument = ReturnDocument.After
};
return await Collection.FindOneAndUpdateAsync(filter, update, option);
}
public async Task<AdminAccountEntity> changePassword(string username, string newPassword)
{
var filter = Builders<AdminAccountEntity>.Filter
.Eq(x => x.Username, username);
var update = Builders<AdminAccountEntity>.Update
.Set(x => x.Password, newPassword)
.Set(x => x.UpdatedAt, DateTime.UtcNow);
var option = new FindOneAndUpdateOptions<AdminAccountEntity>
{
IsUpsert = true,
ReturnDocument = ReturnDocument.After
};
return await Collection.FindOneAndUpdateAsync(filter, update, option);
}
public async Task<AdminAccountEntity?> saveRefreshToken(string id, string refreshToken, DateTime? refreshTokenExpryTime)
{
var filter = Builders<AdminAccountEntity>.Filter
.Eq(x => x.Id, id);
var updateBuilder = Builders<AdminAccountEntity>.Update;
var update = Builders<AdminAccountEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.RefreshToken, refreshToken);
if (refreshTokenExpryTime != null)
update = updateBuilder.Combine(update, updateBuilder.Set(x => x.RefreshTokenExpiryTime, refreshTokenExpryTime));
var options = new FindOneAndUpdateOptions<AdminAccountEntity>
{
ReturnDocument = ReturnDocument.After,
};
var updated = await Collection.FindOneAndUpdateAsync(filter, update, options);
return updated;
}
public async Task<AdminAccountEntity?> deleteRefreshToken(string username)
{
var filter = Builders<AdminAccountEntity>.Filter
.Eq(x => x.Username, username);
var updateBuilder = Builders<AdminAccountEntity>.Update;
var update = Builders<AdminAccountEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.RefreshToken, "")
.Set(x => x.RefreshTokenExpiryTime, DateTime.Now);
var options = new FindOneAndUpdateOptions<AdminAccountEntity>
{
ReturnDocument = ReturnDocument.After,
};
var updated = await Collection.FindOneAndUpdateAsync(filter, update, options);
return updated;
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
namespace UGQDataAccess.Repository
{
public class BookmarkRepository : BaseRepository<BookmarkEntity>
{
private const string CollectionName = "Bookmark";
public BookmarkRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<BookmarkEntity?> get(long questId, string userGuid)
{
var builder = Builders<BookmarkEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) &
builder.Eq(x => x.UserGuid, userGuid);
BookmarkEntity? entity = await (await Collection.FindAsync(filter)).FirstOrDefaultAsync();
return entity;
}
public async Task<ServerErrorCode> insert(long questId, string userGuid)
{
var builder = Builders<BookmarkEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) &
builder.Eq(x => x.UserGuid, userGuid);
var update = Builders<BookmarkEntity>.Update
.SetOnInsert(x => x.QuestId, questId)
.SetOnInsert(x => x.UserGuid, userGuid)
.SetOnInsert(x => x.CreatedAt, DateTime.UtcNow);
var option = new UpdateOptions
{
IsUpsert = true,
};
var result = await Collection.UpdateOneAsync(filter, update, option);
return ServerErrorCode.Success;
}
public async Task<ServerErrorCode> delete(long questId, string userGuid)
{
var builder = Builders<BookmarkEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) &
builder.Eq(x => x.UserGuid, userGuid);
var result = await Collection.DeleteOneAsync(filter);
if (result.DeletedCount == 0)
return ServerErrorCode.UgqNullEntity;
return ServerErrorCode.Success;
}
}
}

View File

@@ -0,0 +1,95 @@
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using UGQDataAccess.Repository.Models;
using Amazon.SecurityToken.Model;
using Microsoft.AspNetCore.Mvc.RazorPages;
using UGQDataAccess.Repository.Query;
using System.Linq.Expressions;
using MetaAssets;
using ServerCommon.UGQ;
namespace UGQDataAccess.Repository;
public class CreatorPointHistoryRepository : BaseRepository<CreatorPointHistoryEntity>
{
private const string CollectionName = "CreatorPointHistory";
public CreatorPointHistoryRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<ServerErrorCode> insert(CreatorPointHistoryEntity entity)
{
await Collection.InsertOneAsync(entity);
return ServerErrorCode.Success;
}
public async Task<CreatorPointHistoryQueryResult> getList(string userGuid, int pageNumber, int pageSize, CreatorPointHistoryKind kind, DateTimeOffset startDate, DateTimeOffset endDate)
{
pageNumber = pageNumber < 1 ? 1 : pageNumber;
var filterBuilder = Builders<CreatorPointHistoryEntity>.Filter;
var filter = filterBuilder.Eq(x => x.UserGuid, userGuid);
if (kind != CreatorPointHistoryKind.None)
filter &= filterBuilder.Eq(x => x.Kind, kind);
if (startDate > endDate)
(startDate, endDate) = (endDate, startDate);
if (startDate != DateTimeOffset.MinValue)
filter &= filterBuilder.Gte(x => x.CreatedAt, startDate.UtcDateTime);
if (endDate != DateTimeOffset.MinValue)
filter &= filterBuilder.Lt(x => x.CreatedAt, endDate.UtcDateTime);
var sort = Builders<CreatorPointHistoryEntity>.Sort.Descending(x => x.CreatedAt);
var countFacet = AggregateFacet.Create("count",
new EmptyPipelineDefinition<CreatorPointHistoryEntity>()
.Count()
);
var pagingFacet = AggregateFacet.Create("paging",
new EmptyPipelineDefinition<CreatorPointHistoryEntity>()
.Sort(sort)
.Skip((pageNumber - 1) * pageSize)
.Limit(pageSize)
);
var pipeline = new EmptyPipelineDefinition<CreatorPointHistoryEntity>()
.Match(filter)
.Facet(countFacet, pagingFacet);
var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()
?.FirstOrDefault()
?.Count;
var paging = aggregation.First()
.Facets.First(x => x.Name == "paging")
.Output<CreatorPointHistoryEntity>();
int totalPages = 0;
if (count != null)
totalPages = (int)Math.Ceiling((double)count / pageSize);
return new CreatorPointHistoryQueryResult
{
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = totalPages,
Items = paging.ToList(),
};
}
}

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
using StackExchange.Redis;
using System.Drawing;
using ServerCommon;
namespace UGQDataAccess.Repository;
public class GameQuestDataRepository : BaseRepository<GameQuestDataEntity>
{
private const string CollectionName = "GameQuestData";
public GameQuestDataRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<GameQuestDataEntity?> insert(QuestContentEntity content,
QuestContentState state,
List<GameQuestDialogDataEntity> dialogues,
List<QuestMetaInfo> metas,
string ugqGameQuestDataForClientString,
UgqGradeType? gradeType = null)
{
var filterBuilder = Builders<GameQuestDataEntity>.Filter;
var filter = filterBuilder.Eq(x => x.QuestId, content.QuestId) &
filterBuilder.Eq(x => x.Revision, content.Revision) &
filterBuilder.Eq(x => x.State, state);
var updateGradeType = gradeType;
if (updateGradeType == null)
updateGradeType = content.GradeType;
var update = Builders<GameQuestDataEntity>.Update
.Set(x => x.QuestId, content.QuestId)
.Set(x => x.Revision, content.Revision)
.Set(x => x.UserGuid, content.UserGuid)
.Set(x => x.Author, content.Author)
.Set(x => x.BeaconId, content.BeaconId)
.Set(x => x.UgcBeaconGuid, content.UgcBeaconGuid)
.Set(x => x.UgcBeaconNickname, content.UgcBeaconNickname)
.Set(x => x.Title, content.Title)
.Set(x => x.Langs, content.Langs)
.Set(x => x.TitleImagePath, content.TitleImagePath)
.Set(x => x.BannerImagePath, content.BannerImagePath)
.Set(x => x.Description, content.Description)
.Set(x => x.GradeType, updateGradeType)
.Set(x => x.State, state)
.Set(x => x.Cost, content.Cost)
.Set(x => x.Shutdown, false)
.Set(x => x.Tasks, content.Tasks)
.Set(x => x.Dialogs, dialogues)
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.QuestScriptMetas, metas)
.Set(x => x.UgqGameQuestDataForClientString, ugqGameQuestDataForClientString)
.SetOnInsert(x => x.CreatedAt, DateTime.UtcNow);
var option = new FindOneAndUpdateOptions<GameQuestDataEntity>
{
IsUpsert = true,
ReturnDocument = ReturnDocument.After
};
return await Collection.FindOneAndUpdateAsync(filter, update, option);
}
public async Task delete(long questId, long revision, QuestContentState state)
{
var filterBuilder = Builders<GameQuestDataEntity>.Filter;
var filter = filterBuilder.Eq(x => x.QuestId, questId) &
filterBuilder.Eq(x => x.Revision, revision) &
filterBuilder.Eq(x => x.State, state);
await Collection.DeleteOneAsync(filter);
}
public async Task<GameQuestDataEntity?> get(long questId, long revision, QuestContentState state)
{
var builder = Builders<GameQuestDataEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) &
builder.Eq(x => x.Revision, revision) &
builder.Eq(x => x.State, state);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task<GameQuestDataEntity?> getLatestRevision(long questId)
{
var builder = Builders<GameQuestDataEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId);
GameQuestDataEntity? entity = await Collection.Find(filter)
.SortByDescending(x => x.Revision)
.Limit(1)
.FirstOrDefaultAsync();
return entity;
}
public async Task<GameQuestDataEntity?> setShutdown(long questId, long revision)
{
var builder = Builders<GameQuestDataEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) & builder.Eq(x => x.Revision, revision) &
builder.Eq(x => x.State, QuestContentState.Live);
var update = Builders<GameQuestDataEntity>.Update
.Set(x => x.Shutdown, true);
var options = new FindOneAndUpdateOptions<GameQuestDataEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
namespace UGQDataAccess.Repository
{
public class LikeRepository : BaseRepository<LikeEntity>
{
private const string CollectionName = "Like";
public LikeRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<LikeEntity?> get(long questId, string userGuid)
{
var builder = Builders<LikeEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) &
builder.Eq(x => x.UserGuid, userGuid);
LikeEntity? entity = await (await Collection.FindAsync(filter)).FirstOrDefaultAsync();
return entity;
}
public async Task<ServerErrorCode> insert(long questId, long revision, string userGuid)
{
var builder = Builders<LikeEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) &
builder.Eq(x => x.UserGuid, userGuid);
var update = Builders<LikeEntity>.Update
.SetOnInsert(x => x.QuestId, questId)
.SetOnInsert(x => x.UserGuid, userGuid)
.SetOnInsert(x => x.CreatedAt, DateTime.UtcNow);
var option = new UpdateOptions
{
IsUpsert = true,
};
var result = await Collection.UpdateOneAsync(filter, update, option);
return ServerErrorCode.Success;
}
public async Task<ServerErrorCode> delete(long questId, long revision, string userGuid)
{
var builder = Builders<LikeEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) &
builder.Eq(x => x.UserGuid, userGuid);
var result = await Collection.DeleteOneAsync(filter);
if (result.DeletedCount == 0)
return ServerErrorCode.UgqNullEntity;
return ServerErrorCode.Success;
}
}
}

View File

@@ -0,0 +1,266 @@
using Amazon.DynamoDBv2.Model;
using MongoDB.Bson.Serialization.Attributes;
using System.Text.Json.Serialization;
using UGQDatabase.Models;
namespace UGQDataAccess.Repository.Models;
#pragma warning disable CS8618
public class UGQSaveQuestModel
{
public int BeaconId { get; set; }
public string? UgcBeaconGuid { get; set; }
public string? UgcBeaconNickname { get; set; }
public TextEntity Title { get; set; }
public List<string> Languages { get; set; }
public TextEntity Description { get; set; }
public int Cost { get; set; }
public List<TaskEntity> Tasks { get; set; }
public string Savelanguage { get; set; }
}
public class UGQSaveDialogSequenceAction
{
public DialogTalker Talker { get; set; }
public int Type { get; set; }
public TextEntity Talk { get; set; }
public int Condition { get; set; }
public int ConditionValue { get; set; }
public int NextSequence { get; set; }
public int NpcAction { get; set; }
}
public class UGQSaveDialogSequence
{
public int SequenceId { get; set; }
public List<UGQSaveDialogSequenceAction> Actions { get; set; }
}
public class UGQSaveDialogModel
{
public List<UGQSaveDialogSequence> Sequences { get; set; }
}
public class UGQStats
{
public int Accepted { get; set; } = 0;
public int Completed { get; set; } = 0;
public int Like { get; set; } = 0;
public int Bookmark { get; set; } = 0;
public int Report { get; set; } = 0;
public double TotalProfit { get; set; } = 0;
}
public class SummaryItemResult
{
public string QuestContentId { get; set; }
public long QuestId { get; set; }
public long Revision { get; set; }
public string UserGuid { get; set; }
public string Author { get; set; }
public int BeaconId { get; set; }
public string? UgcBeaconGuid { get; set; }
public string? UgcBeaconNickname { get; set; }
public UgqGradeType GradeType { get; set; }
public TextEntity Title { get; set; }
public List<string> Languages { get; set; }
public TextEntity Description { get; set; }
public int Cost { get; set; }
public string TitleImagePath { get; set; }
public string BannerImagePath { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public QuestContentState State { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime CreatedAt { get; set; }
public int AcceptedCount { get; set; }
public int CompletedCount { get; set; }
public int LikeCount { get; set; }
public int BookmarkCount { get; set; }
public int ReportCount { get; set; }
public double TotalProfit { get; set; }
public string Savelanguage { get; set; }
}
public class SummaryQueryResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<SummaryItemResult> Items { get; set; }
}
public class AllSummaryQueryResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<SummaryItemResult> Items { get; set; }
}
public class QuestProfitStatsItemResult
{
public string QuestContentId { get; set; }
public long QuestId { get; set; }
public long Revision { get; set; }
public TextEntity Title { get; set; }
public List<string> Languages { get; set; }
public string TitleImagePath { get; set; }
public string BannerImagePath { get; set; }
public int CompletedCount { get; set; }
public double TotalProfit { get; set; }
}
public class QuestProfitStatsQueryResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<QuestProfitStatsItemResult> Items { get; set; }
}
public class QuestBoardItemResult
{
public long QuestId { get; set; }
public long Revision { get; set; }
public string Author { get; set; }
public TextEntity Title { get; set; }
public List<string> Languages { get; set; }
public TextEntity Description { get; set; }
public int Cost { get; set; }
public UgqGradeType GradeType { get; set; }
public string TitleImagePath { get; set; }
public string BannerImagePath { get; set; }
public DateTime UpdatedAt { get; set; }
public int LikeCount { get; set; }
public int BookmarkCount { get; set; }
}
public class QuestBoardQueryResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<QuestBoardItemResult> Items { get; set; }
}
public class QuestBoardSportlightQueryResult
{
public class DateRangeResult
{
public QuestBoardItemResult? Today { get; set; }
public QuestBoardItemResult? ThisWeek { get; set; }
public QuestBoardItemResult? ThisMonth { get; set; }
}
public DateRangeResult MostLiked { get; set; }
public DateRangeResult MostBookmarked { get; set; }
}
public class QuestBoardDetailItemResult
{
public long QuestId { get; set; }
public long Revision { get; set; }
public string Author { get; set; }
public TextEntity Title { get; set; }
public List<string> Languages { get; set; }
public TextEntity Description { get; set; }
public int Cost { get; set; }
public UgqGradeType GradeType { get; set; }
public string TitleImagePath { get; set; }
public string BannerImagePath { get; set; }
public int BeaconId { get; set; }
public string? UgcBeaconGuid { get; set; }
public string? UgcBeaconNickname { get; set; }
public DateTime UpdatedAt { get; set; }
public int LikeCount { get; set; }
public int BookmarkCount { get; set; }
public int QuestAcceptedCount { get; set; }
public int QuestCompletedCount { get; set; }
public bool Liked { get; set; }
public bool Bookmarked { get; set; }
}
public class CreatorPointHistoryQueryResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<CreatorPointHistoryEntity> Items { get; set; }
}
public class AccountItemResult
{
public string UserGuid { get; set; }
public string Nickname { get; set; }
public string AccountId { get; set; }
public int AdditionalSlotCount { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType GradeType { get; set; }
public double CreatorPoint { get; set; }
public DateTime CreatedAt { get; set; }
public int QuestCount { get; set; }
}
public class AllAccountQueryResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<AccountItemResult> Items { get; set; }
}
public class ReserveAccountGradeItemResult
{
public string ReserveId { get; set; }
public string UserGuid { get; set; }
public string AccountId { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType CurrentGradeType { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType ReserveGradeType { get; set; }
public DateTime ReserveTime { get; set; }
public DateTime UpdatedAt { get; set; }
public bool IsCompleted { get; set; }
}
public class AllReserveAccountGradeResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<ReserveAccountGradeItemResult> Items { get; set; }
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
namespace UGQDataAccess.Repository
{
public class NpcNameRepository : BaseRepository<NpcNameEntity>
{
private const string CollectionName = "NpcName";
public NpcNameRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task upsertNpcName(int npcId, TextEntity npcName)
{
var filter = Builders<NpcNameEntity>.Filter.Eq(x => x.NpcId, npcId);
var update = Builders<NpcNameEntity>.Update
.Set(x => x.NpcName, npcName);
var option = new UpdateOptions
{
IsUpsert = true,
};
await Collection.UpdateOneAsync(filter, update, option);
}
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using UGQDataAccess.Repository.Models;
using UGQDatabase.Models;
using static UGQDataAccess.Repository.Query.QuestContentQuery;
namespace UGQDataAccess.Repository.Query;
public static class AccountQuery
{
public class AllAccountQueryJoin : AccountEntity
{
public IEnumerable<QuestContentEntity> QuestContents { get; set; } = null!;
public int QuestCount { get; set; } = 0;
}
public static Expression<Func<AllAccountQueryJoin, AccountItemResult>> AccountItemResultProjection =
x => new AccountItemResult
{
UserGuid = x.UserGuid,
Nickname = x.Nickname,
AccountId = x.AccountId ?? "",
AdditionalSlotCount = x.AdditionalSlotCount,
GradeType = x.GradeType,
CreatorPoint = x.CreatorPoint,
CreatedAt = x.CreatedAt,
QuestCount = x.QuestCount,
};
public static PipelineDefinition<AccountEntity, T> allAccountPipeline<T>(
FilterDefinition<AccountEntity> filter,
Expression<Func<AllAccountQueryJoin, T>> projection)
{
var lookupStage1 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "QuestContent" },
{ "localField", "UserGuid" },
{ "foreignField", "UserGuid" },
{ "pipeline",
new BsonArray
{
new BsonDocument("$match",
new BsonDocument("IsDeleted", false))
} },
{ "as", "QuestContents" }
});
var addFields1 = new BsonDocument("$addFields",
new BsonDocument("QuestCount",
new BsonDocument("$size", "$QuestContents")));
var pipeline = new EmptyPipelineDefinition<AccountEntity>()
.Match(filter)
.AppendStage<AccountEntity, AccountEntity, AllAccountQueryJoin>(lookupStage1)
.AppendStage<AccountEntity, AllAccountQueryJoin, AllAccountQueryJoin>(addFields1)
.Project(projection);
return pipeline;
}
}

View File

@@ -0,0 +1,563 @@
using System.Linq.Expressions;
using MongoDB.Bson;
using MongoDB.Driver;
using UGQDataAccess.Repository.Models;
using UGQDatabase.Models;
namespace UGQDataAccess.Repository.Query;
public static class QuestContentQuery
{
public class QuestBoardQueryJoin : QuestContentEntity
{
public IEnumerable<LikeEntity> Likes { get; set; } = null!;
public int LikeCount { get; set; } = 0;
public IEnumerable<BookmarkEntity> Bookmarks { get; set; } = null!;
public int BookmarkCount { get; set; } = 0;
public List<QuestAcceptedEntity> QuestAccepteds { get; set; } = null!;
public int QuestAcceptedCount { get; set; } = 0;
public List<QuestAcceptedEntity> QuestCompleteds { get; set; } = null!;
public int QuestCompletedCount { get; set; } = 0;
}
public class QuestSummariesQueryJoin : QuestContentEntity
{
public IEnumerable<LikeEntity> Likes { get; set; } = null!;
public int LikeCount { get; set; } = 0;
public IEnumerable<BookmarkEntity> Bookmarks { get; set; } = null!;
public int BookmarkCount { get; set; } = 0;
public List<QuestAcceptedEntity> QuestAccepteds { get; set; } = null!;
public int QuestAcceptedCount { get; set; } = 0;
public List<QuestAcceptedEntity> QuestCompleteds { get; set; } = null!;
public int QuestCompletedCount { get; set; } = 0;
public List<QuestAcceptedEntity> QuestAborteds { get; set; } = null!;
public int QuestAbortedCount { get; set; } = 0;
public List<ReportEntity> Reports { get; set; } = null!;
public int ReporCount { get; set; } = 0;
public List<CreatorPointHistoryEntity> ProfitHistories { get; set; } = null!;
public double TotalProfit { get; set; } = 0;
}
public class QuestProfitStatsQueryJoin : QuestContentEntity
{
public List<QuestAcceptedEntity> QuestCompleteds { get; set; } = null!;
public int QuestCompletedCount { get; set; } = 0;
public List<CreatorPointHistoryEntity> ProfitHistories { get; set; } = null!;
public double TotalProfit { get; set; } = 0;
}
public class QuestAcceptedLookup : QuestAcceptedEntity
{
}
public static Expression<Func<QuestBoardQueryJoin, QuestBoardItemResult>> QuestBoardItemResultProjection =
x => new QuestBoardItemResult
{
QuestId = x.QuestId,
Revision = x.Revision,
Author = x.Author,
Title = x.Title,
Languages = x.Langs,
Cost = x.Cost,
GradeType = x.GradeType,
TitleImagePath = x.TitleImagePath,
BannerImagePath = x.BannerImagePath,
UpdatedAt = x.UpdatedAt,
LikeCount = x.LikeCount,
BookmarkCount = x.BookmarkCount,
};
public static Expression<Func<QuestSummariesQueryJoin, SummaryItemResult>> SummaryItemResultProjection =
x => new SummaryItemResult
{
QuestContentId = x.Id.ToString(),
QuestId = x.QuestId,
Revision = x.Revision,
BeaconId = x.BeaconId,
UserGuid = x.UserGuid,
Author = x.Author,
UgcBeaconGuid = x.UgcBeaconGuid,
UgcBeaconNickname = x.UgcBeaconNickname,
GradeType = x.GradeType,
Title = x.Title,
Languages = x.Langs,
Description = x.Description,
Cost = x.Cost,
TitleImagePath = x.TitleImagePath,
BannerImagePath = x.BannerImagePath,
State = x.State,
UpdatedAt = x.UpdatedAt,
CreatedAt = x.CreatedAt,
AcceptedCount = x.QuestAcceptedCount,
CompletedCount = x.QuestCompletedCount,
LikeCount = x.LikeCount,
BookmarkCount = x.BookmarkCount,
ReportCount = x.ReporCount,
TotalProfit = x.TotalProfit,
Savelanguage = x.Savelanguage,
};
public static Expression<Func<QuestProfitStatsQueryJoin, QuestProfitStatsItemResult>> QuestProfitStatsResultProjection =
x => new QuestProfitStatsItemResult
{
QuestContentId = x.Id.ToString(),
QuestId = x.QuestId,
Revision = x.Revision,
Title = x.Title,
Languages = x.Langs,
TitleImagePath = x.TitleImagePath,
BannerImagePath = x.BannerImagePath,
CompletedCount = x.QuestCompletedCount,
TotalProfit = x.TotalProfit,
};
static List<UgqGradeType> getGradeTypes(UgqUICategoryGradeType gradeType)
{
switch (gradeType)
{
case UgqUICategoryGradeType.Amateur:
return [UgqGradeType.Amature];
case UgqUICategoryGradeType.RisingStar:
return [UgqGradeType.RisingStar];
case UgqUICategoryGradeType.Master:
return [UgqGradeType.Master1, UgqGradeType.Master2, UgqGradeType.Master3];
}
return [];
}
static async Task<List<long>> getBookmarks(IMongoCollection<BookmarkEntity> bookmarkCollection, string userGuid)
{
var filterBuilder = Builders<BookmarkEntity>.Filter;
var filter = filterBuilder.Eq(x => x.UserGuid, userGuid);
var list = await bookmarkCollection.Find(filter).ToListAsync();
return list.Select(x => x.QuestId).ToList();
}
static async Task<List<int>> searchNpcs(IMongoCollection<NpcNameEntity> npcNameCollection, string searchText)
{
var filterBuilder = Builders<NpcNameEntity>.Filter;
var filter = filterBuilder.Empty;
if (string.IsNullOrEmpty(searchText) == false)
{
filter &= (filterBuilder.Regex(x => x.NpcName.Kr, searchText) |
filterBuilder.Regex(x => x.NpcName.En, searchText) |
filterBuilder.Regex(x => x.NpcName.Jp, searchText));
}
var list = await npcNameCollection.Find(filter).ToListAsync();
return list.Select(x => x.NpcId).ToList();
}
static FilterDefinition<QuestContentEntity> searchTitle(FilterDefinitionBuilder<QuestContentEntity> filterBuilder, string searchText)
{
return filterBuilder.Regex(x => x.Title.Kr, searchText) |
filterBuilder.Regex(x => x.Title.En, searchText) |
filterBuilder.Regex(x => x.Title.Jp, searchText);
}
public static async Task<FilterDefinition<QuestContentEntity>> summariesFilter(string? userGuid,
UgqSearchType searchType, string? searchText, QuestContentState state,
IMongoCollection<AccountEntity> accountCollection,
IMongoCollection<NpcNameEntity> npcNameCollection)
{
var filterBuilder = Builders<QuestContentEntity>.Filter;
var filter = filterBuilder.Eq(x => x.IsDeleted, false);
if (state != QuestContentState.None)
{
if(state == QuestContentState.Uncomplate ||
state == QuestContentState.Editable)
{
var UncomplateOrFilter = filterBuilder.Eq(x => x.State, QuestContentState.Uncomplate);
var EditableOrFilter = filterBuilder.Eq(x => x.State, QuestContentState.Editable);
filter &= filterBuilder.Or(UncomplateOrFilter, EditableOrFilter);
}
else
{
filter &= filterBuilder.Eq(x => x.State, state);
}
}
if (string.IsNullOrEmpty(userGuid) == false)
filter &= filterBuilder.Eq(x => x.UserGuid, userGuid);
if (string.IsNullOrEmpty(searchText) == false)
{
switch (searchType)
{
case UgqSearchType.Title:
filter &= searchTitle(filterBuilder, searchText);
break;
case UgqSearchType.Beacon:
var npcIds = await searchNpcs(npcNameCollection, searchText);
filter &= (filterBuilder.In(x => x.BeaconId, npcIds) |
filterBuilder.Regex(x => x.UgcBeaconNickname, searchText));
break;
}
}
return filter;
}
public static async Task<FilterDefinition<QuestContentEntity>> questBoardFilter(string? userGuid,
UgqUICategoryGradeType gradeType, UgqSearchType searchType, string? searchText,
IMongoCollection<AccountEntity> accountCollection,
IMongoCollection<NpcNameEntity> npcNameCollection)
{
var filterBuilder = Builders<QuestContentEntity>.Filter;
var filter = filterBuilder.Eq(x => x.IsDeleted, false);
filter &= filterBuilder.Eq(x => x.State, QuestContentState.Live);
if (string.IsNullOrEmpty(userGuid) == false)
filter &= filterBuilder.Eq(x => x.UserGuid, userGuid);
if(gradeType != UgqUICategoryGradeType.None)
{
var gradeTypes = getGradeTypes(gradeType);
filter &= filterBuilder.In(x => x.GradeType, gradeTypes);
}
if (string.IsNullOrEmpty(searchText) == false)
{
switch (searchType)
{
case UgqSearchType.Title:
filter &= searchTitle(filterBuilder, searchText);
break;
case UgqSearchType.Beacon:
var npcIds = await searchNpcs(npcNameCollection, searchText);
filter &= (filterBuilder.In(x => x.BeaconId, npcIds) |
filterBuilder.Regex(x => x.UgcBeaconNickname, searchText));
break;
}
}
return filter;
}
public static async Task<FilterDefinition<QuestContentEntity>> questBoardBookmarkFilter(string userGuid,
UgqUICategoryGradeType gradeType, UgqSearchType searchType, string? searchText,
IMongoCollection<NpcNameEntity> npcNameCollection,
IMongoCollection<BookmarkEntity> bookmarkCollection)
{
var filterBuilder = Builders<QuestContentEntity>.Filter;
var filter = filterBuilder.Eq(x => x.IsDeleted, false);
filter &= filterBuilder.Eq(x => x.State, QuestContentState.Live);
if (gradeType != UgqUICategoryGradeType.None)
{
var gradeTypes = getGradeTypes(gradeType);
filter &= filterBuilder.In(x => x.GradeType, gradeTypes);
}
var bookmarkQuestIds = await getBookmarks(bookmarkCollection, userGuid);
filter &= filterBuilder.In(x => x.QuestId, bookmarkQuestIds);
if (string.IsNullOrEmpty(searchText) == false)
{
switch (searchType)
{
case UgqSearchType.Title:
filter &= searchTitle(filterBuilder, searchText);
break;
case UgqSearchType.Beacon:
var npcIds = await searchNpcs(npcNameCollection, searchText);
filter &= (filterBuilder.In(x => x.BeaconId, npcIds) |
filterBuilder.Regex(x => x.UgcBeaconNickname, searchText));
break;
}
}
return filter;
}
public static SortDefinition<T> questBoardSort<T>(UgqSortType sortType)
{
var sortBuilder = Builders<T>.Sort;
SortDefinition<T> sort;
switch (sortType)
{
case UgqSortType.New:
sort = Builders<T>.Sort.Descending("UpdatedAt");
break;
case UgqSortType.Like:
sort = Builders<T>.Sort.Descending("LikeCount");
break;
case UgqSortType.Bookmark:
sort = Builders<T>.Sort.Descending("BookmarkCount");
break;
default:
sort = Builders<T>.Sort.Descending("UpdatedAt");
break;
}
return sort;
}
public static PipelineDefinition<QuestContentEntity, T> summariesPipeline<T>(
IMongoCollection<LikeEntity> likeCollection,
IMongoCollection<BookmarkEntity> bookmarkCollection,
FilterDefinition<QuestContentEntity> filter,
Expression<Func<QuestSummariesQueryJoin, T>> projection)
{
var lookupStage1 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "QuestAccepted" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "pipeline",
new BsonArray
{
new BsonDocument("$match",
new BsonDocument("Reason", "Player"))
} },
{ "as", "QuestAccepteds" }
});
var addFields1 = new BsonDocument("$addFields",
new BsonDocument("QuestAcceptedCount",
new BsonDocument("$size", "$QuestAccepteds")));
var lookupStage2 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "QuestCompleted" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "as", "QuestCompleteds" }
});
var addFields2 = new BsonDocument("$addFields",
new BsonDocument("QuestCompletedCount",
new BsonDocument("$size", "$QuestCompleteds")));
var lookupStage3 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "QuestAborted" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "as", "QuestAborteds" }
});
var addFields3 = new BsonDocument("$addFields",
new BsonDocument("QuestAbortedCount",
new BsonDocument("$size", "$QuestAborteds")));
var lookupStage4 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "CreatorPointHistory" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "pipeline",
new BsonArray
{
new BsonDocument("$match",
new BsonDocument("Kind", "QuestProfit"))
} },
{ "as", "ProfitHistories" }
});
var addFields4 = new BsonDocument("$addFields",
new BsonDocument("TotalProfit",
new BsonDocument("$sum", "$ProfitHistories.Amount")));
var lookupStage5 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "Report" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "as", "Reports" }
});
var addFields5 = new BsonDocument("$addFields",
new BsonDocument("ReporCount",
new BsonDocument("$size", "$Reports")));
var pipeline = new EmptyPipelineDefinition<QuestContentEntity>()
.Match(filter)
.Lookup<QuestContentEntity, QuestContentEntity, LikeEntity, QuestSummariesQueryJoin>(
likeCollection,
x => x.QuestId,
x => x.QuestId,
x => x.Likes)
.Lookup<QuestContentEntity, QuestSummariesQueryJoin, BookmarkEntity, QuestSummariesQueryJoin>(
bookmarkCollection,
x => x.QuestId,
x => x.QuestId,
x => x.Bookmarks)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>("{$addFields: {LikeCount: {$size:'$Likes'}}}")
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>("{$addFields: {BookmarkCount: {$size:'$Bookmarks'}}}")
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(lookupStage1)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(addFields1)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(lookupStage2)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(addFields2)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(lookupStage3)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(addFields3)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(lookupStage4)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(addFields4)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(lookupStage5)
.AppendStage<QuestContentEntity, QuestSummariesQueryJoin, QuestSummariesQueryJoin>(addFields5)
.Project(projection);
return pipeline;
}
public static PipelineDefinition<QuestContentEntity, T> questProfitStatsPipeline<T>(
FilterDefinition<QuestContentEntity> filter,
Expression<Func<QuestProfitStatsQueryJoin, T>> projection)
{
var lookupStage1 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "QuestCompleted" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "as", "QuestCompleteds" }
});
var addFields1 = new BsonDocument("$addFields",
new BsonDocument("QuestCompletedCount",
new BsonDocument("$size", "$QuestCompleteds")));
var lookupStage2 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "CreatorPointHistory" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "pipeline",
new BsonArray
{
new BsonDocument("$match",
new BsonDocument("Kind", "QuestProfit"))
} },
{ "as", "ProfitHistories" }
});
var addFields2 = new BsonDocument("$addFields",
new BsonDocument("TotalProfit",
new BsonDocument("$sum", "$ProfitHistories.Amount")));
var pipeline = new EmptyPipelineDefinition<QuestContentEntity>()
.Match(filter)
.AppendStage<QuestContentEntity, QuestContentEntity, QuestProfitStatsQueryJoin>(lookupStage1)
.AppendStage<QuestContentEntity, QuestProfitStatsQueryJoin, QuestProfitStatsQueryJoin>(addFields1)
.AppendStage<QuestContentEntity, QuestProfitStatsQueryJoin, QuestProfitStatsQueryJoin>(lookupStage2)
.AppendStage<QuestContentEntity, QuestProfitStatsQueryJoin, QuestProfitStatsQueryJoin>(addFields2)
.Project(projection);
return pipeline;
}
public static PipelineDefinition<QuestContentEntity, T> questBoardPipeline<T>(
IMongoCollection<LikeEntity> likeCollection,
IMongoCollection<BookmarkEntity> bookmarkCollection,
FilterDefinition<QuestContentEntity> filter,
Expression<Func<QuestBoardQueryJoin, T>> projection)
{
var pipeline = new EmptyPipelineDefinition<QuestContentEntity>()
.Match(filter)
.Lookup<QuestContentEntity, QuestContentEntity, LikeEntity, QuestBoardQueryJoin>(
likeCollection,
x => x.QuestId,
x => x.QuestId,
x => x.Likes)
.Lookup<QuestContentEntity, QuestBoardQueryJoin, BookmarkEntity, QuestBoardQueryJoin>(
bookmarkCollection,
x => x.QuestId,
x => x.QuestId,
x => x.Bookmarks)
.AppendStage<QuestContentEntity, QuestBoardQueryJoin, QuestBoardQueryJoin>("{$addFields: {LikeCount: {$size:'$Likes'}}}")
.AppendStage<QuestContentEntity, QuestBoardQueryJoin, QuestBoardQueryJoin>("{$addFields: {BookmarkCount: {$size:'$Bookmarks'}}}")
.Project(projection);
return pipeline;
}
public static PipelineDefinition<QuestContentEntity, T> questBoardDetailPipeline<T>(
IMongoCollection<LikeEntity> likeCollection,
IMongoCollection<BookmarkEntity> bookmarkCollection,
FilterDefinition<QuestContentEntity> filter,
Expression<Func<QuestBoardQueryJoin, T>> projection)
{
var lookupStage1 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "QuestAccepted" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "pipeline",
new BsonArray
{
new BsonDocument("$match",
new BsonDocument("Reason", "Player"))
} },
{ "as", "QuestAccepteds" }
});
var lookupStage2 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "QuestCompleted" },
{ "localField", "QuestId" },
{ "foreignField", "QuestId" },
{ "as", "QuestCompleteds" }
});
var pipeline = new EmptyPipelineDefinition<QuestContentEntity>()
.Match(filter)
.Lookup<QuestContentEntity, QuestContentEntity, LikeEntity, QuestBoardQueryJoin>(
likeCollection,
x => x.QuestId,
x => x.QuestId,
x => x.Likes)
.Lookup<QuestContentEntity, QuestBoardQueryJoin, BookmarkEntity, QuestBoardQueryJoin>(
bookmarkCollection,
x => x.QuestId,
x => x.QuestId,
x => x.Bookmarks)
.AppendStage<QuestContentEntity, QuestBoardQueryJoin, QuestBoardQueryJoin>(lookupStage1)
.AppendStage<QuestContentEntity, QuestBoardQueryJoin, QuestBoardQueryJoin>(lookupStage2)
.AppendStage<QuestContentEntity, QuestBoardQueryJoin, QuestBoardQueryJoin>("{$addFields: {LikeCount: {$size:'$Likes'}}}")
.AppendStage<QuestContentEntity, QuestBoardQueryJoin, QuestBoardQueryJoin>("{$addFields: {BookmarkCount: {$size:'$Bookmarks'}}}")
.AppendStage<QuestContentEntity, QuestBoardQueryJoin, QuestBoardQueryJoin>("{$addFields: {QuestAcceptedCount: {$size:'$QuestAccepteds'}}}")
.AppendStage<QuestContentEntity, QuestBoardQueryJoin, QuestBoardQueryJoin>("{$addFields: {QuestCompletedCount: {$size:'$QuestCompleteds'}}}")
.Project(projection);
return pipeline;
}
}

View File

@@ -0,0 +1,51 @@
using MongoDB.Bson;
using MongoDB.Driver;
using System.Linq.Expressions;
using UGQDataAccess.Repository.Models;
using UGQDatabase.Models;
public static class ReserveAccountGradeQuery
{
public class AllReserveAccountGradeQueryJoin : ReserveAccountGradeEntity
{
public IEnumerable<AccountEntity> Accounts { get; set; } = null!;
}
public static Expression<Func<AllReserveAccountGradeQueryJoin, ReserveAccountGradeItemResult>> ReserveAccountGradeItemResultProjection =
x => new ReserveAccountGradeItemResult
{
ReserveId = x.Id.ToString(),
UserGuid = x.UserGuid,
CurrentGradeType = x.BeforeGradeType,
AccountId = x.Accounts.First().AccountId ?? string.Empty,
ReserveGradeType = x.ReserveGradeType,
ReserveTime = x.ReserveTime,
UpdatedAt = x.UpdatedAt,
IsCompleted = x.IsCompleted,
};
public static PipelineDefinition<ReserveAccountGradeEntity, T> allReserveAccountGradePipeline<T>(
FilterDefinition<ReserveAccountGradeEntity> filter,
Expression<Func<AllReserveAccountGradeQueryJoin, T>> projection)
{
var lookupStage1 = new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "Account" },
{ "localField", "UserGuid" },
{ "foreignField", "UserGuid" },
{ "as", "Accounts" }
});
var pipeline = new EmptyPipelineDefinition<ReserveAccountGradeEntity>()
.Match(filter)
.AppendStage<ReserveAccountGradeEntity, ReserveAccountGradeEntity, AllReserveAccountGradeQueryJoin>(lookupStage1)
.Project(projection);
return pipeline;
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
namespace UGQDataAccess.Repository;
public class QuestAbortedRepository : BaseRepository<QuestAbortedEntity>
{
private const string CollectionName = "QuestAborted";
public QuestAbortedRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<List<QuestAbortedEntity>> getAll()
{
return await Collection.Find(Builders<QuestAbortedEntity>.Filter.Empty).ToListAsync();
}
public async Task<ServerErrorCode> insert(long questId, long revision, string author, UGQAbortReason reason, string userGuid)
{
var entity = new QuestAbortedEntity
{
QuestId = questId,
Revision = revision,
Author = author,
UserGuid = userGuid,
Reason = reason,
CreatedAt = DateTime.UtcNow,
};
await Collection.InsertOneAsync(entity);
return ServerErrorCode.Success;
}
public async Task<QuestAbortedEntity?> setAuthor(string id, string author)
{
var filterBuilder = Builders<QuestAbortedEntity>.Filter;
var filter = filterBuilder.Eq(x => x.Id, id);
var update = Builders<QuestAbortedEntity>.Update
.Set(x => x.Author, author);
var options = new FindOneAndUpdateOptions<QuestAbortedEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
namespace UGQDataAccess.Repository;
public class QuestAcceptedRepository : BaseRepository<QuestAcceptedEntity>
{
private const string CollectionName = "QuestAccepted";
public QuestAcceptedRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<List<QuestAcceptedEntity>> getAll()
{
return await Collection.Find(Builders<QuestAcceptedEntity>.Filter.Empty).ToListAsync();
}
public async Task<ServerErrorCode> insert(long questId, long revision, string author, UGQAcceptReason reason, string userGuid)
{
var entity = new QuestAcceptedEntity
{
QuestId = questId,
Revision = revision,
Author = author,
UserGuid = userGuid,
Reason = reason,
CreatedAt = DateTime.UtcNow,
};
await Collection.InsertOneAsync(entity);
return ServerErrorCode.Success;
}
public async Task<QuestAcceptedEntity?> setAuthor(string id, string author)
{
var filterBuilder = Builders<QuestAcceptedEntity>.Filter;
var filter = filterBuilder.Eq(x => x.Id, id);
var update = Builders<QuestAcceptedEntity>.Update
.Set(x => x.Author, author);
var options = new FindOneAndUpdateOptions<QuestAcceptedEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
using ServerCommon;
namespace UGQDataAccess.Repository;
public class QuestCompletedRepository : BaseRepository<QuestCompletedEntity>
{
private const string CollectionName = "QuestCompleted";
public QuestCompletedRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<List<QuestCompletedEntity>> getAll()
{
return await Collection.Find(Builders<QuestCompletedEntity>.Filter.Empty).ToListAsync();
}
public async Task<ServerErrorCode> insert(long questId, long revision, string author, string userGuid)
{
var entity = new QuestCompletedEntity
{
QuestId = questId,
Revision = revision,
Author = author,
UserGuid = userGuid,
CreatedAt = DateTime.UtcNow,
};
await Collection.InsertOneAsync(entity);
return ServerErrorCode.Success;
}
public async Task<QuestCompletedEntity?> setAuthor(string id, string author)
{
var filterBuilder = Builders<QuestCompletedEntity>.Filter;
var filter = filterBuilder.Eq(x => x.Id, id);
var update = Builders<QuestCompletedEntity>.Update
.Set(x => x.Author, author);
var options = new FindOneAndUpdateOptions<QuestCompletedEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<long> getCount(string author)
{
var builder = Builders<QuestCompletedEntity>.Filter;
var filter = builder.Eq(x => x.Author, author);
var result = await Collection.Find(filter).CountDocumentsAsync();
return result;
}
}

View File

@@ -0,0 +1,686 @@
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using UGQDataAccess.Repository.Models;
using Amazon.SecurityToken.Model;
using Microsoft.AspNetCore.Mvc.RazorPages;
using UGQDataAccess.Repository.Query;
using System.Linq.Expressions;
using ServerCommon.UGQ;
using ServerCommon.UGQ.Models;
using UGQDataAccess;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using static System.Net.Mime.MediaTypeNames;
namespace UGQDataAccess.Repository;
public class QuestContentRepository : BaseRepository<QuestContentEntity>
{
internal enum SpotlightSort
{
MostLiked,
MostBookmarked,
}
internal class SportlightQuery
{
internal SpotlightSort Sort { get; set; }
internal DateRange DateRange { get; set; }
internal List<QuestBoardItemResult> Items { get; set; } = new();
internal SportlightQuery(SpotlightSort sort, DateRange dateRange)
{
Sort = sort;
DateRange = dateRange;
}
}
private const string CollectionName = "QuestContent";
public QuestContentRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<QuestContentEntity> insert(QuestContentEntity entity)
{
await Collection.InsertOneAsync(entity);
return entity;
}
public async Task<List<QuestContentEntity>> getAll(string userGuid)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.UserGuid, userGuid);
var result = await Collection.Find(filter).ToListAsync();
return result;
}
public async Task<long> getAllCount()
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
var result = await Collection.Find(filter).CountDocumentsAsync();
return result;
}
public async Task<List<QuestContentEntity>> getUserQuests(string userGuid, IEnumerable<QuestContentState> states)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter = builder.Eq(x => x.UserGuid, userGuid) &
builder.In(x => x.State, states);
return await Collection.Find(filter).ToListAsync();
}
public async Task<SummaryQueryResult> getSummaries(int pageNumber, int pageSize, string userGuid, QuestContentState state, string? searchText)
{
var accountCollection = _mongoDatabase.GetCollection<AccountEntity>("Account");
var likeCollection = _mongoDatabase.GetCollection<LikeEntity>("Like");
var bookmarkCollection = _mongoDatabase.GetCollection<BookmarkEntity>("Bookmark");
var npcNameCollection = _mongoDatabase.GetCollection<NpcNameEntity>("NpcName");
pageNumber = pageNumber < 1 ? 1 : pageNumber;
var filter = await QuestContentQuery.summariesFilter(userGuid,
UgqSearchType.Title, searchText, state,
accountCollection, npcNameCollection);
var countFacet = AggregateFacet.Create("count",
new EmptyPipelineDefinition<SummaryItemResult>()
.Count()
);
var pagingFacet = AggregateFacet.Create("paging",
new EmptyPipelineDefinition<SummaryItemResult>()
.Skip((pageNumber - 1) * pageSize)
.Limit(pageSize)
);
var pipeline = QuestContentQuery.summariesPipeline<SummaryItemResult>(
likeCollection, bookmarkCollection,
filter,
QuestContentQuery.SummaryItemResultProjection)
.Facet(countFacet, pagingFacet);
var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()
?.FirstOrDefault()
?.Count;
var paging = aggregation.First()
.Facets.First(x => x.Name == "paging")
.Output<SummaryItemResult>();
int totalPages = 0;
if (count != null)
totalPages = (int)Math.Ceiling((double)count / pageSize);
var result = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
return new SummaryQueryResult
{
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = totalPages,
Items = paging.ToList(),
};
}
public async Task<AllSummaryQueryResult> getAllSummaries(int pageNumber, int pageSize, QuestContentState state, string? searchText)
{
var accountCollection = _mongoDatabase.GetCollection<AccountEntity>("Account");
var likeCollection = _mongoDatabase.GetCollection<LikeEntity>("Like");
var bookmarkCollection = _mongoDatabase.GetCollection<BookmarkEntity>("Bookmark");
var npcNameCollection = _mongoDatabase.GetCollection<NpcNameEntity>("NpcName");
pageNumber = pageNumber < 1 ? 1 : pageNumber;
var filter = await QuestContentQuery.summariesFilter(null,
UgqSearchType.Title, searchText, state,
accountCollection, npcNameCollection);
var countFacet = AggregateFacet.Create("count",
new EmptyPipelineDefinition<SummaryItemResult>()
.Count()
);
var pagingFacet = AggregateFacet.Create("paging",
new EmptyPipelineDefinition<SummaryItemResult>()
.Skip((pageNumber - 1) * pageSize)
.Limit(pageSize)
);
var pipeline = QuestContentQuery.summariesPipeline<SummaryItemResult>(
likeCollection, bookmarkCollection,
filter,
QuestContentQuery.SummaryItemResultProjection)
.Facet(countFacet, pagingFacet);
var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()
?.FirstOrDefault()
?.Count;
var paging = aggregation.First()
.Facets.First(x => x.Name == "paging")
.Output<SummaryItemResult>();
int totalPages = 0;
if (count != null)
totalPages = (int)Math.Ceiling((double)count / pageSize);
return new AllSummaryQueryResult
{
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = totalPages,
Items = paging.ToList(),
};
}
public async Task<QuestContentEntity?> get(string id)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.Id, id);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task<QuestContentEntity?> getByQuestId(long questId)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.QuestId, questId);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task<QuestContentEntity?> getByQuestIdRevision(long questId, long revision)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.QuestId, questId) & builder.Eq(x => x.Revision, revision);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task<QuestContentEntity?> updateContent(string id, UGQSaveQuestModel model, int? contentVersion)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.Id, id) &
builder.Eq(x => x.ContentVersion, contentVersion);
var State = QuestContentState.Editable;
foreach (var task in model.Tasks)
{
if (task.ActionId == 0)
{
State = QuestContentState.Uncomplate;
break;
}
}
var update = Builders<QuestContentEntity>.Update
.Set(x => x.BeaconId, model.BeaconId)
.Set(x => x.UgcBeaconGuid, model.UgcBeaconGuid)
.Set(x => x.UgcBeaconNickname, model.UgcBeaconNickname)
.Set(x => x.Title, model.Title)
.Set(x => x.Langs, model.Languages)
.Set(x => x.Savelanguage, model.Savelanguage)
.Set(x => x.Description, model.Description)
.Set(x => x.Cost, model.Cost)
.Set(x => x.Tasks, model.Tasks)
.Set(x => x.State, State)
.Inc(x => x.ContentVersion, 1)
.Set(x => x.UpdatedAt, DateTime.UtcNow);
var options = new FindOneAndUpdateOptions<QuestContentEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<QuestContentEntity?> updateTasks(string id, List<TaskEntity> tasks, int? contentVersion)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.Id, id) &
builder.Eq(x => x.ContentVersion, contentVersion);
var update = Builders<QuestContentEntity>.Update
.Set(x => x.Tasks, tasks)
.Inc(x => x.ContentVersion, 1)
.Set(x => x.UpdatedAt, DateTime.UtcNow);
var options = new FindOneAndUpdateOptions<QuestContentEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<QuestContentEntity?> updateImages(string id, string? titleImage, string? bannerImage)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.Id, id);
var updateBuilder = Builders<QuestContentEntity>.Update;
var update = updateBuilder.Set(x => x.UpdatedAt, DateTime.UtcNow);
if (titleImage != null)
update = updateBuilder.Combine(update, updateBuilder.Set(x => x.TitleImagePath, titleImage));
if (bannerImage != null)
update = updateBuilder.Combine(update, updateBuilder.Set(x => x.BannerImagePath, bannerImage));
var options = new FindOneAndUpdateOptions<QuestContentEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<QuestContentEntity?> updateState(string id, long questId, long revision,
IEnumerable<QuestContentState> before, QuestContentState after, UgqGradeType? gradeType = null)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.Id, id) &
builder.In(x => x.State, before);
// Test->Standby <20><> <20><> toNextRevision<6F><6E> true<75><65> <20>ؼ<EFBFBD> Live <20><> <20><> Revision + 1 ó<><C3B3> <20>Ѵ<EFBFBD>.
bool toNextRevision = before.Any(x => x == QuestContentState.Test) && (after == QuestContentState.Standby);
var updateBuilder = Builders<QuestContentEntity>.Update;
var update = updateBuilder
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.QuestId, questId)
.Set(x => x.Revision, revision)
.Set(x => x.State, after)
.Set(x => x.ToNextRevision, toNextRevision);
if (gradeType != null)
update = updateBuilder.Combine(update, updateBuilder.Set(x => x.GradeType, gradeType));
var options = new FindOneAndUpdateOptions<QuestContentEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<(ServerErrorCode, int)> incUploadCounter(string questContentId)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.Id, questContentId);
var update = Builders<QuestContentEntity>.Update
.Inc(x => x.UploadCounter, 1)
.Set(x => x.UpdatedAt, DateTime.UtcNow);
var options = new FindOneAndUpdateOptions<QuestContentEntity>()
{
ReturnDocument = ReturnDocument.After
};
var updated = await Collection.FindOneAndUpdateAsync(filter, update, options);
return (ServerErrorCode.Success, updated.UploadCounter);
}
public async Task<ServerErrorCode> deleteQuest(string userGuid, string questContentId)
{
var builder = Builders<QuestContentEntity>.Filter;
var filter = builder.Eq(x => x.IsDeleted, false);
filter &= builder.Eq(x => x.UserGuid, userGuid) & builder.Eq(x => x.Id, questContentId);
var update = Builders<QuestContentEntity>.Update
.Set(x => x.UpdatedAt, DateTime.UtcNow)
.Set(x => x.DeletedAt, DateTime.UtcNow)
.Set(x => x.IsDeleted, true);
var result = await Collection.UpdateOneAsync(filter, update);
if(result.ModifiedCount == 0)
return ServerErrorCode.UgqNullEntity;
return ServerErrorCode.Success;
}
public async Task<QuestBoardQueryResult> getQuestBoard(string? userGuid,
UgqQuestBoardRequest request, int pageSize)
{
var accountCollection = _mongoDatabase.GetCollection<AccountEntity>("Account");
var likeCollection = _mongoDatabase.GetCollection<LikeEntity>("Like");
var bookmarkCollection = _mongoDatabase.GetCollection<BookmarkEntity>("Bookmark");
var npcNameCollection = _mongoDatabase.GetCollection<NpcNameEntity>("NpcName");
request.PageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
var filter = await QuestContentQuery.questBoardFilter(userGuid, request.GradeType,
request.SearchType, request.SearchText, accountCollection, npcNameCollection);
var sort = QuestContentQuery.questBoardSort<QuestBoardItemResult>(request.SortType);
var countFacet = AggregateFacet.Create("count",
new EmptyPipelineDefinition<QuestBoardItemResult>()
.Count()
);
var pagingFacet = AggregateFacet.Create("paging",
new EmptyPipelineDefinition<QuestBoardItemResult>()
.Sort(sort)
.Skip((request.PageNumber - 1) * pageSize)
.Limit(pageSize)
);
var pipeline = QuestContentQuery.questBoardPipeline<QuestBoardItemResult>(
likeCollection, bookmarkCollection, filter,
QuestContentQuery.QuestBoardItemResultProjection)
.Facet(countFacet, pagingFacet);
var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()
?.FirstOrDefault()
?.Count;
var paging = aggregation.First()
.Facets.First(x => x.Name == "paging")
.Output<QuestBoardItemResult>();
int totalPages = 0;
if(count != null)
totalPages = (int)Math.Ceiling((double)count / pageSize);
return new QuestBoardQueryResult
{
PageNumber = request.PageNumber,
PageSize = pageSize,
TotalPages = totalPages,
Items = paging.ToList(),
};
}
public async Task<QuestBoardDetailItemResult?> getQuestBoardDetail(string userGuid, long questId, long revision)
{
var likeCollection = _mongoDatabase.GetCollection<LikeEntity>("Like");
var bookmarkCollection = _mongoDatabase.GetCollection<BookmarkEntity>("Bookmark");
var filterBuilder = Builders<QuestContentEntity>.Filter;
var filter = filterBuilder.Eq(x => x.IsDeleted, false);
filter &= filterBuilder.Eq(x => x.QuestId, questId);
filter &= filterBuilder.Eq(x => x.Revision, revision);
filter &= filterBuilder.Eq(x => x.State, QuestContentState.Live);
Expression<Func<QuestContentQuery.QuestBoardQueryJoin, QuestBoardDetailItemResult>> questBoardDetailItemProjection =
x => new QuestBoardDetailItemResult
{
QuestId = x.QuestId,
Revision = x.Revision,
Author = x.Author,
Title = x.Title,
Languages = x.Langs,
Description = x.Description,
Cost = x.Cost,
GradeType = x.GradeType,
TitleImagePath = x.TitleImagePath,
BannerImagePath = x.BannerImagePath,
UpdatedAt = x.UpdatedAt,
LikeCount = x.LikeCount,
BookmarkCount = x.BookmarkCount,
QuestAcceptedCount = x.QuestAcceptedCount,
QuestCompletedCount = x.QuestCompletedCount,
BeaconId = x.BeaconId,
UgcBeaconGuid = x.UgcBeaconGuid,
UgcBeaconNickname = x.UgcBeaconNickname,
Liked = x.Likes.Any(x => x.UserGuid == userGuid),
Bookmarked = x.Bookmarks.Any(x => x.UserGuid == userGuid),
};
var pipeline = QuestContentQuery.questBoardDetailPipeline<QuestBoardDetailItemResult>(
likeCollection, bookmarkCollection,
filter,
questBoardDetailItemProjection);
return await (await Collection.AggregateAsync(pipeline)).FirstOrDefaultAsync();
}
public async Task<QuestBoardQueryResult> getBookmarkQuests(string userGuid, UgqQuestBoardRequest request, int pageSize)
{
var likeCollection = _mongoDatabase.GetCollection<LikeEntity>("Like");
var bookmarkCollection = _mongoDatabase.GetCollection<BookmarkEntity>("Bookmark");
var npcNameCollection = _mongoDatabase.GetCollection<NpcNameEntity>("NpcName");
request.PageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
var filter = await QuestContentQuery.questBoardBookmarkFilter(userGuid,
request.GradeType, request.SearchType, request.SearchText,
npcNameCollection, bookmarkCollection);
var sort = QuestContentQuery.questBoardSort<QuestBoardItemResult>(request.SortType);
var countFacet = AggregateFacet.Create("count",
new EmptyPipelineDefinition<QuestBoardItemResult>()
.Count()
);
var pagingFacet = AggregateFacet.Create("paging",
new EmptyPipelineDefinition<QuestBoardItemResult>()
.Sort(sort)
.Skip((request.PageNumber - 1) * pageSize)
.Limit(pageSize)
);
var pipeline = QuestContentQuery.questBoardPipeline<QuestBoardItemResult>(
likeCollection, bookmarkCollection, filter,
QuestContentQuery.QuestBoardItemResultProjection)
.Facet(countFacet, pagingFacet);
var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()
?.FirstOrDefault()
?.Count;
var paging = aggregation.First()
.Facets.First(x => x.Name == "paging")
.Output<QuestBoardItemResult>();
int totalPages = 0;
if (count != null)
totalPages = (int)Math.Ceiling((double)count / pageSize);
return new QuestBoardQueryResult
{
PageNumber = request.PageNumber,
PageSize = pageSize,
TotalPages = totalPages,
Items = paging.ToList(),
};
}
public async Task<QuestBoardSportlightQueryResult> getQuestBoardSpotlight()
{
var accountCollection = _mongoDatabase.GetCollection<AccountEntity>("Account");
var likeCollection = _mongoDatabase.GetCollection<LikeEntity>("Like");
var bookmarkCollection = _mongoDatabase.GetCollection<BookmarkEntity>("Bookmark");
SportlightQuery[] queries = [
new SportlightQuery(SpotlightSort.MostLiked, DataRangeHelper.Yesterday(DateTime.Now)),
new SportlightQuery(SpotlightSort.MostBookmarked, DataRangeHelper.Yesterday(DateTime.Now)),
new SportlightQuery(SpotlightSort.MostLiked, DataRangeHelper.LastWeek(DateTime.Now)),
new SportlightQuery(SpotlightSort.MostBookmarked, DataRangeHelper.LastWeek(DateTime.Now)),
new SportlightQuery(SpotlightSort.MostLiked, DataRangeHelper.LastMonth(DateTime.Now)),
new SportlightQuery(SpotlightSort.MostBookmarked, DataRangeHelper.LastMonth(DateTime.Now)),
];
var filterBuilder = Builders<QuestContentEntity>.Filter;
var filter = filterBuilder.Eq(x => x.IsDeleted, false);
foreach (var (value, i) in queries.Select((value, i) => (value, i)))
{
int limit = 1;
switch(value.Sort)
{
case SpotlightSort.MostLiked:
{
var sort = Builders<QuestBoardItemResult>.Sort.Descending("LikeCount").Descending("CreatedAt");
var pipeline = QuestContentQuery.questBoardPipeline<QuestBoardItemResult>(
likeCollection, bookmarkCollection, filter,
QuestContentQuery.QuestBoardItemResultProjection)
.Sort(sort)
.Limit(limit);
value.Items = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
}
break;
case SpotlightSort.MostBookmarked:
{
var sort = Builders<QuestBoardItemResult>.Sort.Descending("BookmarkCount").Descending("CreatedAt");
var pipeline = QuestContentQuery.questBoardPipeline<QuestBoardItemResult>(
likeCollection, bookmarkCollection, filter,
QuestContentQuery.QuestBoardItemResultProjection)
.Sort(sort)
.Limit(limit);
value.Items = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
}
break;
}
}
/*
// <20>ߺ<EFBFBD> <20><><EFBFBD><EFBFBD>
foreach (var (value, i) in queries.Select((value, i) => (value, i)))
{
if (value.Items.Count == 0)
continue;
long questId = value.Items[0].QuestId;
long revision = value.Items[0].Revision;
for (int s = i + 1; s < queries.Length; s++)
{
queries[s].Items.RemoveAll(x => x.QuestId == questId && x.Revision == revision);
}
}
*/
return new QuestBoardSportlightQueryResult
{
MostLiked = new()
{
Today = queries[0].Items.FirstOrDefault(),
ThisWeek = queries[2].Items.FirstOrDefault(),
ThisMonth = queries[4].Items.FirstOrDefault(),
},
MostBookmarked = new()
{
Today = queries[1].Items.FirstOrDefault(),
ThisWeek = queries[3].Items.FirstOrDefault(),
ThisMonth = queries[5].Items.FirstOrDefault(),
},
};
}
public async Task<QuestProfitStatsQueryResult> getQuestProfitStats(string userGuid, int pageNumber, int pageSize)
{
var accountCollection = _mongoDatabase.GetCollection<AccountEntity>("Account");
var likeCollection = _mongoDatabase.GetCollection<LikeEntity>("Like");
var bookmarkCollection = _mongoDatabase.GetCollection<BookmarkEntity>("Bookmark");
var npcNameCollection = _mongoDatabase.GetCollection<NpcNameEntity>("NpcName");
var questAcceptedCollection = _mongoDatabase.GetCollection<QuestAcceptedEntity>("QuestAccepted");
var questCompletedCollection = _mongoDatabase.GetCollection<QuestCompletedEntity>("QuestCompleted");
var questAbortedCollection = _mongoDatabase.GetCollection<QuestAbortedEntity>("QuestAborted");
var creatorPointHistoryCollection = _mongoDatabase.GetCollection<CreatorPointHistoryEntity>("CreatorPointHistory");
pageNumber = pageNumber < 1 ? 1 : pageNumber;
var filterBuilder = Builders<QuestContentEntity>.Filter;
var filter = filterBuilder.Eq(x => x.IsDeleted, false);
filter &= filterBuilder.Eq(x => x.UserGuid, userGuid);
var countFacet = AggregateFacet.Create("count",
new EmptyPipelineDefinition<QuestProfitStatsItemResult>()
.Count()
);
var pagingFacet = AggregateFacet.Create("paging",
new EmptyPipelineDefinition<QuestProfitStatsItemResult>()
.Skip((pageNumber - 1) * pageSize)
.Limit(pageSize)
);
var pipeline = QuestContentQuery.questProfitStatsPipeline<QuestProfitStatsItemResult>(
filter,
QuestContentQuery.QuestProfitStatsResultProjection)
.Facet(countFacet, pagingFacet);
var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()
?.FirstOrDefault()
?.Count;
var paging = aggregation.First()
.Facets.First(x => x.Name == "paging")
.Output<QuestProfitStatsItemResult>();
int totalPages = 0;
if (count != null)
totalPages = (int)Math.Ceiling((double)count / pageSize);
return new QuestProfitStatsQueryResult
{
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = totalPages,
Items = paging.ToList(),
};
}
}

View File

@@ -0,0 +1,62 @@
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
namespace UGQDataAccess.Repository;
public class QuestDialogRepository : BaseRepository<QuestDialogEntity>
{
private const string CollectionName = "QuestDialog";
public QuestDialogRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<(ServerErrorCode, QuestDialogEntity)> add(QuestDialogEntity entity)
{
await Collection.InsertOneAsync(entity);
return (ServerErrorCode.Success, entity);
}
public async Task<QuestDialogEntity?> get(string id)
{
var filter = Builders<QuestDialogEntity>.Filter
.Eq(x => x.Id, id);
var entity = await Collection.Find(filter).FirstOrDefaultAsync();
return entity;
}
public async Task<List<QuestDialogEntity>> get(IEnumerable<string> ids)
{
var filter = Builders<QuestDialogEntity>.Filter
.In(x => x.Id, ids);
return await Collection.Find(filter).ToListAsync();
}
public async Task<(ServerErrorCode, QuestDialogEntity?)> updateSequences(string dialogId, List<DialogSequenceEntity> sequences)
{
var filter = Builders<QuestDialogEntity>.Filter
.Eq(x => x.Id, dialogId);
var update = Builders<QuestDialogEntity>.Update
.Set(x => x.Sequences, sequences)
.Set(x => x.UpdatedAt, DateTime.UtcNow);
var options = new FindOneAndUpdateOptions<QuestDialogEntity>
{
ReturnDocument = ReturnDocument.After,
};
var updated = await Collection.FindOneAndUpdateAsync(filter, update, options);
return (ServerErrorCode.Success, updated);
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
namespace UGQDataAccess.Repository
{
public class QuestIdSequenceRepository : BaseRepository<QuestIdSequenceEntity>
{
private const string CollectionName = "QuestIdSequence";
public QuestIdSequenceRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task init()
{
var found = await Collection.FindAsync(x => x.Id == "QuestId");
if(found.FirstOrDefault() == null)
{
QuestIdSequenceEntity entity = new ()
{
Id = "QuestId",
Sequence = 0,
UpdatedAt = DateTime.UtcNow,
CreatedAt = DateTime.UtcNow,
};
await Collection.InsertOneAsync(entity);
}
}
public async Task<long> getNextSequence()
{
var filter = Builders<QuestIdSequenceEntity>.Filter.Eq(x => x.Id, "QuestId");
var update = Builders<QuestIdSequenceEntity>.Update
.Inc(x => x.Sequence, 1)
.Set(x => x.UpdatedAt, DateTime.UtcNow);
var options = new FindOneAndUpdateOptions<QuestIdSequenceEntity>()
{
ReturnDocument = ReturnDocument.After
};
var result = await Collection.FindOneAndUpdateAsync(filter, update, options);
return result.Sequence;
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using SharpCompress.Common;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
using ServerCommon.UGQ;
using System.Runtime.CompilerServices;
namespace UGQDataAccess.Repository
{
public class ReportRepository : BaseRepository<ReportEntity>
{
private const string CollectionName = "Report";
public ReportRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<ReportEntity?> get(long questId, long revision, string userGuid)
{
var builder = Builders<ReportEntity>.Filter;
var filter = builder.Eq(x => x.QuestId, questId) &
builder.Eq(x => x.Revision, revision) &
builder.Eq(x => x.UserGuid, userGuid);
ReportEntity? entity = await (await Collection.FindAsync(filter)).FirstOrDefaultAsync();
return entity;
}
public async Task<ServerErrorCode> insert(long questId, long revision, string userGuid, string contents)
{
var entity = new ReportEntity
{
QuestId = questId,
Revision = revision,
UserGuid = userGuid,
Contents = contents,
CreatedAt = DateTime.UtcNow,
};
await Collection.InsertOneAsync(entity);
return ServerErrorCode.Success;
}
}
}

View File

@@ -0,0 +1,230 @@
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using UGQDataAccess.Repository.Models;
using UGQDataAccess.Settings;
using UGQDatabase.Models;
public enum AccountGradeSortType
{
UpdatedAtAsc,
UpdatedAtDesc,
GradeAsc,
GradeDesc,
ReservedAtAsc,
ReservedDesc,
}
public enum AccountGradeProcessType
{
All,
Processing,
Completed,
}
namespace UGQDataAccess.Repository
{
public class ReserveAccountGradeRepository : BaseRepository<ReserveAccountGradeEntity>
{
private const string CollectionName = "ReserveAccountGrade";
public ReserveAccountGradeRepository(IMongoClient mongoClient, IOptions<UGQDatabaseSettings> settings) :
base(mongoClient, settings.Value.DatabaseName, CollectionName)
{
}
public async Task<ReserveAccountGradeEntity?> get(string userGuid)
{
var builder = Builders<ReserveAccountGradeEntity>.Filter;
var filter = builder.Eq(x => x.IsCompleted, false);
filter &= builder.Eq(x => x.UserGuid, userGuid);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
public async Task<List<ReserveAccountGradeEntity>> get(string userGuid, AccountGradeProcessType processType)
{
var builder = Builders<ReserveAccountGradeEntity>.Filter;
var filter = builder.Eq(x => x.UserGuid, userGuid);
switch (processType)
{
case AccountGradeProcessType.Processing:
filter &= builder.Eq(x => x.IsCompleted, false);
break;
case AccountGradeProcessType.Completed:
filter &= builder.Eq(x => x.IsCompleted, true);
break;
case AccountGradeProcessType.All:
default:
break;
}
return await Collection.Find(filter).ToListAsync();
}
public async Task<long> getAllCount()
{
var builder = Builders<ReserveAccountGradeEntity>.Filter;
var filter = builder.Empty;
var result = await Collection.Find(filter).CountDocumentsAsync();
return result;
}
public async Task<ReserveAccountGradeEntity> reserveAccountGrade(string userGuid, UgqGradeType beforeGrade, UgqGradeType reserveGrade, DateTime reserveTime)
{
var entity = new ReserveAccountGradeEntity
{
UserGuid = userGuid,
BeforeGradeType = beforeGrade,
ReserveGradeType = reserveGrade,
ReserveTime = reserveTime,
UpdatedAt = DateTime.UtcNow,
IsCompleted = false,
};
await Collection.InsertOneAsync(entity);
return entity;
}
public async Task<ReserveAccountGradeEntity> modifyReserveAccountGrade(string reserveId, UgqGradeType reserveGrade, DateTime reserveTime)
{
var builder = Builders<ReserveAccountGradeEntity>.Filter;
var filter = builder.Eq(x => x.IsCompleted, false);
filter &= builder.Eq(x => x.Id, reserveId);
var update = Builders<ReserveAccountGradeEntity>.Update
.Set(x => x.ReserveGradeType, reserveGrade)
.Set(x => x.ReserveTime, reserveTime)
.Set(x => x.UpdatedAt, DateTime.UtcNow);
var options = new FindOneAndUpdateOptions<ReserveAccountGradeEntity>
{
ReturnDocument = ReturnDocument.After,
};
return await Collection.FindOneAndUpdateAsync(filter, update, options);
}
public async Task<List<ReserveAccountGradeEntity>> completeReserveAccountGrade()
{
var builder = Builders<ReserveAccountGradeEntity>.Filter;
var filter = builder.Eq(x => x.IsCompleted, false);
filter &= builder.Lte(x => x.ReserveTime, DateTime.UtcNow);
var update = Builders<ReserveAccountGradeEntity>.Update
.Set(x => x.IsCompleted, true)
.Set(x => x.UpdatedAt, DateTime.UtcNow);
var result = await Collection.Find(filter).ToListAsync();
await Collection.UpdateManyAsync(filter, update);
return result;
}
public async Task<ServerErrorCode> deleteReserveAccountGrade(string reserveId)
{
var builder = Builders<ReserveAccountGradeEntity>.Filter;
var filter = builder.Eq(x => x.IsCompleted, false);
filter &= builder.Eq(x => x.Id, reserveId);
var result = await Collection.DeleteOneAsync(filter);
if (result.DeletedCount == 0)
return ServerErrorCode.UgqNullEntity;
return ServerErrorCode.Success;
}
public async Task<AllReserveAccountGradeResult> getAccounts(int pageNumber, int pageSize, AccountGradeSortType sortType, AccountGradeProcessType processType, string userGuid)
{
pageNumber = pageNumber < 1 ? 1 : pageNumber;
var builder = Builders<ReserveAccountGradeEntity>.Filter;
var filter = Builders<ReserveAccountGradeEntity>.Filter.Empty;
switch (processType)
{
case AccountGradeProcessType.Processing:
filter = builder.Eq(x => x.IsCompleted, false);
break;
case AccountGradeProcessType.Completed:
filter = builder.Eq(x => x.IsCompleted, true);
break;
case AccountGradeProcessType.All:
default:
break;
}
if(userGuid != string.Empty)
filter &= builder.Eq(x => x.UserGuid, userGuid);
var sortBuilder = Builders<ReserveAccountGradeItemResult>.Sort;
SortDefinition<ReserveAccountGradeItemResult> sort;
switch (sortType)
{
case AccountGradeSortType.UpdatedAtAsc:
sort = Builders<ReserveAccountGradeItemResult>.Sort.Ascending("UpdatedAt");
break;
case AccountGradeSortType.UpdatedAtDesc:
sort = Builders<ReserveAccountGradeItemResult>.Sort.Descending("UpdatedAt");
break;
case AccountGradeSortType.GradeAsc:
sort = Builders<ReserveAccountGradeItemResult>.Sort.Ascending("CurrentGradeType");
break;
case AccountGradeSortType.GradeDesc:
sort = Builders<ReserveAccountGradeItemResult>.Sort.Descending("CurrentGradeType");
break;
case AccountGradeSortType.ReservedAtAsc:
sort = Builders<ReserveAccountGradeItemResult>.Sort.Ascending("ReserveTime");
break;
case AccountGradeSortType.ReservedDesc:
sort = Builders<ReserveAccountGradeItemResult>.Sort.Descending("ReserveTime");
break;
default:
sort = Builders<ReserveAccountGradeItemResult>.Sort.Descending("UpdatedAt");
break;
}
var countFacet = AggregateFacet.Create("count",
new EmptyPipelineDefinition<ReserveAccountGradeItemResult>()
.Count()
);
var pagingFacet = AggregateFacet.Create("paging",
new EmptyPipelineDefinition<ReserveAccountGradeItemResult>()
.Sort(sort)
.Skip((pageNumber - 1) * pageSize)
.Limit(pageSize)
);
var pipeline = ReserveAccountGradeQuery.allReserveAccountGradePipeline<ReserveAccountGradeItemResult>(
filter,
ReserveAccountGradeQuery.ReserveAccountGradeItemResultProjection)
.Facet(countFacet, pagingFacet);
var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()
?.FirstOrDefault()
?.Count;
var paging = aggregation.First()
.Facets.First(x => x.Name == "paging")
.Output<ReserveAccountGradeItemResult>();
int totalPages = 0;
if (count != null)
totalPages = (int)Math.Ceiling((double)count / pageSize);
var result = await (await Collection.AggregateAsync(pipeline)).ToListAsync();
return new AllReserveAccountGradeResult
{
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = totalPages,
Items = paging.ToList(),
};
}
}
}

View File

@@ -0,0 +1,324 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UGQDataAccess.Repository;
using UGQDataAccess.Repository.Models;
using UGQDatabase.Models;
using ServerCore; using ServerBase;
using ServerCommon;
using UGQDataAccess.Logs;
using ServerCommon.BusinessLogDomain;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace UGQDataAccess.Service;
public class AccountService
{
readonly QuestIdSequenceRepository _questIdSequenceRepository;
readonly QuestContentRepository _questContentRepository;
readonly QuestDialogRepository _questDialogRepository;
readonly AccountRepository _accountRepository;
readonly GameQuestDataRepository _gameQuestDataRepository;
readonly CreatorPointHistoryRepository _creatorPointHistoryRepository;
readonly NpcNameRepository _npcNameRepository;
readonly UgqMetaGenerateService _ugqMetaGenerateService;
readonly ReserveAccountGradeRepository _reserveAccountGradeRepository;
public AccountService(QuestIdSequenceRepository questIdSequenceRepository,
QuestContentRepository questContentRepository,
QuestDialogRepository questDialogRepository,
AccountRepository accountRepository,
GameQuestDataRepository gameQuestDataRepository,
CreatorPointHistoryRepository creatorPointHistoryRepository,
NpcNameRepository npcNameRepository,
UgqMetaGenerateService ugqMetaGenerateService,
ReserveAccountGradeRepository reserveAccountGradeRepository)
{
_questIdSequenceRepository = questIdSequenceRepository;
_questContentRepository = questContentRepository;
_questDialogRepository = questDialogRepository;
_accountRepository = accountRepository;
_gameQuestDataRepository = gameQuestDataRepository;
_creatorPointHistoryRepository = creatorPointHistoryRepository;
_npcNameRepository = npcNameRepository;
_ugqMetaGenerateService = ugqMetaGenerateService;
_reserveAccountGradeRepository = reserveAccountGradeRepository;
}
public async Task<AllAccountQueryResult> getAccounts(int pageNumber, int pageSize, string? searchText, AccountSortType sortType)
{
return await _accountRepository.getAccounts(pageNumber, pageSize, searchText, sortType);
}
public async Task<long> getAllAccountCount()
{
return await _accountRepository.getAllCount();
}
public async Task<AccountEntity?> getAccount(string userGuid)
{
return await _accountRepository.get(userGuid);
}
public async Task<AccountEntity?> getAccountByAccountId(string accountId)
{
return await _accountRepository.getByAccountId(accountId);
}
public async Task<AccountEntity> getOrInsertAccount(string userGuid, string nickname, string accountId)
{
return await _accountRepository.getOrInsert(userGuid, nickname, accountId);
}
public async Task<AccountEntity?> saveRefreshToken(string userGuid, string refreshToken, DateTime? refreshTokenExpryTime)
{
return await _accountRepository.saveRefreshToken(userGuid, refreshToken, refreshTokenExpryTime);
}
public async Task<AccountEntity?> deleteRefreshToken(string userGuid)
{
return await _accountRepository.deleteRefreshToken(userGuid);
}
public async Task<(ServerErrorCode, AccountEntity?)> addSlotCount(string userGuid, int count, int? slotCountVersion)
{
if(count < 1)
return (ServerErrorCode.UgqServerException, null);
// 슬롯 증가는 재시도 없이 변경 못했으면 실패 리턴
var updated = await _accountRepository.addSlotCount(userGuid, slotCountVersion, count);
if (updated == null)
return (ServerErrorCode.UgqExceedTransactionRetry, null);
return (ServerErrorCode.Success, updated);
}
public async Task<(ServerErrorCode, AccountEntity?)> modifySlotCount(string userGuid, int count, string adminUsername)
{
int retryCount = 0;
while (retryCount < UGQConstants.MAX_UPDATE_RETRY_COUNT)
{
var entity = await _accountRepository.get(userGuid);
if (entity == null)
return (ServerErrorCode.UgqNullEntity, null);
int haveCount = ServerCommon.MetaHelper.GameConfigMeta.UGQDefaultSlot + entity.AdditionalSlotCount + count;
if (haveCount < 0 || haveCount > ServerCommon.MetaHelper.GameConfigMeta.UGQMaximumSlot)
return (ServerErrorCode.UgqSlotLimit, null);
var updated = await _accountRepository.addSlotCount(userGuid, entity.SlotCountVersion, count);
if (updated == null)
{
retryCount++;
Log.getLogger().warn($"slotCount version missmatch. retryCount: {retryCount}");
continue;
}
List<ILogInvoker> business_logs = [
new UgqApiAddSlotBusinessLog(updated.AdditionalSlotCount, adminUsername),
];
var log_action = new LogActionEx(LogActionType.UgqApiAddSlot);
UgqApiBusinessLogger.collectLogs(log_action, updated.UserGuid, business_logs);
return (ServerErrorCode.Success, updated);
}
return (ServerErrorCode.UgqExceedTransactionRetry, null);
}
public async Task<ServerErrorCode> addCreatorPoint(string userGuid, long questId, long revision, double amount, UgqCreatorPointReason reason)
{
int retryCount = 0;
while (retryCount < UGQConstants.MAX_UPDATE_RETRY_COUNT)
{
var entity = await _accountRepository.getOrInsertForGameUser(userGuid);
if (entity == null)
return ServerErrorCode.UgqNullEntity;
var updated = await _accountRepository.incCreatorPoint(userGuid, entity.CreatorPointVersion, amount);
if (updated == null)
{
retryCount++;
Log.getLogger().warn($"creatorPoint version missmatch. retryCount: {retryCount}");
continue;
}
CreatorPointHistoryEntity historyEntity = new CreatorPointHistoryEntity
{
UserGuid = userGuid,
QuestId = questId,
Revision = revision,
Kind = CreatorPointHistoryKind.QuestProfit,
DetailReason = "Development",
Amount = amount,
TotalAmount = updated.CreatorPoint,
CreatedAt = DateTime.UtcNow
};
await _creatorPointHistoryRepository.insert(historyEntity);
List<ILogInvoker> business_logs = [
new UgqApiCreatorPointBusinessLog(amount, questId, revision, "", reason, ""),
];
var log_action = new LogActionEx(LogActionType.UgqApiCreatorPoint);
UgqApiBusinessLogger.collectLogs(log_action, userGuid, business_logs);
return ServerErrorCode.Success;
}
return ServerErrorCode.UgqExceedTransactionRetry;
}
public async Task<ServerErrorCode> decCreatorPoint(string userGuid, int amount, UgqCreatorPointReason reason)
{
int retryCount = 0;
while (retryCount < UGQConstants.MAX_UPDATE_RETRY_COUNT)
{
var entity = await _accountRepository.get(userGuid);
if (entity == null)
return ServerErrorCode.UgqNullEntity;
// 포인트가 0보다 작아지지 않게 처리해야 한다
if (entity.CreatorPoint < amount)
return ServerErrorCode.UgqNotEnoughCreatorPoint;
int creatorPointVersion = entity.CreatorPointVersion ?? 1;
var updated = await _accountRepository.incCreatorPoint(userGuid, creatorPointVersion, -amount);
if (updated == null)
{
retryCount++;
Log.getLogger().warn($"creatorPoint version missmatch. retryCount: {retryCount}");
continue;
}
CreatorPointHistoryEntity historyEntity = new CreatorPointHistoryEntity
{
UserGuid = userGuid,
QuestId = 0,
Revision = 0,
Kind = CreatorPointHistoryKind.SettleUp,
DetailReason = "Development",
Amount = -amount,
TotalAmount = updated.CreatorPoint,
CreatedAt = DateTime.UtcNow
};
await _creatorPointHistoryRepository.insert(historyEntity);
List<ILogInvoker> business_logs = [
new UgqApiCreatorPointBusinessLog(-amount, 0, 0, "", reason, ""),
];
var log_action = new LogActionEx(LogActionType.UgqApiCreatorPoint);
UgqApiBusinessLogger.collectLogs(log_action, userGuid, business_logs);
return ServerErrorCode.Success;
}
return ServerErrorCode.UgqExceedTransactionRetry;
}
public async Task<AccountEntity?> modifyAccountGrade(string userGuid, UgqGradeType grade)
{
return await _accountRepository.modifyAccountGrade(userGuid, grade);
}
public async Task<AccountEntity?> promoteAccountGrade(string userGuid, UgqGradeType before, UgqGradeType after)
{
return await _accountRepository.promoteAccountGrade(userGuid, before, after);
}
public async Task<(ServerErrorCode, AccountEntity?)> modifyCreatorPoint(string userGuid, int amount, string reason, string adminUsername)
{
int retryCount = 0;
while (retryCount < UGQConstants.MAX_UPDATE_RETRY_COUNT)
{
var entity = await _accountRepository.get(userGuid);
if (entity == null)
return (ServerErrorCode.UgqNullEntity, null);
// 포인트가 0보다 작아지지 않게 처리해야 한다
if (amount < 0 && entity.CreatorPoint < -amount)
return (ServerErrorCode.UgqNotEnoughCreatorPoint, null);
int? creatorPointVersion = entity.CreatorPointVersion;
var updated = await _accountRepository.incCreatorPoint(userGuid, creatorPointVersion, amount);
if (updated == null)
{
retryCount++;
Log.getLogger().warn($"creatorPoint version missmatch. retryCount: {retryCount}");
continue;
}
CreatorPointHistoryEntity historyEntity = new CreatorPointHistoryEntity
{
UserGuid = userGuid,
QuestId = 0,
Revision = 0,
Kind = CreatorPointHistoryKind.Admin,
DetailReason = reason,
Amount = amount,
TotalAmount = updated.CreatorPoint,
CreatedAt = DateTime.UtcNow
};
await _creatorPointHistoryRepository.insert(historyEntity);
List<ILogInvoker> business_logs = [
new UgqApiCreatorPointBusinessLog(amount, 0, 0, adminUsername, UgqCreatorPointReason.Admin, reason),
];
var log_action = new LogActionEx(LogActionType.UgqApiCreatorPoint);
UgqApiBusinessLogger.collectLogs(log_action, userGuid, business_logs);
return (ServerErrorCode.Success, updated);
}
return (ServerErrorCode.UgqExceedTransactionRetry, null);
}
public async Task<AllReserveAccountGradeResult> getReserveAccountGrades(int pageNumber, int pageSize, AccountGradeSortType sortType, AccountGradeProcessType processType, string userGuid)
{
return await _reserveAccountGradeRepository.getAccounts(pageNumber, pageSize, sortType, processType, userGuid);
}
public async Task<long> getAllReserveAccountGradeCount()
{
return await _reserveAccountGradeRepository.getAllCount();
}
public async Task<ReserveAccountGradeEntity?> getReserveAccountGrade(string userGuid)
{
return await _reserveAccountGradeRepository.get(userGuid);
}
public async Task<List<ReserveAccountGradeEntity>> getReserveAccountGrade(string userGuid, AccountGradeProcessType processType)
{
return await _reserveAccountGradeRepository.get(userGuid, processType);
}
public async Task<ReserveAccountGradeEntity> reserveAccountGrade(string userGuid, UgqGradeType beforeGrade, UgqGradeType grade, DateTime reserveTime)
{
return await _reserveAccountGradeRepository.reserveAccountGrade(userGuid, beforeGrade, grade, reserveTime);
}
public async Task<ReserveAccountGradeEntity> modifyReserveAccountGrade(string reserveId, UgqGradeType grade, DateTime reserveTime)
{
return await _reserveAccountGradeRepository.modifyReserveAccountGrade(reserveId, grade, reserveTime);
}
public async Task<List<ReserveAccountGradeEntity>> completeReserveAccountGrade()
{
return await _reserveAccountGradeRepository.completeReserveAccountGrade();
}
public async Task<ServerErrorCode> deleteReserveAccountGrade(string reserveId)
{
return await _reserveAccountGradeRepository.deleteReserveAccountGrade(reserveId);
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UGQDataAccess.Repository.Models;
using UGQDataAccess.Repository;
using UGQDatabase.Models;
using ServerCommon;
using StackExchange.Redis;
namespace UGQDataAccess.Service
{
public class AdminService
{
readonly AdminAccountRepository _adminAccountRepository;
public AdminService(AdminAccountRepository adminAccountRepository)
{
_adminAccountRepository = adminAccountRepository;
}
public async Task<AdminAccountEntity?> get(string username)
{
return await _adminAccountRepository.get(username);
}
public async Task<AdminAccountEntity?> signup(string username, string password, UGQAccountRole role)
{
var passwordHash = BCrypt.Net.BCrypt.HashPassword(password);
return await _adminAccountRepository.signup(username, passwordHash, role);
}
public async Task<AdminAccountEntity?> login(string username, string password)
{
var entity = await _adminAccountRepository.get(username);
if (entity == null)
return null;
if (BCrypt.Net.BCrypt.Verify(password, entity.Password) == false)
return null;
return entity;
}
public async Task<AdminAccountEntity?> changePassword(string username, string newPassword)
{
var entity = await _adminAccountRepository.get(username);
if (entity == null)
return null;
var passwordHash = BCrypt.Net.BCrypt.HashPassword(newPassword);
return await _adminAccountRepository.changePassword(username, passwordHash);
}
public async Task<AdminAccountEntity?> saveRefreshToken(string id, string refreshToken, DateTime? refreshTokenExpryTime)
{
return await _adminAccountRepository.saveRefreshToken(id, refreshToken, refreshTokenExpryTime);
}
public async Task<AdminAccountEntity?> deleteRefreshToken(string userGuid)
{
return await _adminAccountRepository.deleteRefreshToken(userGuid);
}
}
}

View File

@@ -0,0 +1,313 @@
using MongoDB.Driver;
using UGQDatabase.Models;
using UGQDataAccess.Repository;
using UGQDataAccess.Repository.Models;
using ServerCommon.UGQ;
using ServerCommon.UGQ.Models;
using System.Text.Json;
using Amazon.DynamoDBv2.Model;
using ServerCommon.BusinessLogDomain;
using ServerCommon;
using MetaAssets;
namespace UGQDataAccess.Service;
public class InGameService
{
readonly QuestContentRepository _questContentRepository;
readonly QuestDialogRepository _questDialogRepository;
readonly LikeRepository _likeRepository;
readonly BookmarkRepository _bookmarkRepository;
readonly NpcNameRepository _npcNameRepository;
readonly GameQuestDataRepository _gameQuestDataRepository;
readonly ReportRepository _reportRepository;
readonly AccountRepository _accountRepository;
readonly QuestAcceptedRepository _questAccceptedRepository;
readonly QuestCompletedRepository _questCompletedRepository;
readonly QuestAbortedRepository _questAbortedRepository;
readonly CreatorPointHistoryRepository _creatorPointHistoryRepository;
readonly AccountService _accountService;
public InGameService(
AccountService accountService,
QuestContentRepository questContentRepository,
QuestDialogRepository questDialogRepository,
LikeRepository likeRepository,
BookmarkRepository bookmarkRepository,
NpcNameRepository npcNameRepository,
GameQuestDataRepository gameQuestDataRepository,
ReportRepository reportRepository,
AccountRepository accountRepository,
QuestAcceptedRepository accceptedRepository,
QuestCompletedRepository completedRepository,
QuestAbortedRepository questAbortedRepository,
CreatorPointHistoryRepository creatorPointHistoryRepository)
{
_accountService = accountService;
_questContentRepository = questContentRepository;
_questDialogRepository = questDialogRepository;
_likeRepository = likeRepository;
_bookmarkRepository = bookmarkRepository;
_npcNameRepository = npcNameRepository;
_gameQuestDataRepository = gameQuestDataRepository;
_reportRepository = reportRepository;
_accountRepository = accountRepository;
_questAccceptedRepository = accceptedRepository;
_questCompletedRepository = completedRepository;
_questAbortedRepository = questAbortedRepository;
_creatorPointHistoryRepository = creatorPointHistoryRepository;
}
public async Task<(ServerErrorCode, AccountEntity?)> getAccount(string userGuid)
{
AccountEntity? entity = await _accountRepository.get(userGuid);
if (entity == null)
return (ServerErrorCode.UgqNullEntity, null);
return (ServerErrorCode.Success, entity);
}
public async Task<List<QuestContentEntity>> getUserQuests(string userGuid, IEnumerable<QuestContentState> states)
{
return await _questContentRepository.getUserQuests(userGuid, states);
}
public async Task<QuestBoardQueryResult> getQuestBoard(string? userGuid, UgqQuestBoardRequest request, int pageSize)
{
return await _questContentRepository.getQuestBoard(userGuid, request, pageSize);
}
public async Task<QuestBoardQueryResult> getBookmarkQuests(string userGuid, UgqQuestBoardRequest request, int pageSize)
{
return await _questContentRepository.getBookmarkQuests(userGuid, request, pageSize);
}
public async Task<QuestBoardSportlightQueryResult> getQuestBoardSpotlight()
{
return await _questContentRepository.getQuestBoardSpotlight();
}
public async Task<QuestBoardDetailItemResult?> getQuestBoardDetail(string userGuid, long questId, long revision)
{
return await _questContentRepository.getQuestBoardDetail(userGuid, questId, revision);
}
public async Task<ServerErrorCode> bookmark(long questId, string userGuid)
{
var entity = await _bookmarkRepository.get(questId, userGuid);
if (entity != null)
return ServerErrorCode.UgqAlreadyBookmarked;
return await _bookmarkRepository.insert(questId, userGuid);
}
public async Task<ServerErrorCode> unbookmark(long questId, string userGuid)
{
var entity = await _bookmarkRepository.get(questId, userGuid);
if (entity == null)
return ServerErrorCode.UgqNotBookmarked;
return await _bookmarkRepository.delete(questId, userGuid);
}
public async Task<ServerErrorCode> like(long questId, long revision, string userGuid)
{
var entity = await _likeRepository.get(questId, userGuid);
if (entity != null)
return ServerErrorCode.UgqAlreadyLiked;
return await _likeRepository.insert(questId, revision, userGuid);
}
public async Task<ServerErrorCode> unlike(long questId, long revision, string userGuid)
{
var entity = await _likeRepository.get(questId, userGuid);
if (entity == null)
return ServerErrorCode.UgqNotLiked;
return await _likeRepository.delete(questId, revision, userGuid);
}
public async Task<ServerErrorCode> report(string userGuid, long questId, long revision, string contents)
{
var entity = await _reportRepository.get(questId, revision, userGuid);
if (entity != null)
return ServerErrorCode.UgqAlreadyReported;
await _reportRepository.insert(questId, revision, userGuid, contents);
return ServerErrorCode.Success;
}
public async Task<ServerErrorCode> setQuestAccepted(string userGuid, UgqQuestAcceptedRequest request)
{
var gameQuestData = await _gameQuestDataRepository.get(request.QuestId, request.Revision, QuestContentState.Live);
if (gameQuestData == null)
return ServerErrorCode.UgqNullEntity;
string author = gameQuestData.UserGuid;
await _questAccceptedRepository.insert(request.QuestId, request.Revision, author, request.Reason, userGuid);
return ServerErrorCode.Success;
}
public async Task<ServerErrorCode> setQuestCompleted(string userGuid, UgqQuestCompletedRequest request)
{
var gameQuestData = await _gameQuestDataRepository.get(request.QuestId, request.Revision, QuestContentState.Live);
if (gameQuestData == null)
return ServerErrorCode.UgqNullEntity;
string author = gameQuestData.UserGuid;
await _questCompletedRepository.insert(request.QuestId, request.Revision, author, userGuid);
double cost = 0.0f;
switch (gameQuestData.GradeType)
{
case UgqGradeType.Amature: cost = MetaHelper.GameConfigMeta.UgqUsageFeeAmateur / 2.0f; break;
case UgqGradeType.RisingStar: cost = MetaHelper.GameConfigMeta.UgqUsageFeeRisingStar / 10.0f; break;
case UgqGradeType.Master1: cost = MetaHelper.GameConfigMeta.UgqUsageFeeMaster1 / 2.0f; break;
case UgqGradeType.Master2: cost = MetaHelper.GameConfigMeta.UgqUsageFeeMaster2 / 2.0f; break;
case UgqGradeType.Master3: cost = MetaHelper.GameConfigMeta.UgqUsageFeeMaster3 / 2.0f; break;
default:
return ServerErrorCode.UgqCurrencyError;
}
/*
long completeCount = await _questCompletedRepository.getCount(author);
if(completeCount >= MetaHelper.GameConfigMeta.UGQPromoteConditionRisingstar)
{
await _accountService.promoteAccountGrade(author, UgqGradeType.Amature, UgqGradeType.RisingStar);
}
*/
return await _accountService.addCreatorPoint(author, request.QuestId, request.Revision, cost, UgqCreatorPointReason.QuestCompleted);
}
public async Task<ServerErrorCode> setQuestAborted(string userGuid, UgqQuestAbortedRequest request)
{
var gameQuestData = await _gameQuestDataRepository.get(request.QuestId, request.Revision, QuestContentState.Live);
if (gameQuestData == null)
return ServerErrorCode.UgqNullEntity;
string author = gameQuestData.UserGuid;
await _questAbortedRepository.insert(request.QuestId, request.Revision, author, request.Reason, userGuid);
return ServerErrorCode.Success;
}
public async Task<(ServerErrorCode, QuestContentEntity?)> changeQuestStateForInGame(string userGuid,
long questId, long revision,
IEnumerable<QuestContentState> before, QuestContentState after)
{
var content = await _questContentRepository.getByQuestIdRevision(questId, revision);
if (content == null)
return (ServerErrorCode.UgqNullEntity, null);
var errorCode = ServerErrorCode.Success;
QuestContentEntity? updated = null;
switch (after)
{
case QuestContentState.Standby:
updated = await _questContentRepository.updateState(content.Id,
content.QuestId, content.Revision, before, after);
break;
case QuestContentState.Editable:
updated = await _questContentRepository.updateState(content.Id,
content.QuestId, content.Revision, before, after);
// <20><><EFBFBD><EFBFBD> Test <20><><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD> <20><><EFBFBD><EFBFBD>.
await _gameQuestDataRepository.delete(content.QuestId, content.Revision, QuestContentState.Test);
break;
default:
errorCode = ServerErrorCode.UgqStateChangeError;
break;
}
if (updated == null)
return (ServerErrorCode.UgqStateChangeError, null);
return (errorCode, updated);
}
public async Task<(ServerErrorCode, GameQuestDataEntity?)> getTestGameQuestData(string userGuid, long questId, long revision)
{
GameQuestDataEntity? entity = await _gameQuestDataRepository.get(questId, revision, QuestContentState.Test);
if (entity == null)
return (ServerErrorCode.UgqNullEntity, null);
// <20><><EFBFBD><EFBFBD> <20>ۼ<EFBFBD><DBBC><EFBFBD> <20><><EFBFBD><EFBFBD>Ʈ <20><> Test <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>͸<EFBFBD> <20><><EFBFBD><EFBFBD>
if (entity.UserGuid != userGuid)
return (ServerErrorCode.UgqNotOwnQuest, null);
return (ServerErrorCode.Success, entity);
}
public async Task<(ServerErrorCode, GameQuestDataEntity?)> getGameQuestData(long questId, long revision, QuestContentState state)
{
GameQuestDataEntity? entity = await _gameQuestDataRepository.get(questId, revision, state);
if(entity == null)
return (ServerErrorCode.UgqNullEntity, null);
return (ServerErrorCode.Success, entity);
}
public async Task<(ServerErrorCode, GameQuestDataEntity?)> getLatestRevisionGameQuestData(long questId)
{
GameQuestDataEntity? entity = await _gameQuestDataRepository.getLatestRevision(questId);
if (entity == null)
return (ServerErrorCode.UgqNullEntity, null);
return (ServerErrorCode.Success, entity);
}
public async Task setAuthor()
{
{
var list = await _questAbortedRepository.getAll();
foreach (var item in list)
{
var gameQuestData = await _gameQuestDataRepository.get(item.QuestId, item.Revision, QuestContentState.Live);
if (gameQuestData == null)
continue;
string author = gameQuestData.UserGuid;
await _questAbortedRepository.setAuthor(item.Id, author);
}
}
{
var list = await _questAccceptedRepository.getAll();
foreach (var item in list)
{
var gameQuestData = await _gameQuestDataRepository.get(item.QuestId, item.Revision, QuestContentState.Live);
if (gameQuestData == null)
continue;
string author = gameQuestData.UserGuid;
await _questAccceptedRepository.setAuthor(item.Id, author);
}
}
{
var list = await _questCompletedRepository.getAll();
foreach (var item in list)
{
var gameQuestData = await _gameQuestDataRepository.get(item.QuestId, item.Revision, QuestContentState.Live);
if (gameQuestData == null)
continue;
string author = gameQuestData.UserGuid;
await _questCompletedRepository.setAuthor(item.Id, author);
}
}
}
}

View File

@@ -0,0 +1,499 @@
using MongoDB.Driver;
using ServerCommon;
using ServerCore; using ServerBase;
using UGQDataAccess.Logs;
using UGQDataAccess.Repository;
using UGQDataAccess.Repository.Models;
using UGQDatabase.Models;
namespace UGQDataAccess.Service;
public class QuestEditorService
{
readonly QuestIdSequenceRepository _questIdSequenceRepository;
readonly QuestContentRepository _questContentRepository;
readonly QuestDialogRepository _questDialogRepository;
readonly GameQuestDataRepository _gameQuestDataRepository;
readonly CreatorPointHistoryRepository _creatorPointHistoryRepository;
readonly NpcNameRepository _npcNameRepository;
readonly UgqMetaGenerateService _ugqMetaGenerateService;
readonly AccountService _accountService;
public QuestEditorService(
AccountService accountService,
QuestIdSequenceRepository questIdSequenceRepository,
QuestContentRepository questContentRepository,
QuestDialogRepository questDialogRepository,
GameQuestDataRepository gameQuestDataRepository,
CreatorPointHistoryRepository creatorPointHistoryRepository,
NpcNameRepository npcNameRepository,
UgqMetaGenerateService ugqMetaGenerateService)
{
_accountService = accountService;
_questIdSequenceRepository = questIdSequenceRepository;
_questContentRepository = questContentRepository;
_questDialogRepository = questDialogRepository;
_gameQuestDataRepository = gameQuestDataRepository;
_creatorPointHistoryRepository = creatorPointHistoryRepository;
_npcNameRepository = npcNameRepository;
_ugqMetaGenerateService = ugqMetaGenerateService;
}
public async Task<AllSummaryQueryResult> getAllQuests(int pageNumber, int pageSize, QuestContentState state, string? searchText)
{
return await _questContentRepository.getAllSummaries(pageNumber, pageSize, state, searchText);
}
public async Task<List<QuestContentEntity>> getAll(string userGuid)
{
return await _questContentRepository.getAll(userGuid);
}
public async Task<long> getAllCount()
{
return await _questContentRepository.getAllCount();
}
public async Task<SummaryQueryResult> getSummaries(int pageNumber, int pageSize, string userGuid, QuestContentState state, string? searchText)
{
return await _questContentRepository.getSummaries(pageNumber, pageSize, userGuid, state, searchText);
}
public async Task<(ServerErrorCode, QuestContentEntity?)> addQuest(string userGuid, UGQSaveQuestModel saveQuestModel, string adminUsername)
{
var accountEntity = await _accountService.getAccount(userGuid);
if (accountEntity == null)
return (ServerErrorCode.UgqNullEntity, null);
List<QuestContentEntity> quests = await _questContentRepository.getAll(userGuid);
int slotCount = ServerCommon.MetaHelper.GameConfigMeta.UGQDefaultSlot + accountEntity.AdditionalSlotCount;
if (slotCount <= quests.Count)
return (ServerErrorCode.UgqNotEnoughQuestSlot, null);
var State = QuestContentState.Editable;
foreach(var task in saveQuestModel.Tasks)
{
if(task.ActionId == 0)
{
State = QuestContentState.Uncomplate;
break;
}
}
QuestContentEntity entity = new()
{
QuestId = 0,
Revision = 0,
UserGuid = userGuid,
Author = accountEntity.Nickname,
BeaconId = saveQuestModel.BeaconId,
UgcBeaconGuid = saveQuestModel.UgcBeaconGuid,
UgcBeaconNickname = saveQuestModel.UgcBeaconNickname,
GradeType = accountEntity.GradeType,
Title = new TextEntity
{
Kr = saveQuestModel.Title.Kr,
En = saveQuestModel.Title.En,
Jp = saveQuestModel.Title.Jp,
},
Langs = saveQuestModel.Languages.ToList(),
Description = new TextEntity
{
Kr = saveQuestModel.Description.Kr,
En = saveQuestModel.Description.En,
Jp = saveQuestModel.Description.Jp,
},
UploadCounter = 0,
TitleImagePath = string.Empty,
BannerImagePath = string.Empty,
State = State,
Cost = saveQuestModel.Cost,
Tasks = saveQuestModel.Tasks.Select(x => new TaskEntity
{
GoalText = new TextEntity
{
Kr = x.GoalText.Kr,
En = x.GoalText.En,
Jp = x.GoalText.Jp,
},
ActionId = x.ActionId,
ActionValue = x.ActionValue,
IsShowNpcLocation = x.IsShowNpcLocation,
UgcActionValueGuid = x.UgcActionValueGuid,
UgcActionValueName = x.UgcActionValueName,
}).ToList(),
Savelanguage = saveQuestModel.Savelanguage,
ContentVersion = 1,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
};
await _questContentRepository.insert(entity);
List<ILogInvoker> business_logs = [
new UgqApiQuestCraeteBusinessLog(entity.Id, adminUsername),
];
var log_action = new LogActionEx(LogActionType.UgqApiQuestCraete);
UgqApiBusinessLogger.collectLogs(log_action, userGuid, business_logs);
return (ServerErrorCode.Success, entity);
}
public async Task<ServerErrorCode> deleteQuest(string userGuid, string questContentId)
{
return await _questContentRepository.deleteQuest(userGuid, questContentId);
}
public async Task<(ServerErrorCode, QuestContentEntity?)> getQuest(string userGuid, string questContentId)
{
var entity = await _questContentRepository.get(questContentId);
if (entity == null)
return (ServerErrorCode.UgqNullEntity, null);
if (entity?.UserGuid != userGuid)
return (ServerErrorCode.UgqNotOwnQuest, null);
return (ServerErrorCode.Success, entity);
}
public async Task<(ServerErrorCode, QuestContentEntity?)> editQuest(string userGuid, string questContentId)
{
(ServerErrorCode errorCode, var entity) = await getQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return (errorCode, null);
if (entity == null)
return (ServerErrorCode.UgqNullEntity, null);
if (entity.State != QuestContentState.Editable &&
entity.State != QuestContentState.Uncomplate)
return (ServerErrorCode.UgqNotAllowEdit, null);
return (ServerErrorCode.Success, entity);
}
public async Task<(ServerErrorCode, QuestContentEntity?)> saveQuest(string userGuid,
string questContentId, UGQSaveQuestModel saveQuestModel)
{
int retryCount = 0;
while (retryCount < UGQConstants.MAX_UPDATE_RETRY_COUNT)
{
(ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return (errorCode, null);
if (questContentEntity == null)
return (ServerErrorCode.UgqNullEntity, null);
if (questContentEntity.State != QuestContentState.Editable &&
questContentEntity.State != QuestContentState.Uncomplate)
return (ServerErrorCode.UgqNotAllowEdit, null);
List<string> newDialogIds = saveQuestModel.Tasks
.Where(x => string.IsNullOrEmpty(x.DialogId) == false)
.Select(x => x.DialogId!).ToList();
HashSet<string> oldDialogIds = questContentEntity.Tasks
.Where(x => string.IsNullOrEmpty(x.DialogId) == false)
.Select(x => x.DialogId!).ToHashSet();
// <20><><EFBFBD><EFBFBD> <20><> task<73><6B> dialogId<49><64> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ִٸ<D6B4> <20><><EFBFBD><EFBFBD>
foreach (var dialogId in newDialogIds)
{
if (oldDialogIds.Contains(dialogId) == false)
return (ServerErrorCode.UgqDialogIsNotInTask, null);
}
int? contentVersion = questContentEntity.ContentVersion;
var updated = await _questContentRepository.updateContent(questContentId, saveQuestModel, contentVersion);
if (updated == null)
{
retryCount++;
Log.getLogger().warn($"questContent version missmatch. retryCount: {retryCount}");
continue;
}
return (ServerErrorCode.Success, updated);
}
return (ServerErrorCode.UgqExceedTransactionRetry, null);
}
public async Task<(ServerErrorCode, int)> incUploadCounter(string userGuid, string questContentId)
{
return await _questContentRepository.incUploadCounter(questContentId);
}
public async Task<(ServerErrorCode, QuestContentEntity?)> updateQuestImages(string questContentId, string? titleImage, string? bannerImage)
{
if (titleImage == null && bannerImage == null)
return (ServerErrorCode.UgqRequireImage, null);
var updated = await _questContentRepository.updateImages(questContentId, titleImage, bannerImage);
if (updated == null)
return (ServerErrorCode.UgqNullEntity, null);
return (ServerErrorCode.Success, updated);
}
public async Task<List<QuestDialogEntity>> getQuestDialogs(IEnumerable<string> questDialogIds)
{
return await _questDialogRepository.get(questDialogIds);
}
public async Task<(ServerErrorCode, QuestDialogEntity?)> getQuestDialog(string userGuid, string questContentId, string questDialogId)
{
(ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return (errorCode, null);
if (questContentEntity == null)
return (errorCode, null);
var task = questContentEntity.Tasks.Where(x => x.DialogId == questDialogId).FirstOrDefault();
if (task == null)
return (ServerErrorCode.UgqDialogIsNotInTask, null);
QuestDialogEntity? entity = await _questDialogRepository.get(questDialogId);
if (entity == null)
return (ServerErrorCode.UgqNullEntity, null);
return (ServerErrorCode.Success, entity);
}
public async Task<(ServerErrorCode, QuestContentEntity?, QuestDialogEntity?)> addQuestDialog(string userGuid, string questContentId,
int taskIndex, List<DialogSequenceEntity> sequences)
{
QuestDialogEntity dialogEntity = new()
{
Sequences = sequences,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow,
};
await _questDialogRepository.add(dialogEntity);
int retryCount = 0;
while (retryCount < UGQConstants.MAX_UPDATE_RETRY_COUNT)
{
(ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return (errorCode, null, null);
if (questContentEntity == null)
return (errorCode, null, null);
List<TaskEntity> tasks = questContentEntity.Tasks;
if (tasks.Count < taskIndex + 1)
{
int addCount = (taskIndex + 1) - tasks.Count;
for (int i = 0; i < addCount; i++)
{
TaskEntity emptyTask = new TaskEntity();
tasks.Add(emptyTask);
}
}
tasks[taskIndex].DialogId = dialogEntity.Id;
int? contentVersion = questContentEntity.ContentVersion;
var updated = await _questContentRepository.updateTasks(questContentId, tasks, contentVersion);
if (updated == null)
{
retryCount++;
Log.getLogger().warn($"questContent version missmatch. retryCount: {retryCount}");
continue;
}
return (ServerErrorCode.Success, updated, dialogEntity);
}
return (ServerErrorCode.UgqExceedTransactionRetry, null, null);
}
public async Task<(ServerErrorCode, QuestDialogEntity?)> saveQuestDialog(string userGuid, string questContentId,
string questDialogId, List<DialogSequenceEntity> sequences)
{
(ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return (errorCode, null);
if (questContentEntity == null)
return (errorCode, null);
var task = questContentEntity.Tasks.Where(x => x.DialogId == questDialogId).FirstOrDefault();
if (task == null)
return (ServerErrorCode.UgqDialogIsNotInTask, null);
return await _questDialogRepository.updateSequences(questDialogId, sequences);
}
public async Task<(ServerErrorCode, QuestContentEntity?)> changeQuestStateForEditor(string userGuid,
QuestContentEntity content, List<QuestDialogEntity>? dialogs,
IEnumerable<QuestContentState> before, QuestContentState after, string adminUsername)
{
QuestContentState fromState = content.State;
QuestContentState toState = after;
ServerErrorCode errorCode = ServerErrorCode.Success;
QuestContentEntity? updated = null;
switch (after)
{
case QuestContentState.Editable:
updated = await _questContentRepository.updateState(content.Id,
content.QuestId, content.Revision, before, after);
break;
case QuestContentState.Standby:
{
var gameQuestEntity = await _gameQuestDataRepository.setShutdown(content.QuestId, content.Revision);
if (gameQuestEntity == null)
return (ServerErrorCode.UgqNullEntity, null);
updated = await _questContentRepository.updateState(content.Id,
content.QuestId, content.Revision, before, after);
}
break;
case QuestContentState.Test:
{
if (dialogs == null)
return (ServerErrorCode.UgqNullEntity, null);
var accountEntity = await _accountService.getAccount(userGuid);
if (accountEntity == null)
return (ServerErrorCode.UgqNullEntity, null);
var beforeRevision = content.Revision;
if (content.QuestId == 0)
{
content.QuestId = await _questIdSequenceRepository.getNextSequence();
content.Revision = 1;
}
else
{
content.Revision++;
}
var tasks = content.Tasks;
var game_quest_dialog_data_entities = dialogs.Select(x => new GameQuestDialogDataEntity
{
Id = x.Id,
Sequences = x.Sequences,
}).ToList();
(var result, var quest_metas) = _ugqMetaGenerateService.generateUgqMeta(content);
var ugq_game_quest_data_for_client_string = _ugqMetaGenerateService.makeUgqGameQuestDataForClients(content, after, game_quest_dialog_data_entities);
var gameQuestEntity = await _gameQuestDataRepository.insert(content, QuestContentState.Test,
game_quest_dialog_data_entities, quest_metas, ugq_game_quest_data_for_client_string, accountEntity.GradeType);
if (gameQuestEntity == null)
return (ServerErrorCode.UgqNullEntity, null);
updated = await _questContentRepository.updateState(content.Id,
content.QuestId, content.Revision, before, after, accountEntity.GradeType);
if (updated != null&& beforeRevision != 0)
await _gameQuestDataRepository.delete(content.QuestId, beforeRevision, QuestContentState.Test);
}
break;
case QuestContentState.Live:
{
if (dialogs == null)
return (ServerErrorCode.UgqNullEntity, null);
bool toNextRevision = content.ToNextRevision ?? true;
long beforeRevision = content.Revision;
content.Revision = toNextRevision == true ? content.Revision + 1 : content.Revision;
var tasks = content.Tasks;
var game_quest_dialog_data_entities = dialogs.Select(x => new GameQuestDialogDataEntity
{
Id = x.Id,
Sequences = x.Sequences,
}).ToList();
(var result, var quest_metas) = _ugqMetaGenerateService.generateUgqMeta(content);
var ugq_game_quest_data_for_client_string = _ugqMetaGenerateService.makeUgqGameQuestDataForClients(content, after, game_quest_dialog_data_entities);
var gameQuestEntity = await _gameQuestDataRepository.insert(content, QuestContentState.Live, game_quest_dialog_data_entities, quest_metas, ugq_game_quest_data_for_client_string);
if (gameQuestEntity == null)
return (ServerErrorCode.UgqNullEntity, null);
updated = await _questContentRepository.updateState(content.Id,
content.QuestId, content.Revision, before, after);
if(updated != null)
await _gameQuestDataRepository.delete(content.QuestId, beforeRevision, QuestContentState.Test);
}
break;
case QuestContentState.Shutdown:
{
await _gameQuestDataRepository.setShutdown(content.QuestId, content.Revision);
updated = await _questContentRepository.updateState(content.Id,
content.QuestId, content.Revision, before, after);
}
break;
default:
errorCode = ServerErrorCode.UgqStateChangeError;
break;
}
if (updated == null)
return (ServerErrorCode.UgqStateChangeError, null);
if(errorCode == ServerErrorCode.Success)
{
List<ILogInvoker> business_logs = [
new UgqApiChangeStateBusinessLog(updated.Id, fromState.ToString(), toState.ToString(), updated.QuestId, updated.Revision, adminUsername),
];
var log_action = new LogActionEx(LogActionType.UgqApiChangeState);
UgqApiBusinessLogger.collectLogs(log_action, userGuid, business_logs);
}
return (errorCode, updated);
}
public async Task<CreatorPointHistoryQueryResult> getCreatorPointHistories(string userGuid, int pageNumber, int pageSize, CreatorPointHistoryKind kind, DateTimeOffset startDate, DateTimeOffset endDate)
{
return await _creatorPointHistoryRepository.getList(userGuid, pageNumber, pageSize, kind, startDate, endDate);
}
public async Task<QuestProfitStatsQueryResult> getQuestProfitStats(string userGuid, int pageNumber, int pageSize)
{
return await _questContentRepository.getQuestProfitStats(userGuid, pageNumber, pageSize);
}
public async Task gameQuestDataFakeUpdate(string userGuid, string questContentId)
{
(ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return;
if (questContentEntity == null)
return;
var questDialogIds = questContentEntity.Tasks
.Where(x => string.IsNullOrEmpty(x.DialogId) == false)
.Select(x => x.DialogId!)
.ToList();
var questDialogs = await getQuestDialogs(questDialogIds);
var tasks = questContentEntity.Tasks;
var game_quest_dialog_data_entities = questDialogs.Select(x => new GameQuestDialogDataEntity
{
Id = x.Id,
Sequences = x.Sequences,
}).ToList();
(var result, var quest_metas) = _ugqMetaGenerateService.generateUgqMeta(questContentEntity);
var ugq_game_quest_data_for_client = _ugqMetaGenerateService.makeUgqGameQuestDataForClients(questContentEntity, questContentEntity.State, game_quest_dialog_data_entities);
var gameQuestEntity = await _gameQuestDataRepository.insert(questContentEntity, QuestContentState.Test, game_quest_dialog_data_entities, quest_metas, ugq_game_quest_data_for_client);
}
}

View File

@@ -0,0 +1,317 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson.IO;
using Newtonsoft.Json;
using ServerCore; using ServerBase;
using ServerCommon;
using MetaAssets;
using UGQDatabase.Models;
using JsonConvert = MongoDB.Bson.IO.JsonConvert;
namespace UGQDataAccess.Service;
public class UgqMetaGenerateService
{
public (Result, List<QuestMetaInfo>) generateUgqMeta(QuestContentEntity content)
{
var result = new Result();
IUgqMetaGenerator? generator = null;
List<QuestMetaInfo> script_datas = new();
for (int i = 0; i < content.Tasks.Count; i++)
{
QuestMetaInfo meta = new();
//첫줄 스크립트 생성
var idx = script_datas.Count() + 1;
meta.Index = idx;
meta.ComposedQuestId = QuestHelper.convertQuestIdAndRevisionToUgqQuestId((UInt32)content.QuestId, (UInt32)content.Revision);
meta.EventTarget = EQuestEventTargetType.TASK.ToString();
meta.EventName = EQuestEventNameType.ACTIVED.ToString();
meta.EventCondition1 = (i + 1).ToString();
meta.EventCondition2 = string.Empty;
meta.EventCondition3 = string.Empty;
meta.FunctionTarget = EQuestFunctionTargetType.TASK.ToString();
meta.FunctionName = EQuestFunctionNameType.TITLE_UPDATE.ToString();
meta.FunctionCondition1 = (i + 1).ToString();
meta.FunctionCondition2 = string.Empty;
meta.FunctionCondition3 = string.Empty;
script_datas.Add(meta);
//태스크 시작에 대한 generator 생성
var action_id = content.Tasks[i].ActionId;
MetaData.Instance.UGQInputTaskMetaDataById.TryGetValue(action_id, out var task_meta);
if (task_meta == null)
{
result.setFail(ServerErrorCode.MetaInfoException, "");
return (result, []);
}
var event_target = task_meta.EventTarget;
var event_name = task_meta.Event;
(result, generator) = getUgqMetaGenerator(event_target, event_name, content, i);
if (result.isFail())
{
return (result, script_datas);
}
if(generator == null)
{
result.setFail(ServerErrorCode.MetaInfoException, "");
return (result, []);
}
generator.generateMetas(ref script_datas);
}
//Log.getLogger().info($"generateUgqMeta script_datas : {JsonConvert.SerializeObject(script_datas)}");
return (result, script_datas);
}
private (Result, IUgqMetaGenerator?) getUgqMetaGenerator(string eventTargetType, string eventNameType, QuestContentEntity questContentEntity, int currentIdx)
{
var result = new Result();
if (eventTargetType == EQuestEventTargetType.DIALOGUE.ToString() && eventNameType == EQuestEventNameType.ENDED.ToString())
{
return (result, new UgqMetaGeneratorDialogueEnded(questContentEntity, currentIdx));
}
if (eventTargetType == EQuestEventTargetType.ITEM.ToString() && eventNameType == EQuestEventNameType.BOUGHT.ToString())
{
return (result, new UgqMetaGeneratorItemBought(questContentEntity, currentIdx));
}
if (eventTargetType == EQuestEventTargetType.COSTUME.ToString() && eventNameType == EQuestEventNameType.EQUIPED.ToString())
{
return (result, new UgqMetaGeneratorCostumeEquiped(questContentEntity, currentIdx));
}
if (eventTargetType == EQuestEventTargetType.ITEM.ToString() && eventNameType == EQuestEventNameType.SOLD.ToString())
{
return (result, new UgqMetaGeneratorItemSold(questContentEntity, currentIdx));
}
if (eventTargetType == EQuestEventTargetType.TOOL.ToString() && eventNameType == EQuestEventNameType.ACTIVED.ToString())
{
return (result, new UgqMetaGeneratorToolActived(questContentEntity, currentIdx));
}
var err_msg = $"implemented IUgqGenerator not exist eventTargetType : {eventTargetType}, eventNameType : {eventNameType}";
result.setFail(ServerErrorCode.UgqMetaGeneratorNotExist, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, null);
}
public string makeUgqGameQuestDataForClients(QuestContentEntity content, QuestContentState after, List<GameQuestDialogDataEntity> dialogues, bool isRevisionUpdated = false)
{
UgqGameQuestDataForClient data = new();
UInt32 revision = (UInt32)content.Revision;
if (isRevisionUpdated)
{
revision += 1;
}
var composed_quest_id = QuestHelper.convertQuestIdAndRevisionToUgqQuestId((UInt32)content.QuestId, revision);
data.ComposedQuestId = composed_quest_id;
data.Author = content.Author;
data.AuthorGuid = content.UserGuid;
data.BeaconId = content.BeaconId;
data.UgcBeaconGuid = content.UgcBeaconGuid ?? "";
data.UgcBeaconNickname = content.UgcBeaconNickname ?? "";
data.Title = new();
data.Title.Kr = content.Title.Kr;
data.Title.En = content.Title.En;
data.Title.Jp = content.Title.Jp;
data.Task.AddRange(makeUgqGameTaskForClient(content));
data.Dialogues.AddRange(makeUgqGameDialogueForClient(dialogues));
data.UgqStateType = QuestHelper.convertQuestContentStateToUgqStateType(after);
return Newtonsoft.Json.JsonConvert.SerializeObject(data);
//return data;
}
private List<UgqGameQuestDialogue> makeUgqGameDialogueForClient(List<GameQuestDialogDataEntity> dialogues)
{
List<UgqGameQuestDialogue> datas = new();
foreach (var dialogue in dialogues)
{
UgqGameQuestDialogue data = new();
data.DialogueId = dialogue.Id;
data.Sequences.AddRange(makeUgqGameDialogueSequences(dialogue));
data.Returns.AddRange(makeUgqGameDialogueReturns(dialogue));
datas.Add(data);
}
return datas;
}
private List<UgqDialogueReturns> makeUgqGameDialogueReturns(GameQuestDialogDataEntity dialogue)
{
List<UgqDialogueReturns> datas = new();
foreach (var seq in dialogue.Sequences)
{
foreach(var action in seq.Actions)
{
if (action.NextSequence == -1)
{
UgqDialogueReturns data = new();
data.ReturnIdx = 1;
/*
//송팀장님과 나눈 내용 정리 (24-08-15)
원레 시스템 퀘스트는 Returns값을 만들기 위해 ActionIndex를 -1을 사용하지 않고 현재 사용중인 sequenceId에 +1을 해서 사용하고(미사용sequenceId를 할당하는 개념)
미사용sequenceId 로 returns를 만들어사 사용했음
그러나 현재 webtool에서는 명시적으로 대화가 종료되는 것을 -1로 명시한 상태에서 action.NextSequence 가 -1일 경우 ActionIndex를 current SequenceId 설정 했음
그래서 아래와 같이 코드를 추가했다.
추후에 내용을 바꾸려고 할경우 해당 경우 클라와 협의해서 변경해야된다. 임의로 바꾸면 안된다.
*/
data.ActionIndex = seq.SequenceId;
datas.Add(data);
break;
}
}
}
return datas;
}
private List<UgqDialogueSequences> makeUgqGameDialogueSequences(GameQuestDialogDataEntity dialogue)
{
List<UgqDialogueSequences> datas = new();
foreach (var seq in dialogue.Sequences)
{
UgqDialogueSequences data = new();
data.SequenceId = seq.SequenceId;
data.Actions.AddRange(makeUgqDialogueSequenceActions(seq.Actions));
datas.Add(data);
}
return datas;
}
private List<UgqDialogSequenceAction> makeUgqDialogueSequenceActions(List<DialogSequenceActionEntity> actions)
{
List<UgqDialogSequenceAction> datas = new();
foreach (var act in actions)
{
UgqDialogSequenceAction data = new();
data.Talker = ConvertDialogueTalkerToUgqDialogueTalker(act.Talker);
data.Contition = makeConditionString(act); //act.Type 으로 가공해야된다.;
data.ActionType = 4;
data.SubType = 0;
data.ActionNumber = act.ConditionValue;
data.ActionIndex = act.NextSequence;
data.Talk = new();
data.Talk.Kr = act.Talk.Kr;
data.Talk.En = act.Talk.En;
data.Talk.Jp = act.Talk.Jp;
if (data.Talker == UgqDialogueTalker.Player) data.IsDialogue = 0;
else data.IsDialogue = 1;
data.NpcAction = act.NpcAction;
datas.Add(data);
}
return datas;
}
private string makeConditionString(DialogSequenceActionEntity action)
{
MetaData.Instance.UGQInputDialogMetaDataListbyId.TryGetValue(action.Type, out UGQInputDialogMetaData? input_meta);
if(input_meta == null)
return "";
if (ServerCommon.Constant.UGQ_INPUT_DATA_TYPE_NAME_TEXT.Equals(input_meta.TypeName))
{
return "text";
}
if (ServerCommon.Constant.UGQ_INPUT_DATA_TYPE_NAME_ATTRIBUTE.Equals(input_meta.TypeName) || input_meta.ConditionSource == EUGQValueSource.UGQ_ATTRIBUTE_DEFINITION)
{
//id로 가져오는 Meta 가 없어서 우선 loop 처리
foreach (var definition_meta in MetaData.Instance._AttributeDefinitionMetaTable)
{
if (definition_meta.Value.ID == action.Condition)
{
return definition_meta.Key;
}
}
Log.getLogger().warn($"makeConditionString error input_meta {input_meta.ConditionSource}");
return "";
}
if (ServerCommon.Constant.UGQ_INPUT_DATA_TYPE_NAME_ITEM.Equals(input_meta.TypeName))
{
return "item";
}
if (ServerCommon.Constant.UGQ_INPUT_DATA_TYPE_NAME_EMOTE.Equals(input_meta.TypeName))
{
return "emote";
}
Log.getLogger().warn($"makeConditionString error input_meta {input_meta.ConditionSource}");
return "";
}
private UgqDialogueTalker ConvertDialogueTalkerToUgqDialogueTalker(DialogTalker talker)
{
return talker switch
{
DialogTalker.Player => UgqDialogueTalker.Player,
DialogTalker.Npc => UgqDialogueTalker.Npc,
_ => throw new NotImplementedException(),
};
}
private List<UgqGameTaskDataForClient> makeUgqGameTaskForClient(QuestContentEntity content)
{
List<UgqGameTaskDataForClient> tasks = new();
int i = 1;
foreach (var t in content.Tasks)
{
UgqGameTaskDataForClient data = new();
data.TaskNum = i;
data.GoalText = new();
data.GoalText.Kr = t.GoalText.Kr;
data.GoalText.En = t.GoalText.En;
data.GoalText.Jp = t.GoalText.Jp;
data.DialogueId = t.DialogId ?? "";
data.UgcBeaconGuid = t.UgcActionValueGuid ?? "";
data.UgcBeaconNickname = t.UgcActionValueName ?? "";
data.ActionId = t.ActionId;
data.ActionValue = t.ActionValue;
data.IsShowNpcLocation = t.IsShowNpcLocation == true ? BoolType.True : BoolType.False;
tasks.Add(data);
i++;
}
return tasks;
}
}

View File

@@ -0,0 +1,15 @@
namespace UGQDataAccess.Settings;
#pragma warning disable CS8618
public class UGQDatabaseSettings
{
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
public int MinConnectionPoolSize { get; set; }
public int MaxConnectionPoolSize { get; set; }
public int WaitQueueTimeoutSecs { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace UGQDataAccess;
public class UGQConstants
{
public const int MAX_INPUT_PAGE_SIZE = 100;
public const int MAX_UPDATE_RETRY_COUNT = 10;
}

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Configurations>Debug;Release;Shipping</Configurations>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
<PackageReference Include="MongoDB.Bson" />
<PackageReference Include="MongoDB.Analyzer" />
<PackageReference Include="MongoDB.Driver" />
<PackageReference Include="MongoDB.Driver.Core" />
<PackageReference Include="BCrypt.Net-Next" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ServerCommon\ServerCommon.csproj" />
</ItemGroup>
</Project>