Compare commits
10 Commits
1a08f932af
...
master
Author | SHA1 | Date | |
---|---|---|---|
c2b1327286 | |||
92397eea18 | |||
227346e096 | |||
e11390f632 | |||
89a0dc1bc2 | |||
25e4b01c84 | |||
7f7817bb64 | |||
86642b5d28 | |||
59ade47bd7 | |||
4405c0349e |
@ -5,6 +5,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -16,6 +18,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="8.1.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="8.1.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="8.1.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.2" />
|
||||
|
11
API/Api.cs
11
API/Api.cs
@ -1,5 +1,6 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.OpenApi;
|
||||
using SQLiteEF;
|
||||
|
||||
@ -22,7 +23,12 @@ public class Api
|
||||
});
|
||||
builder.Services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod()));
|
||||
|
||||
builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddSwaggerGenNewtonsoftSupport();
|
||||
builder.Services.AddSwaggerGen(opt =>
|
||||
{
|
||||
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
opt.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
|
||||
});
|
||||
|
||||
builder.Services.AddDbContext<Context>(options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"), optionsBuilder => optionsBuilder.MigrationsAssembly(Assembly.GetAssembly(GetType())!)));
|
||||
|
||||
@ -44,8 +50,9 @@ public class Api
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
|
||||
app.Services.CreateScope().ServiceProvider.GetRequiredService<Context>().Database.Migrate();
|
||||
_tracker = app.Services.GetRequiredService<Tracker>();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
|
@ -28,6 +28,26 @@ public class ActionsController(Context databaseContext) : ApiController(typeof(A
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("Player/{steamId}")]
|
||||
[ProducesResponseType<Player>(Status202Accepted)]
|
||||
[ProducesResponseType<string>(Status500InternalServerError)]
|
||||
public IActionResult DeletePlayer(ulong steamId)
|
||||
{
|
||||
if (databaseContext.Players.Find(steamId) is not { } player)
|
||||
return NotFound();
|
||||
try
|
||||
{
|
||||
databaseContext.Players.Remove(player);
|
||||
databaseContext.SaveChanges();
|
||||
return Accepted();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
return StatusCode(Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Update/Player/{steamId}/All")]
|
||||
[ProducesResponseType(Status200OK)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
@ -35,6 +55,7 @@ public class ActionsController(Context databaseContext) : ApiController(typeof(A
|
||||
{
|
||||
if (databaseContext.Players.Find(steamId) is not { } player)
|
||||
return NotFound();
|
||||
|
||||
tracker.UpdatePlayer(databaseContext, player);
|
||||
tracker.UpdateOwnedGamesPlayer(databaseContext, player);
|
||||
tracker.UpdateGameTimesPlayer(databaseContext, player);
|
||||
|
@ -8,6 +8,22 @@ namespace API.Controllers;
|
||||
[Route("[controller]")]
|
||||
public class TimeTrackController(Context databaseContext) : ApiController(typeof(TimeTrackController))
|
||||
{
|
||||
|
||||
[HttpGet("{steamId}")]
|
||||
[ProducesResponseType<KeyValuePair<ulong, TrackedTime[]>[]>(Status200OK)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetTrackedTime(ulong steamId)
|
||||
{
|
||||
if (databaseContext.Players.Find(steamId) is not { } player)
|
||||
return NotFound();
|
||||
databaseContext.Entry(player).Collection(p => p.TrackedTimes).Load();
|
||||
KeyValuePair<ulong, TrackedTime[]>[] ret = player.TrackedTimes
|
||||
.GroupBy(t => t.Game)
|
||||
.Select(t => new KeyValuePair<ulong, TrackedTime[]>(t.Key.AppId, t.ToArray()))
|
||||
.ToArray();
|
||||
return Ok(ret);
|
||||
}
|
||||
|
||||
[HttpGet("{steamId}/{appId}")]
|
||||
[ProducesResponseType<TrackedTime[]>(Status200OK)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
@ -38,17 +54,33 @@ public class TimeTrackController(Context databaseContext) : ApiController(typeof
|
||||
return Ok(sum);
|
||||
}
|
||||
|
||||
[HttpGet("{steamId}/PerGame")]
|
||||
[ProducesResponseType<Dictionary<ulong, ulong>>(Status200OK)]
|
||||
[HttpGet("{steamId}/Total/{appId}")]
|
||||
[ProducesResponseType<ulong>(Status200OK)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetTrackedTimeAll(ulong steamId, ulong appId)
|
||||
{
|
||||
if (databaseContext.Players.Find(steamId) is not { } player)
|
||||
return NotFound();
|
||||
if (databaseContext.Games.Find(appId) is not { } game)
|
||||
return NotFound();
|
||||
databaseContext.Entry(player).Collection(p => p.TrackedTimes).Load();
|
||||
ulong? maxTime = player.TrackedTimes.Where(t => t.Game == game).MaxBy(t => t.TimePlayed)?.TimePlayed;
|
||||
|
||||
return maxTime is not null ? Ok(maxTime) : NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("{steamId}/Total/PerGame")]
|
||||
[ProducesResponseType<KeyValuePair<ulong, ulong>[]>(Status200OK)]
|
||||
[ProducesResponseType(Status404NotFound)]
|
||||
public IActionResult GetTrackedTimeAll(ulong steamId)
|
||||
{
|
||||
if (databaseContext.Players.Find(steamId) is not { } player)
|
||||
return NotFound();
|
||||
databaseContext.Entry(player).Collection(p => p.TrackedTimes).Load();
|
||||
Dictionary<ulong, ulong> trackedTimes = player.TrackedTimes
|
||||
KeyValuePair<ulong, ulong>[] trackedTimes = player.TrackedTimes
|
||||
.GroupBy(t => t.Game)
|
||||
.ToDictionary(t => t.Key.AppId, t => t.MaxBy(time => time.TimePlayed)?.TimePlayed??0);
|
||||
.Select(t => new KeyValuePair<ulong, ulong>(t.Key.AppId, t.MaxBy(time => time.TimePlayed)?.TimePlayed ?? 0))
|
||||
.ToArray();
|
||||
|
||||
return Ok(trackedTimes);
|
||||
}
|
||||
|
148
API/Migrations/20250525221352_Initial.Designer.cs
generated
Normal file
148
API/Migrations/20250525221352_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,148 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using SQLiteEF;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
[DbContext(typeof(Context))]
|
||||
[Migration("20250525221352_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.5");
|
||||
|
||||
modelBuilder.Entity("GamePlayer", b =>
|
||||
{
|
||||
b.Property<ulong>("GamesAppId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("PlayedBySteamId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("GamesAppId", "PlayedBySteamId");
|
||||
|
||||
b.HasIndex("PlayedBySteamId");
|
||||
|
||||
b.ToTable("GamePlayer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Game", b =>
|
||||
{
|
||||
b.Property<ulong>("AppId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("AppId");
|
||||
|
||||
b.ToTable("Games");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Player", b =>
|
||||
{
|
||||
b.Property<ulong>("SteamId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AvatarUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProfileUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SteamId");
|
||||
|
||||
b.ToTable("Players");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.TrackedTime", b =>
|
||||
{
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GameAppId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("PlayerSteamId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("TimePlayed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("TimeStamp");
|
||||
|
||||
b.HasIndex("GameAppId");
|
||||
|
||||
b.HasIndex("PlayerSteamId");
|
||||
|
||||
b.ToTable("TrackedTime");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GamePlayer", b =>
|
||||
{
|
||||
b.HasOne("SQLiteEF.Game", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GamesAppId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SQLiteEF.Player", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PlayedBySteamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.TrackedTime", b =>
|
||||
{
|
||||
b.HasOne("SQLiteEF.Game", "Game")
|
||||
.WithMany("TrackedTimes")
|
||||
.HasForeignKey("GameAppId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SQLiteEF.Player", "Player")
|
||||
.WithMany("TrackedTimes")
|
||||
.HasForeignKey("PlayerSteamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Game");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Game", b =>
|
||||
{
|
||||
b.Navigation("TrackedTimes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Player", b =>
|
||||
{
|
||||
b.Navigation("TrackedTimes");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
125
API/Migrations/20250525221352_Initial.cs
Normal file
125
API/Migrations/20250525221352_Initial.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Games",
|
||||
columns: table => new
|
||||
{
|
||||
AppId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Games", x => x.AppId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Players",
|
||||
columns: table => new
|
||||
{
|
||||
SteamId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ProfileUrl = table.Column<string>(type: "TEXT", nullable: false),
|
||||
AvatarUrl = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Players", x => x.SteamId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GamePlayer",
|
||||
columns: table => new
|
||||
{
|
||||
GamesAppId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
PlayedBySteamId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GamePlayer", x => new { x.GamesAppId, x.PlayedBySteamId });
|
||||
table.ForeignKey(
|
||||
name: "FK_GamePlayer_Games_GamesAppId",
|
||||
column: x => x.GamesAppId,
|
||||
principalTable: "Games",
|
||||
principalColumn: "AppId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_GamePlayer_Players_PlayedBySteamId",
|
||||
column: x => x.PlayedBySteamId,
|
||||
principalTable: "Players",
|
||||
principalColumn: "SteamId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TrackedTime",
|
||||
columns: table => new
|
||||
{
|
||||
TimeStamp = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
GameAppId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
PlayerSteamId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
TimePlayed = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TrackedTime", x => x.TimeStamp);
|
||||
table.ForeignKey(
|
||||
name: "FK_TrackedTime_Games_GameAppId",
|
||||
column: x => x.GameAppId,
|
||||
principalTable: "Games",
|
||||
principalColumn: "AppId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_TrackedTime_Players_PlayerSteamId",
|
||||
column: x => x.PlayerSteamId,
|
||||
principalTable: "Players",
|
||||
principalColumn: "SteamId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GamePlayer_PlayedBySteamId",
|
||||
table: "GamePlayer",
|
||||
column: "PlayedBySteamId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TrackedTime_GameAppId",
|
||||
table: "TrackedTime",
|
||||
column: "GameAppId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TrackedTime_PlayerSteamId",
|
||||
table: "TrackedTime",
|
||||
column: "PlayerSteamId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GamePlayer");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TrackedTime");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Games");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Players");
|
||||
}
|
||||
}
|
||||
}
|
154
API/Migrations/20250526144942_Game-Logo-and-Icon.Designer.cs
generated
Normal file
154
API/Migrations/20250526144942_Game-Logo-and-Icon.Designer.cs
generated
Normal file
@ -0,0 +1,154 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using SQLiteEF;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
[DbContext(typeof(Context))]
|
||||
[Migration("20250526144942_Game-Logo-and-Icon")]
|
||||
partial class GameLogoandIcon
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.5");
|
||||
|
||||
modelBuilder.Entity("GamePlayer", b =>
|
||||
{
|
||||
b.Property<ulong>("GamesAppId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("PlayedBySteamId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("GamesAppId", "PlayedBySteamId");
|
||||
|
||||
b.HasIndex("PlayedBySteamId");
|
||||
|
||||
b.ToTable("GamePlayer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Game", b =>
|
||||
{
|
||||
b.Property<ulong>("AppId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("IconUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LogoUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("AppId");
|
||||
|
||||
b.ToTable("Games");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Player", b =>
|
||||
{
|
||||
b.Property<ulong>("SteamId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AvatarUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProfileUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SteamId");
|
||||
|
||||
b.ToTable("Players");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.TrackedTime", b =>
|
||||
{
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GameAppId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("PlayerSteamId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("TimePlayed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("TimeStamp");
|
||||
|
||||
b.HasIndex("GameAppId");
|
||||
|
||||
b.HasIndex("PlayerSteamId");
|
||||
|
||||
b.ToTable("TrackedTime");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GamePlayer", b =>
|
||||
{
|
||||
b.HasOne("SQLiteEF.Game", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GamesAppId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SQLiteEF.Player", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PlayedBySteamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.TrackedTime", b =>
|
||||
{
|
||||
b.HasOne("SQLiteEF.Game", "Game")
|
||||
.WithMany("TrackedTimes")
|
||||
.HasForeignKey("GameAppId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SQLiteEF.Player", "Player")
|
||||
.WithMany("TrackedTimes")
|
||||
.HasForeignKey("PlayerSteamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Game");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Game", b =>
|
||||
{
|
||||
b.Navigation("TrackedTimes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Player", b =>
|
||||
{
|
||||
b.Navigation("TrackedTimes");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
38
API/Migrations/20250526144942_Game-Logo-and-Icon.cs
Normal file
38
API/Migrations/20250526144942_Game-Logo-and-Icon.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class GameLogoandIcon : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "IconUrl",
|
||||
table: "Games",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "LogoUrl",
|
||||
table: "Games",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IconUrl",
|
||||
table: "Games");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LogoUrl",
|
||||
table: "Games");
|
||||
}
|
||||
}
|
||||
}
|
151
API/Migrations/ContextModelSnapshot.cs
Normal file
151
API/Migrations/ContextModelSnapshot.cs
Normal file
@ -0,0 +1,151 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using SQLiteEF;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
[DbContext(typeof(Context))]
|
||||
partial class ContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.5");
|
||||
|
||||
modelBuilder.Entity("GamePlayer", b =>
|
||||
{
|
||||
b.Property<ulong>("GamesAppId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("PlayedBySteamId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("GamesAppId", "PlayedBySteamId");
|
||||
|
||||
b.HasIndex("PlayedBySteamId");
|
||||
|
||||
b.ToTable("GamePlayer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Game", b =>
|
||||
{
|
||||
b.Property<ulong>("AppId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("IconUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LogoUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("AppId");
|
||||
|
||||
b.ToTable("Games");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Player", b =>
|
||||
{
|
||||
b.Property<ulong>("SteamId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AvatarUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProfileUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SteamId");
|
||||
|
||||
b.ToTable("Players");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.TrackedTime", b =>
|
||||
{
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GameAppId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("PlayerSteamId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("TimePlayed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("TimeStamp");
|
||||
|
||||
b.HasIndex("GameAppId");
|
||||
|
||||
b.HasIndex("PlayerSteamId");
|
||||
|
||||
b.ToTable("TrackedTime");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GamePlayer", b =>
|
||||
{
|
||||
b.HasOne("SQLiteEF.Game", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GamesAppId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SQLiteEF.Player", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PlayedBySteamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.TrackedTime", b =>
|
||||
{
|
||||
b.HasOne("SQLiteEF.Game", "Game")
|
||||
.WithMany("TrackedTimes")
|
||||
.HasForeignKey("GameAppId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("SQLiteEF.Player", "Player")
|
||||
.WithMany("TrackedTimes")
|
||||
.HasForeignKey("PlayerSteamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Game");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Game", b =>
|
||||
{
|
||||
b.Navigation("TrackedTimes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("SQLiteEF.Player", b =>
|
||||
{
|
||||
b.Navigation("TrackedTimes");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -86,11 +86,23 @@ public class Tracker : IDisposable
|
||||
SteamGame[] ownedGames = Steam.GetOwnedGames(player.SteamId);
|
||||
foreach (SteamGame ownedGame in ownedGames)
|
||||
{
|
||||
string? iconUrlStr = ownedGame.img_icon_url is not null
|
||||
? $"http://media.steampowered.com/steamcommunity/public/images/apps/{ownedGame.appid}/{ownedGame.img_icon_url}.jpg"
|
||||
: null;
|
||||
string? logoUrlStr = ownedGame.img_logo_url is not null
|
||||
? $"http://media.steampowered.com/steamcommunity/public/images/apps/{ownedGame.appid}/{ownedGame.img_logo_url}.jpg"
|
||||
: null;
|
||||
if (context.Games.Find(ownedGame.appid) is not { } game)
|
||||
{
|
||||
game = new(ownedGame.appid, ownedGame.name);
|
||||
game = new(ownedGame.appid, ownedGame.name, iconUrlStr, logoUrlStr);
|
||||
context.Games.Add(game);
|
||||
}
|
||||
else
|
||||
{
|
||||
game.Name = ownedGame.name;
|
||||
game.IconUrl = iconUrlStr;
|
||||
game.LogoUrl = logoUrlStr;
|
||||
}
|
||||
|
||||
if (!player.Games.Contains(game))
|
||||
{
|
||||
@ -127,13 +139,27 @@ public class Tracker : IDisposable
|
||||
{
|
||||
Log.Debug($"Updating game times for player {player}");
|
||||
GetRecentlyPlayedGames recentlyPlayed = Steam.GetRecentlyPlayedGames(player.SteamId);
|
||||
if (recentlyPlayed.total_count < 1)
|
||||
return;
|
||||
foreach (SteamGame recentlyPlayedGame in recentlyPlayed.games)
|
||||
{
|
||||
string? iconUrlStr = recentlyPlayedGame.img_icon_url is not null
|
||||
? $"http://media.steampowered.com/steamcommunity/public/images/apps/{recentlyPlayedGame.appid}/{recentlyPlayedGame.img_icon_url}.jpg"
|
||||
: null;
|
||||
string? logoUrlStr = recentlyPlayedGame.img_logo_url is not null
|
||||
? $"http://media.steampowered.com/steamcommunity/public/images/apps/{recentlyPlayedGame.appid}/{recentlyPlayedGame.img_logo_url}.jpg"
|
||||
: null;
|
||||
if (context.Games.Find(recentlyPlayedGame.appid) is not { } game)
|
||||
{
|
||||
game = new(recentlyPlayedGame.appid, recentlyPlayedGame.name);
|
||||
game = new(recentlyPlayedGame.appid, recentlyPlayedGame.name, iconUrlStr, logoUrlStr);
|
||||
context.Games.Add(game);
|
||||
}
|
||||
else
|
||||
{
|
||||
game.Name = recentlyPlayedGame.name;
|
||||
game.IconUrl = iconUrlStr;
|
||||
game.LogoUrl = logoUrlStr;
|
||||
}
|
||||
|
||||
if (!player.Games.Contains(game))
|
||||
{
|
||||
|
@ -4,10 +4,12 @@ using Newtonsoft.Json;
|
||||
namespace SQLiteEF;
|
||||
|
||||
[PrimaryKey("AppId")]
|
||||
public class Game(ulong appId, string name)
|
||||
public class Game(ulong appId, string name, string? iconUrl, string? logoUrl)
|
||||
{
|
||||
public ulong AppId { get; init; } = appId;
|
||||
public string Name { get; init; } = name;
|
||||
public string Name { get; set; } = name;
|
||||
public string? IconUrl { get; set; } = iconUrl;
|
||||
public string? LogoUrl { get; set; } = logoUrl;
|
||||
[JsonIgnore] public ICollection<Player> PlayedBy { get; init; } = null!;
|
||||
[JsonIgnore] public ICollection<TrackedTime> TrackedTimes { get; init; } = null!;
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
namespace SteamApiWrapper.ReturnTypes;
|
||||
|
||||
public record Game(ulong appid, ulong playtime_forever, string name);
|
||||
public record Game(ulong appid, ulong playtime_forever, string name, string? img_icon_url, string? img_logo_url);
|
10
SteamGameTimeTrack.sln.DotSettings.user
Normal file
10
SteamGameTimeTrack.sln.DotSettings.user
Normal file
@ -0,0 +1,10 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACallSiteValidator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F223f3993d7b717137d1eac7a1da6b5a458d1d124b0f8a62a3450f9bd6c89af_003FCallSiteValidator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbConnectionStringBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F90c837a1317d49ef94f93ed3285e27ca2bdc00_003F5a_003F4192172b_003FDbConnectionStringBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbContextServices_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc28655223245c8d433d4d4234c31c4b8a4fd871bd6a0e2268c726eb4db0d_003FDbContextServices_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFinder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fd8142acd624a4579e70bd7df6edcf5f73fc8c86908b583b38f17b390476a1f_003FEntityFinder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMigrationCommandExecutor_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fb43021565ba7f13262dd95827ae378ea6015db2c146979398f92c7356d98488_003FMigrationCommandExecutor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARelationalConnection_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F696ea149e41ddc60bdd9238370d01ba5f417a4a91ce8ac42c17d4eee0afa038_003FRelationalConnection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASingleQueryingEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F413341ec7da6e42cb630e52ba9208edacb2e7267da1d9296f51628fcd35e81d9_003FSingleQueryingEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf07be36dfaa4f47b843d44a6617c8b9d19e00_003Fa5_003F6dee0ce4_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AXmlTextReaderImpl_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff3fbda37b1e0423781e129b1ac69751d7a1a00_003F84_003F4c900766_003FXmlTextReaderImpl_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
Reference in New Issue
Block a user