Skip to content

Commit 69e3561

Browse files
committed
feat: implement playtime restriction feature
1 parent b4a853f commit 69e3561

20 files changed

+617
-4
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace MapChooserSharp.Modules.McsDatabase.Entities;
2+
3+
public class McsUserInformation
4+
{
5+
public int Id { get; set; }
6+
7+
public long SteamId { get; set; }
8+
9+
public uint SessionTime { get; set; }
10+
11+
public DateTime LastLoggedInAt { get; set; }
12+
13+
public DateTime UserSessionStartedAt { get; set; }
14+
}
15+

MapChooserSharp/Modules/McsDatabase/Interfaces/IMcsDatabaseProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ internal interface IMcsDatabaseProvider
77
{
88
internal IMcsMapInformationRepository MapInfoRepository { get; }
99
internal McsGroupInformationRepository GroupInfoRepository { get; }
10+
internal IMcsUserInformationRepository UserInfoRepository { get; }
1011
}

MapChooserSharp/Modules/McsDatabase/McsDatabaseProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal sealed class McsDatabaseProvider(IServiceProvider serviceProvider)
2626
public IMcsMapInformationRepository MapInfoRepository { get; private set; } = null!;
2727

2828
public McsGroupInformationRepository GroupInfoRepository { get; private set; } = null!;
29+
public IMcsUserInformationRepository UserInfoRepository { get; private set; } = null!;
2930

