-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Common: マスタデータをスプレッドシートから読み込めるように (#18)
- Loading branch information
Showing
32 changed files
with
598 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
# .NET Core build folders | ||
bin/ | ||
obj/ | ||
Environment/ | ||
|
||
# Common node modules locations | ||
/node_modules | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace Approvers.King.Common; | ||
|
||
public static class EnumrableExtensions | ||
{ | ||
public static TDist? FirstTypeOrDefault<T, TDist>(this IEnumerable<T> source) | ||
{ | ||
foreach (var item in source) | ||
{ | ||
if (item is TDist dist) | ||
{ | ||
return dist; | ||
} | ||
} | ||
|
||
return default; | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using Google.Apis.Sheets.v4.Data; | ||
|
||
namespace Approvers.King.Common; | ||
|
||
public class GoogleSheet | ||
{ | ||
public string SheetName { get; } | ||
public IList<IList<object>> Values { get; } | ||
public int RowCount { get; } | ||
public int ColumnCount { get; } | ||
public GoogleSheet(ValueRange valueRange) | ||
{ | ||
SheetName = valueRange.Range.Split('!')[0].Replace("'", ""); | ||
Values = valueRange.Values; | ||
RowCount = Values.Count; | ||
ColumnCount = Values.Count > 0 ? Values[0].Count : 0; | ||
} | ||
public string Get(int row, int column) | ||
{ | ||
return (string)Values[row][column]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System.Net.Mime; | ||
using Google.Apis.Auth.OAuth2; | ||
using Google.Apis.Services; | ||
using Google.Apis.Sheets.v4; | ||
|
||
namespace Approvers.King.Common; | ||
|
||
public static class GoogleSheetManager | ||
{ | ||
private static readonly string[] Scopes = [SheetsService.Scope.SpreadsheetsReadonly]; | ||
private static ICredential? _credential; | ||
|
||
public static async Task<IReadOnlyDictionary<string, GoogleSheet>> GetAllSheetsAsync(IEnumerable<string> sheetNames) | ||
{ | ||
if (_credential == null) | ||
{ | ||
await using var stream = new FileStream(EnvironmentManager.GoogleCredentialFilePath, FileMode.Open, FileAccess.Read); | ||
_credential = (await GoogleCredential.FromStreamAsync(stream, CancellationToken.None)).CreateScoped(Scopes).UnderlyingCredential; | ||
} | ||
var sheetService = new SheetsService(new BaseClientService.Initializer | ||
{ | ||
HttpClientInitializer = _credential | ||
}); | ||
var request = sheetService.Spreadsheets.Values.BatchGet(EnvironmentManager.GoogleMasterSheetId); | ||
request.Ranges = sheetNames.ToList(); | ||
var sheet = await request.ExecuteAsync(); | ||
return sheet.ValueRanges.Select(x => new GoogleSheet(x)).ToDictionary(x => x.SheetName, x => x); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
namespace Approvers.King.Common; | ||
|
||
public static class LogManager | ||
{ | ||
private static string CreateTimestamp() | ||
{ | ||
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); | ||
} | ||
|
||
public static void Log(string message) | ||
{ | ||
Console.WriteLine($"{CreateTimestamp()}: [INFO] {message}"); | ||
} | ||
|
||
public static void LogError(string message) | ||
{ | ||
Console.Error.WriteLine($"{CreateTimestamp()}: [ERROR] {message}"); | ||
} | ||
|
||
public static void LogException(Exception exception) | ||
{ | ||
Console.Error.WriteLine($"{CreateTimestamp()}: [EXCEPTION] {exception.Message}"); | ||
Console.Error.Write(exception); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
using System.Reflection; | ||
|
||
namespace Approvers.King.Common; | ||
|
||
public class MasterManager : Singleton<MasterManager> | ||
{ | ||
[MasterTable("random_message")] private RandomMessageMaster _randomMessageMaster; | ||
public static RandomMessageMaster RandomMessageMaster => Instance._randomMessageMaster; | ||
|
||
[MasterTable("trigger_phrase")] private TriggerPhraseMaster _triggerPhraseMaster; | ||
public static TriggerPhraseMaster TriggerPhraseMaster => Instance._triggerPhraseMaster; | ||
|
||
[field: MasterTable("setting")] private SettingMaster _settingMaster; | ||
public static SettingMaster SettingMaster => Instance._settingMaster; | ||
|
||
public static async Task FetchAsync() | ||
{ | ||
var dict = new Dictionary<FieldInfo, string>(); | ||
var masterFields = Instance.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance); | ||
foreach (var field in masterFields) | ||
{ | ||
var tableAttributes = field.GetCustomAttributes().FirstTypeOrDefault<Attribute, MasterTableAttribute>(); | ||
if (tableAttributes == null) continue; | ||
dict.Add(field, tableAttributes.KeyName); | ||
} | ||
var sheets = await GoogleSheetManager.GetAllSheetsAsync(dict.Values); | ||
foreach (var (field, sheetName) in dict) | ||
{ | ||
var masterType = field.FieldType; | ||
var masterGenericArguments = masterType.BaseType!.GetGenericArguments(); | ||
var keyType = masterGenericArguments[0]; | ||
var recordType = masterGenericArguments[1]; | ||
var master = typeof(MasterManager) | ||
.GetMethod(nameof(BuildMaster), BindingFlags.NonPublic | BindingFlags.Static)! | ||
.MakeGenericMethod(masterType, keyType, recordType) | ||
.Invoke(null, [sheets[sheetName]]); | ||
field.SetValue(Instance, master); | ||
} | ||
} | ||
|
||
private static TMaster BuildMaster<TMaster, TKey, TRecord>(GoogleSheet sheet) | ||
where TMaster : MasterTable<TKey, TRecord>, new() | ||
where TRecord : MasterRecord<TKey>, new() | ||
where TKey : notnull | ||
{ | ||
var records = Enumerable.Range(0, sheet.RowCount - 1).Select(_ => new TRecord()).ToList(); | ||
var keyDictionary = Enumerable.Range(0, sheet.ColumnCount).ToDictionary(i => sheet.Get(0, i), i => i); | ||
var fields = typeof(TRecord).GetFields(BindingFlags.NonPublic | BindingFlags.Instance); | ||
foreach (var field in fields) | ||
{ | ||
var propertyAttributes = field.GetCustomAttributes(); | ||
foreach (var propertyAttribute in propertyAttributes) | ||
{ | ||
switch (propertyAttribute) | ||
{ | ||
case MasterIntValueAttribute integerValueAttribute: | ||
{ | ||
if (keyDictionary.TryGetValue(integerValueAttribute.KeyName, out var column) == false) | ||
{ | ||
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + integerValueAttribute.KeyName); | ||
continue; | ||
} | ||
for (var row = 1; row < sheet.RowCount; row++) | ||
{ | ||
var value = sheet.Get(row, column); | ||
if (int.TryParse(value, out var intValue) == false) | ||
{ | ||
LogManager.LogError("Invalid value type (integer): " + sheet.SheetName + "/" + integerValueAttribute.KeyName + $"[{row}] = {value}"); | ||
continue; | ||
} | ||
field.SetValue(records[row - 1], intValue); | ||
} | ||
break; | ||
} | ||
case MasterFloatValueAttribute floatValueAttribute: | ||
{ | ||
if (keyDictionary.TryGetValue(floatValueAttribute.KeyName, out var column) == false) | ||
{ | ||
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + floatValueAttribute.KeyName); | ||
continue; | ||
} | ||
for (var row = 1; row < sheet.RowCount; row++) | ||
{ | ||
var value = sheet.Get(row, column); | ||
if (float.TryParse(value, out var floatValue) == false) | ||
{ | ||
LogManager.LogError("Invalid value type (float): " + sheet.SheetName + "/" + floatValueAttribute.KeyName + $"[{row}] = {value}"); | ||
continue; | ||
} | ||
field.SetValue(records[row - 1], floatValue); | ||
} | ||
break; | ||
} | ||
case MasterStringValueAttribute stringValueAttribute: | ||
{ | ||
if (keyDictionary.TryGetValue(stringValueAttribute.KeyName, out var column) == false) | ||
{ | ||
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + stringValueAttribute.KeyName); | ||
continue; | ||
} | ||
for (var row = 1; row < sheet.RowCount; row++) | ||
{ | ||
var value = sheet.Get(row, column); | ||
field.SetValue(records[row - 1], value); | ||
} | ||
break; | ||
} | ||
case MasterBoolValueAttribute booleanValueAttribute: | ||
{ | ||
if (keyDictionary.TryGetValue(booleanValueAttribute.KeyName, out var column) == false) | ||
{ | ||
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + booleanValueAttribute.KeyName); | ||
continue; | ||
} | ||
for (var row = 1; row < sheet.RowCount; row++) | ||
{ | ||
var value = sheet.Get(row, column); | ||
if (bool.TryParse(value, out var boolValue) == false) | ||
{ | ||
LogManager.LogError("Invalid value type (bool): " + sheet.SheetName + "/" + booleanValueAttribute.KeyName + $"[{row}] = {value}"); | ||
continue; | ||
} | ||
field.SetValue(records[row - 1], boolValue); | ||
} | ||
break; | ||
} | ||
case MasterEnumValueAttribute enumValueAttribute: | ||
{ | ||
if (keyDictionary.TryGetValue(enumValueAttribute.KeyName, out var column) == false) | ||
{ | ||
LogManager.LogError("Key not found: " + sheet.SheetName + "/" + enumValueAttribute.KeyName); | ||
continue; | ||
} | ||
for (var row = 1; row < sheet.RowCount; row++) | ||
{ | ||
var value = sheet.Get(row, column); | ||
if (Enum.TryParse(enumValueAttribute.EnumType, value, out var enumValue) == false) | ||
{ | ||
LogManager.LogError($"Invalid value type ({enumValueAttribute.EnumType.Name}): " + sheet.SheetName + "/" + enumValueAttribute.KeyName + $"[{row}] = {value}"); | ||
continue; | ||
} | ||
field.SetValue(records[row - 1], enumValue); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
var master = new TMaster(); | ||
master.Set(records); | ||
return master; | ||
} | ||
} | ||
|
||
public class MasterBoolValueAttribute : Attribute | ||
{ | ||
public MasterBoolValueAttribute(string keyName) | ||
{ | ||
KeyName = keyName; | ||
} | ||
public string KeyName { get; } | ||
} | ||
public class MasterEnumValueAttribute : Attribute | ||
{ | ||
public MasterEnumValueAttribute(string keyName, Type enumType) | ||
{ | ||
KeyName = keyName; | ||
EnumType = enumType; | ||
} | ||
public string KeyName { get; } | ||
public Type EnumType { get; } | ||
} | ||
|
||
public class MasterFloatValueAttribute : Attribute | ||
{ | ||
public MasterFloatValueAttribute(string keyName) | ||
{ | ||
KeyName = keyName; | ||
} | ||
public string KeyName { get; } | ||
} | ||
|
||
public class MasterIntValueAttribute : Attribute | ||
{ | ||
public MasterIntValueAttribute(string keyName) | ||
{ | ||
KeyName = keyName; | ||
} | ||
public string KeyName { get; } | ||
} | ||
|
||
public class MasterStringValueAttribute : Attribute | ||
{ | ||
public MasterStringValueAttribute(string keyName) | ||
{ | ||
KeyName = keyName; | ||
} | ||
public string KeyName { get; } | ||
} | ||
|
||
public class MasterStringCollectionValueAttribute : Attribute | ||
{ | ||
public MasterStringCollectionValueAttribute(string keyName) | ||
{ | ||
KeyName = keyName; | ||
} | ||
public string KeyName { get; } | ||
} | ||
|
||
public class MasterTableAttribute : Attribute | ||
{ | ||
public MasterTableAttribute(string keyName) | ||
{ | ||
KeyName = keyName; | ||
} | ||
|
||
public string KeyName { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace Approvers.King.Common; | ||
|
||
public abstract class MasterRecord<TKey> where TKey : notnull | ||
{ | ||
public abstract TKey Key { get; } | ||
} |
Oops, something went wrong.