3031
public override void RegisterServices(IServiceCollection services)
3132
{
@@ -40,6 +41,7 @@ protected override void OnInitialize()
4041

4142
MapInfoRepository = new McsMapInformationRepository(_connectionString, _providerType, _pluginConfigProvider.PluginConfig.GeneralConfig.SqlConfig.MapSettingsSqlTableName, ServiceProvider);
4243
GroupInfoRepository = new McsGroupInformationRepository(_connectionString, _providerType, _pluginConfigProvider.PluginConfig.GeneralConfig.SqlConfig.GroupSettingsSqlTableName, ServiceProvider);
44+
UserInfoRepository = new McsUserInformationRepository(_connectionString, _providerType, "mcs_user_settings", ServiceProvider);
4345

4446
MapInfoRepository.EnsureAllMapInfoExistsAsync().ConfigureAwait(false);
4547
GroupInfoRepository.EnsureAllGroupInfoExistsAsync().ConfigureAwait(false);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using MapChooserSharp.Modules.McsDatabase.Entities;
2+
3+
namespace MapChooserSharp.Modules.McsDatabase.Repositories.Interfaces;
4+
5+
public interface IMcsUserInformationRepository
6+
{
7+
Task UpsertUserInformationAsync(long steamId, McsUserInformation userInformation);
8+
9+
Task IncrementUserSessionTimeAsync(long steamId);
10+
11+
Task IncrementUserSessionTimeWithResetCheckAsync(long steamId, TimeSpan resetTimeOfDay);
12+
13+
Task ResetUserSessionTimeAsync(long steamId);
14+
15+
Task<McsUserInformation?> GetUserInformationAsync(long steamId);
16+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
using Dapper;
2+
using MapChooserSharp.Modules.McsDatabase.Entities;
3+
using MapChooserSharp.Modules.McsDatabase.Repositories.Interfaces;
4+
using MapChooserSharp.Modules.McsDatabase.Repositories.SqlProviders.Interfaces;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace MapChooserSharp.Modules.McsDatabase.Repositories;
8+
9+
public sealed class McsUserInformationRepository
10+
: McsDatabaseRepositoryBase, IMcsUserInformationRepository
11+
{
12+
private readonly IMcsSqlQueryProvider _sqlQueryProvider;
13+
private readonly string _connectionString;
14+
15+
public McsUserInformationRepository(string connectionString, McsSupportedSqlType providerType, string tableName, IServiceProvider provider)
16+
: base(provider, tableName)
17+
{
18+
_sqlQueryProvider = CreateSqlProvider(providerType);
19+
_connectionString = connectionString;
20+
21+
EnsureTableExists();
22+
}
23+
24+
private void EnsureTableExists()
25+
{
26+
try
27+
{
28+
using var connection = _sqlQueryProvider.CreateConnection(_connectionString);
29+
connection.Open();
30+
connection.Execute(_sqlQueryProvider.UserInfoSqlQueries().GetEnsureTableExistsSql());
31+
}
32+
catch (Exception ex)
33+
{
34+
Plugin.Logger.LogError(ex, "Failed to create McsUserInformation table");
35+
throw;
36+
}
37+
Plugin.Logger.LogInformation("McsUserInformation table ensured");
38+
}
39+
40+
public async Task UpsertUserInformationAsync(long steamId, McsUserInformation userInformation)
41+
{
42+
try
43+
{
44+
Logger.LogInformation($"Upserting user information for SteamId {steamId}");
45+
46+
using var connection = _sqlQueryProvider.CreateConnection(_connectionString);
47+
connection.Open();
48+
49+
await connection.ExecuteAsync(
50+
_sqlQueryProvider.UserInfoSqlQueries().GetUpsertUserInfoSql(),
51+
new
52+
{
53+
SteamId = steamId,
54+
userInformation.SessionTime,
55+
userInformation.LastLoggedInAt,
56+
userInformation.UserSessionStartedAt
57+
}
58+
);
59+
60+
Logger.LogInformation($"Successfully upserted user information for SteamId {steamId}");
61+
}
62+
catch (Exception ex)
63+
{
64+
Plugin.Logger.LogError(ex, $"Error upserting user information for SteamId {steamId}: {ex.Message}");
65+
throw;
66+
}
67+
}
68+
69+
public async Task IncrementUserSessionTimeAsync(long steamId)
70+
{
71+
try
72+
{
73+
using var connection = _sqlQueryProvider.CreateConnection(_connectionString);
74+
connection.Open();
75+
76+
var now = DateTime.UtcNow;
77+
78+
// First try to increment existing record
79+
var rowsAffected = await connection.ExecuteAsync(
80+
_sqlQueryProvider.UserInfoSqlQueries().GetIncrementSessionTimeSql(),
81+
new { SteamId = steamId, LastLoggedInAt = now }
82+
);
83+
84+
// If no rows were affected, the user doesn't exist yet, so insert a new record
85+
if (rowsAffected == 0)
86+
{
87+
await connection.ExecuteAsync(
88+
_sqlQueryProvider.UserInfoSqlQueries().GetUpsertUserInfoSql(),
89+
new
90+
{
91+
SteamId = steamId,
92+
SessionTime = 1,
93+
LastLoggedInAt = now,
94+
UserSessionStartedAt = now
95+
}
96+
);
97+
}
98+
}
99+
catch (Exception ex)
100+
{
101+
Plugin.Logger.LogError(ex, $"Error incrementing session time for SteamId {steamId}: {ex.Message}");
102+
throw;
103+
}
104+
}
105+
106+
/// <summary>
107+
/// Increments user session time with automatic reset check based on configured reset time.
108+
/// If the current time has passed the reset time since UserSessionStartedAt, the session is reset.
109+
/// </summary>
110+
/// <param name="steamId">Steam ID</param>
111+
/// <param name="resetTimeOfDay">Time of day when sessions should reset (e.g., 04:00 for 4 AM)</param>
112+
public async Task IncrementUserSessionTimeWithResetCheckAsync(long steamId, TimeSpan resetTimeOfDay)
113+
{
114+
try
115+
{
116+
using var connection = _sqlQueryProvider.CreateConnection(_connectionString);
117+
connection.Open();
118+
119+
var now = DateTime.UtcNow;
120+
121+
// Get existing user information
122+
var userInfo = await connection.QueryFirstOrDefaultAsync<McsUserInformation>(
123+
_sqlQueryProvider.UserInfoSqlQueries().GetUserInfoBySteamIdSql(),
124+
new { SteamId = steamId }
125+
);
126+
127+
if (userInfo == null)
128+
{
129+
// User doesn't exist, create new record
130+
await connection.ExecuteAsync(
131+
_sqlQueryProvider.UserInfoSqlQueries().GetUpsertUserInfoSql(),
132+
new
133+
{
134+
SteamId = steamId,
135+
SessionTime = 1,
136+
LastLoggedInAt = now,
137+
UserSessionStartedAt = now
138+
}
139+
);
140+
return;
141+
}
142+
143+
// Check if we need to reset the session
144+
bool shouldReset = ShouldResetSession(userInfo.UserSessionStartedAt, now, resetTimeOfDay);
145+
146+
if (shouldReset)
147+
{
148+
Logger.LogInformation(
149+
$"Resetting session for SteamId {steamId}. Started at: {userInfo.UserSessionStartedAt:yyyy-MM-dd HH:mm:ss}, " +
150+
$"Current time: {now:yyyy-MM-dd HH:mm:ss}, Reset time: {resetTimeOfDay}"
151+
);
152+
153+
// Reset session time and update start time
154+
await connection.ExecuteAsync(
155+
_sqlQueryProvider.UserInfoSqlQueries().GetResetSessionTimeWithStartTimeSql(),
156+
new { SteamId = steamId, UserSessionStartedAt = now }
157+
);
158+
159+
// Increment to 1 (since this is a new session)
160+
await connection.ExecuteAsync(
161+
_sqlQueryProvider.UserInfoSqlQueries().GetIncrementSessionTimeSql(),
162+
new { SteamId = steamId, LastLoggedInAt = now }
163+
);
164+
}
165+
else
166+
{
167+
// Just increment normally
168+
await connection.ExecuteAsync(
169+
_sqlQueryProvider.UserInfoSqlQueries().GetIncrementSessionTimeSql(),
170+
new { SteamId = steamId, LastLoggedInAt = now }
171+
);
172+
}
173+
}
174+
catch (Exception ex)
175+
{
176+
Plugin.Logger.LogError(ex, $"Error incrementing session time with reset check for SteamId {steamId}: {ex.Message}");
177+
throw;
178+
}
179+
}
180+
181+
public async Task ResetUserSessionTimeAsync(long steamId)
182+
{
183+
try
184+
{
185+
Logger.LogInformation($"Resetting session time for SteamId {steamId}");
186+
187+
using var connection = _sqlQueryProvider.CreateConnection(_connectionString);
188+
connection.Open();
189+
190+
await connection.ExecuteAsync(
191+
_sqlQueryProvider.UserInfoSqlQueries().GetResetSessionTimeSql(),
192+
new { SteamId = steamId }
193+
);
194+
195+
Logger.LogInformation($"Successfully reset session time for SteamId {steamId}");
196+
}
197+
catch (Exception ex)
198+
{
199+
Plugin.Logger.LogError(ex, $"Error resetting session time for SteamId {steamId}: {ex.Message}");
200+
throw;
201+
}
202+
}
203+
204+
public async Task<McsUserInformation?> GetUserInformationAsync(long steamId)
205+
{
206+
try
207+
{
208+
using var connection = _sqlQueryProvider.CreateConnection(_connectionString);
209+
connection.Open();
210+
211+
var userInfo = await connection.QueryFirstOrDefaultAsync<McsUserInformation>(
212+
_sqlQueryProvider.UserInfoSqlQueries().GetUserInfoBySteamIdSql(),
213+
new { SteamId = steamId }
214+
);
215+
216+
return userInfo;
217+
}
218+
catch (Exception ex)
219+
{
220+
Plugin.Logger.LogError(ex, $"Error getting user information for SteamId {steamId}: {ex.Message}");
221+
throw;
222+
}
223+
}
224+
225+
/// <summary>
226+
/// Determines if a session should be reset based on the reset time of day.
227+
/// Returns true if the reset time has been crossed since the session started.
228+
/// </summary>
229+
/// <param name="sessionStartedAt">When the session started</param>
230+
/// <param name="currentTime">Current time</param>
231+
/// <param name="resetTimeOfDay">Time of day when reset should occur</param>
232+
/// <returns>True if session should be reset</returns>
233+
private bool ShouldResetSession(DateTime sessionStartedAt, DateTime currentTime, TimeSpan resetTimeOfDay)
234+
{
235+
// Calculate the reset datetime for the session start date
236+
var resetDateTimeOnSessionStartDate = sessionStartedAt.Date + resetTimeOfDay;
237+
238+
// If session started before reset time on that day, the next reset is on the same day
239+
// If session started after reset time on that day, the next reset is on the next day
240+
var nextResetTime = sessionStartedAt <= resetDateTimeOnSessionStartDate
241+
? resetDateTimeOnSessionStartDate
242+
: resetDateTimeOnSessionStartDate.AddDays(1);
243+
244+
// Check if current time has passed the next reset time
245+
return currentTime >= nextResetTime;
246+
}
247+
}

MapChooserSharp/Modules/McsDatabase/Repositories/SqlProviders/Interfaces/IMcsSqlQueryProvider.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,10 @@ internal interface IMcsSqlQueryProvider
1515
/// </summary>
1616
IMcsGroupSqlQueries GroupSqlQueries();
1717

18+
/// <summary>
19+
/// Access User SQL queries
20+
/// </summary>
21+
IMcsUserInformationSqlQueries UserInfoSqlQueries();
22+
1823
IDbConnection CreateConnection(string connectionString);
1924
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace MapChooserSharp.Modules.McsDatabase.Repositories.SqlProviders.Interfaces;
2+
3+
internal interface IMcsUserInformationSqlQueries
4+
{
5+
string TableName { get; }
6+
7+
string GetEnsureTableExistsSql();
8+
9+
string GetUpsertUserInfoSql();
10+
11+
string GetIncrementSessionTimeSql();
12+
13+
string GetResetSessionTimeSql();
14+
15+
string GetResetSessionTimeWithStartTimeSql();
16+
17+
string GetUserInfoBySteamIdSql();
18+
}

MapChooserSharp/Modules/McsDatabase/Repositories/SqlProviders/MySql/MySqlSqlProvider.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ internal sealed class MySqlSqlProvider(string tableName) : IMcsSqlQueryProvider
1414
private readonly IMcsGroupSqlQueries _mcsGroupSqlQueries = new MySqlGroupSqlQueries(tableName);
1515

1616
public IMcsGroupSqlQueries GroupSqlQueries() => _mcsGroupSqlQueries;
17+
18+
private readonly IMcsUserInformationSqlQueries _mcsUserInformationSqlQueries = new MySqlUserInformationSqlQueries(tableName);
19+
20+
public IMcsUserInformationSqlQueries UserInfoSqlQueries() => _mcsUserInformationSqlQueries;
1721

1822
public IDbConnection CreateConnection(string connectionString) =>
1923
new MySqlConnection(connectionString);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using MapChooserSharp.Modules.McsDatabase.Repositories.SqlProviders.Interfaces;
2+
3+
namespace MapChooserSharp.Modules.McsDatabase.Repositories.SqlProviders.MySql;
4+
5+
internal sealed class MySqlUserInformationSqlQueries(string tableName) : IMcsUserInformationSqlQueries
6+
{
7+
public string TableName { get; } = tableName;
8+
9+
public string GetEnsureTableExistsSql() => @$"
10+
CREATE TABLE IF NOT EXISTS {TableName} (
11+
Id INT AUTO_INCREMENT PRIMARY KEY,
12+
SteamId BIGINT NOT NULL UNIQUE,
13+
SessionTime INT UNSIGNED NOT NULL DEFAULT 0,
14+
LastLoggedInAt DATETIME NOT NULL,
15+
UserSessionStartedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
16+
)";
17+
18+
public string GetUpsertUserInfoSql() => @$"
19+
INSERT INTO {TableName} (SteamId, SessionTime, LastLoggedInAt, UserSessionStartedAt)
20+
VALUES (@SteamId, @SessionTime, @LastLoggedInAt, @UserSessionStartedAt)
21+
ON DUPLICATE KEY UPDATE
22+
SessionTime = @SessionTime,
23+
LastLoggedInAt = @LastLoggedInAt,
24+
UserSessionStartedAt = @UserSessionStartedAt";
25+
26+
public string GetIncrementSessionTimeSql() =>
27+
$"UPDATE {TableName} SET SessionTime = SessionTime + 1, LastLoggedInAt = @LastLoggedInAt WHERE SteamId = @SteamId";
28+
29+
public string GetResetSessionTimeSql() =>
30+
$"UPDATE {TableName} SET SessionTime = 0 WHERE SteamId = @SteamId";
31+
32+
public string GetResetSessionTimeWithStartTimeSql() =>
33+
$"UPDATE {TableName} SET SessionTime = 0, UserSessionStartedAt = @UserSessionStartedAt WHERE SteamId = @SteamId";
34+
35+
public string GetUserInfoBySteamIdSql() =>
36+
$"SELECT * FROM {TableName} WHERE SteamId = @SteamId";
37+
}
38+

MapChooserSharp/Modules/McsDatabase/Repositories/SqlProviders/PostgreSql/PostgreSqlProvider.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ internal sealed class PostgreSqlProvider(string tableName) : IMcsSqlQueryProvide
1515

1616
public IMcsGroupSqlQueries GroupSqlQueries() => _mcsGroupSqlQueries;
1717

18+
private readonly IMcsUserInformationSqlQueries _mcsUserInformationSqlQueries = new PostgreSqlUserInformationSqlQueries(tableName);
19+
20+
public IMcsUserInformationSqlQueries UserInfoSqlQueries() => _mcsUserInformationSqlQueries;
21+
1822
public IDbConnection CreateConnection(string connectionString) =>
1923
new NpgsqlConnection(connectionString);
2024
}

0 commit comments

Comments
 (0)