Skip to content

Commit 6371d8d

Browse files
authored
Add reusable IV lists for Pokemon IV instances (#22)
* Add reusable IV lists for Pokemon IV instances * Fix typo * Add delete IV lists * Fix error with IV list not assigned yet * Update settings page * version bump
1 parent c698d6e commit 6371d8d

19 files changed

Lines changed: 461 additions & 33 deletions

migrations/3.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CREATE TABLE `iv_list` (
2+
`name` varchar(50) NOT NULL,
3+
`pokemon_ids` longtext NOT NULL,
4+
PRIMARY KEY (`name`)
5+
);

src/Chuck.Infrastructure/Data/Contexts/DeviceControllerContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public DeviceControllerContext(DbContextOptions<DeviceControllerContext> options
4747

4848
public DbSet<Webhook> Webhooks { get; set; }
4949

50+
public DbSet<IVList> IVLists { get; set; }
51+
5052
public DbSet<Metadata> Metadata { get; set; }
5153

5254
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -84,6 +86,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
8486
modelBuilder.Entity<DeviceGroup>()
8587
.Property(nameof(DeviceGroup.Devices))
8688
.HasConversion(DbContextFactory.CreateJsonValueConverter<List<string>>());
89+
modelBuilder.Entity<IVList>()
90+
.Property(nameof(IVList.PokemonIDs))
91+
.HasConversion(DbContextFactory.CreateJsonValueConverter<List<uint>>());
8792

8893
base.OnModelCreating(modelBuilder);
8994
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace Chuck.Infrastructure.Data.Entities
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.ComponentModel.DataAnnotations;
6+
using System.ComponentModel.DataAnnotations.Schema;
7+
using System.Text.Json.Serialization;
8+
9+
using Chuck.Infrastructure.Data.Interfaces;
10+
11+
[Table("iv_list")]
12+
public class IVList : BaseEntity, IAggregateRoot
13+
{
14+
[
15+
Column("name"),
16+
Key,
17+
DatabaseGenerated(DatabaseGeneratedOption.None),
18+
JsonPropertyName("name"),
19+
]
20+
public string Name { get; set; }
21+
22+
[
23+
Column("pokemon_ids"),
24+
JsonPropertyName("pokemon_ids"),
25+
]
26+
public List<uint> PokemonIDs { get; set; }
27+
}
28+
}

src/Chuck.Infrastructure/Data/Entities/InstanceData.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public class InstanceData
2929
[JsonPropertyName("iv_queue_limit")]
3030
public ushort? IVQueueLimit { get; set; }
3131

32-
[JsonPropertyName("pokemon_ids")]
33-
public List<uint> PokemonIds { get; set; }
32+
[JsonPropertyName("iv_list")]
33+
public string IVList { get; set; }
3434

3535
[JsonPropertyName("spin_limit")]
3636
public ushort? SpinLimit { get; set; }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Chuck.Infrastructure.Data.Repositories
2+
{
3+
using Chuck.Infrastructure.Data.Contexts;
4+
using Chuck.Infrastructure.Data.Entities;
5+
6+
public class IVListRepository : EfCoreRepository<IVList, DeviceControllerContext>
7+
{
8+
public IVListRepository(DeviceControllerContext context)
9+
: base(context)
10+
{
11+
}
12+
}
13+
}

src/ChuckDeviceController/ChuckDeviceController.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<Authors>versx's Projects</Authors>
2323
<Description />
2424
<Copyright>Copyright © 2021</Copyright>
25-
<Version>0.7.0.0</Version>
25+
<Version>0.8.0.0</Version>
2626
<PackageProjectUrl>https://github.com/versx/ChuckDeviceController</PackageProjectUrl>
2727
<RepositoryUrl>https://github.com/versx/ChuckDeviceController</RepositoryUrl>
2828
<RepositoryType>git</RepositoryType>

src/ChuckDeviceController/Controllers/ApiController.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class ApiController : ControllerBase
2626
private readonly GeofenceRepository _geofenceRepository;
2727
private readonly WebhookRepository _webhookRepository;
2828
private readonly DeviceGroupRepository _deviceGroupRepository;
29+
private readonly IVListRepository _ivListRepository;
2930

3031
// Dependency injection variables
3132
private readonly DeviceControllerContext _context;
@@ -45,12 +46,14 @@ public ApiController(DeviceControllerContext context, ILogger<DeviceController>
4546
_context = context;
4647
_logger = logger;
4748

49+
// TODO: Maybe use the DbContextFactory instead of relying on the same one per repo
4850
_deviceRepository = new DeviceRepository(_context);
4951
_instanceRepository = new InstanceRepository(_context);
5052
_assignmentRepository = new AssignmentRepository(_context);
5153
_geofenceRepository = new GeofenceRepository(_context);
5254
_webhookRepository = new WebhookRepository(_context);
5355
_deviceGroupRepository = new DeviceGroupRepository(_context);
56+
_ivListRepository = new IVListRepository(_context);
5457
}
5558

5659
#endregion
@@ -254,12 +257,33 @@ public async Task<dynamic> GetWebhooks()
254257
delay = webhook.Delay,
255258
geofence = webhook.Geofence,
256259
enabled = webhook.Enabled ? "Yes" : "No",
257-
buttons = $"<a href='/dashboard/webhook/edit/{webhook.Name}' role='button' class='btn btn-sm btn-primary'>Edit</a>",
260+
buttons = $"<a href='/dashboard/webhook/edit/{Uri.EscapeDataString(webhook.Name)}' role='button' class='btn btn-sm btn-primary'>Edit</a>",
258261
});
259262
}
260263
return new { data = new { webhooks = list } };
261264
}
262265

266+
[
267+
HttpPost("/api/ivlists"),
268+
Produces("application/json"),
269+
]
270+
public async Task<dynamic> GetIVLists()
271+
{
272+
var ivLists = await _ivListRepository.GetAllAsync().ConfigureAwait(false);
273+
var list = new List<dynamic>();
274+
foreach (var ivList in ivLists)
275+
{
276+
list.Add(new
277+
{
278+
name = ivList.Name,
279+
pokemon_list_count = ivList.PokemonIDs.Count.ToString("N0"),
280+
buttons = $"<a href='/dashboard/ivlist/edit/{Uri.EscapeDataString(ivList.Name)}' role='button' class='btn btn-sm btn-primary'>Edit</a>",
281+
// TODO: Delete button
282+
});
283+
}
284+
return new { data = new { ivlists = list } };
285+
}
286+
263287
/// <summary>
264288
/// Get queue of IV instance
265289
/// </summary>

src/ChuckDeviceController/Controllers/DashboardController.cs

Lines changed: 147 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public class DashboardController : Controller
4343
private readonly PokestopRepository _pokestopRepository;
4444
private readonly GeofenceRepository _geofenceRepository;
4545
private readonly WebhookRepository _webhookRepository;
46-
4746
private readonly DeviceGroupRepository _deviceGroupRepository;
47+
private readonly IVListRepository _ivListRepository;
4848

4949
#endregion
5050

@@ -67,6 +67,7 @@ public DashboardController(DeviceControllerContext context, IConnectionMultiplex
6767
_geofenceRepository = new GeofenceRepository(_context);
6868
_webhookRepository = new WebhookRepository(_context);
6969
_deviceGroupRepository = new DeviceGroupRepository(_context);
70+
_ivListRepository = new IVListRepository(_context);
7071
}
7172

7273
#endregion
@@ -395,6 +396,12 @@ public async Task<IActionResult> AddInstance()
395396
});
396397
obj.circle_size = 70;
397398
obj.nothing_selected = true;
399+
var ivLists = await _ivListRepository.GetAllAsync().ConfigureAwait(false);
400+
obj.iv_lists = ivLists.Select(x => new
401+
{
402+
name = x.Name,
403+
selected = false,
404+
});
398405
obj.iv_queue_limit = 100;
399406
obj.spin_limit = 3500;
400407
obj.quest_retry_limit = 5;
@@ -424,10 +431,7 @@ public async Task<IActionResult> AddInstance()
424431
var circleSize = Request.Form.ContainsKey("circle_size")
425432
? ushort.Parse(Request.Form["circle_size"].ToString() ?? "70")
426433
: 70;
427-
var pokemonIdsValue = Request.Form["pokemon_ids"].ToString();
428-
var pokemonIds = pokemonIdsValue == "*"
429-
? Enumerable.Range(1, 999).Select(x => (uint)x).ToList()
430-
: pokemonIdsValue?.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries)?.Select(uint.Parse).ToList();
434+
var ivList = Request.Form["iv_list"].ToString();
431435
var fastBootstrapMode = Request.Form["fast_bootstrap_mode"].ToString() == "on";
432436
var accountGroup = Request.Form["account_group"].ToString();
433437
var isEvent = Request.Form["is_event"].ToString() == "on";
@@ -458,7 +462,7 @@ public async Task<IActionResult> AddInstance()
458462
}
459463
}
460464

461-
if (minLevel > maxLevel || minLevel == 0 || minLevel > 40 || maxLevel == 0 || maxLevel > 40)
465+
if (minLevel > maxLevel || minLevel < 0 || minLevel > 40 || maxLevel < 0 || maxLevel > 40)
462466
{
463467
// Invalid levels
464468
return BuildErrorResponse("instance-add", "Invalid minimum and maximum levels provided");
@@ -482,7 +486,7 @@ public async Task<IActionResult> AddInstance()
482486
{
483487
IVQueueLimit = ivQueueLimit,
484488
SpinLimit = spinLimit,
485-
PokemonIds = pokemonIds,
489+
IVList = ivList,
486490
Timezone = timezone,
487491
EnableDst = enableDst,
488492
CircleRouteType = circleRouteType,
@@ -552,7 +556,13 @@ public async Task<IActionResult> EditInstance(string name)
552556
obj.circle_route_type = instance.Data.CircleRouteType;
553557
// break;
554558
// case InstanceType.PokemonIV:
555-
obj.pokemon_ids = instance.Data.PokemonIds == null ? null : string.Join("\n", instance.Data.PokemonIds);
559+
//obj.pokemon_ids = instance.Data.PokemonIds == null ? null : string.Join("\n", instance.Data.PokemonIds);
560+
var ivLists = await _ivListRepository.GetAllAsync().ConfigureAwait(false);
561+
obj.iv_lists = ivLists.Select(x => new
562+
{
563+
name = x.Name,
564+
selected = string.Compare(instance.Data.IVList, x.Name, true) == 0,
565+
});
556566
obj.iv_queue_limit = instance.Data.IVQueueLimit > 0 ? instance.Data.IVQueueLimit : 100;
557567
// break;
558568
// case InstanceType.AutoQuest:
@@ -608,18 +618,15 @@ public async Task<IActionResult> EditInstance(string name)
608618
var circleRouteType = Request.Form.ContainsKey("circle_route_type")
609619
? StringToCircleRouteType(Request.Form["circle_route_type"].ToString())
610620
: CircleRouteType.Default;
611-
var pokemonIdsValue = Request.Form["pokemon_ids"].ToString();
612-
var pokemonIds = pokemonIdsValue == "*"
613-
? Enumerable.Range(1, 999).Select(x => (uint)x).ToList()
614-
: pokemonIdsValue?.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries)?.Select(uint.Parse).ToList();
621+
var ivList = Request.Form["iv_list"].ToString();
615622
var ivQueueLimit = ushort.Parse(Request.Form["iv_queue_limit"]);
616623
var spinLimit = ushort.Parse(Request.Form["spin_limit"]);
617624
var fastBootstrapMode = Request.Form["fast_bootstrap_mode"].ToString() == "on";
618625
var questRetryLimit = byte.Parse(Request.Form["quest_retry_limit"].ToString());
619626
var accountGroup = Request.Form["account_group"].ToString();
620627
var isEvent = Request.Form["is_event"].ToString() == "on";
621628
var enableDst = Request.Form["enable_dst"].ToString() == "on";
622-
if (minLevel > maxLevel || minLevel == 0 || minLevel > 40 || maxLevel == 0 || maxLevel > 40)
629+
if (minLevel > maxLevel || minLevel < 0 || minLevel > 40 || maxLevel < 0 || maxLevel > 40)
623630
{
624631
// Invalid levels
625632
return BuildErrorResponse("instance-edit", "Invalid minimum and maximum levels provided");
@@ -647,7 +654,7 @@ public async Task<IActionResult> EditInstance(string name)
647654
CircleRouteType = circleRouteType,
648655
CircleSize = circleSize,
649656
SpinLimit = spinLimit,
650-
PokemonIds = pokemonIds,
657+
IVList = ivList,
651658
Timezone = timezone,
652659
EnableDst = enableDst,
653660
FastBootstrapMode = fastBootstrapMode,
@@ -1413,6 +1420,132 @@ public async Task<IActionResult> EditWebhook(string name)
14131420

14141421
#endregion
14151422

1423+
#region IV Lists
1424+
1425+
[HttpGet("/dashboard/ivlists")]
1426+
public IActionResult GetIVLists()
1427+
{
1428+
var obj = BuildDefaultData();
1429+
var data = TemplateRenderer.ParseTemplate("ivlists", obj);
1430+
return new ContentResult
1431+
{
1432+
Content = data,
1433+
ContentType = "text/html",
1434+
StatusCode = 200,
1435+
};
1436+
}
1437+
1438+
[
1439+
HttpGet("/dashboard/ivlist/add"),
1440+
HttpPost("/dashboard/ivlist/add"),
1441+
]
1442+
public async Task<IActionResult> AddIVList()
1443+
{
1444+
if (Request.Method == "GET")
1445+
{
1446+
dynamic obj = BuildDefaultData();
1447+
var data = TemplateRenderer.ParseTemplate("ivlist-add", obj);
1448+
return new ContentResult
1449+
{
1450+
Content = data,
1451+
ContentType = "text/html",
1452+
StatusCode = 200,
1453+
};
1454+
}
1455+
else
1456+
{
1457+
var name = Request.Form["name"].ToString();
1458+
var pokemonIdsValue = Request.Form["pokemon_ids"].ToString();
1459+
var pokemonIds = pokemonIdsValue == "*"
1460+
? Enumerable.Range(1, 999).Select(x => (uint)x).ToList()
1461+
: pokemonIdsValue?.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries)?
1462+
.Select(uint.Parse)
1463+
.ToList();
1464+
1465+
var ivList = await _ivListRepository.GetByIdAsync(name).ConfigureAwait(false);
1466+
if (ivList != null)
1467+
{
1468+
// Already exists
1469+
return BuildErrorResponse("ivlist-add", $"IV List with name '{name}' already exists");
1470+
}
1471+
ivList = new IVList
1472+
{
1473+
Name = name,
1474+
PokemonIDs = pokemonIds,
1475+
};
1476+
await _ivListRepository.AddOrUpdateAsync(ivList);
1477+
await IVListController.Instance.Reload();
1478+
return Redirect("/dashboard/ivlists");
1479+
}
1480+
}
1481+
1482+
[
1483+
HttpGet("/dashboard/ivlist/edit/{name}"),
1484+
HttpPost("/dashboard/ivlist/edit/{name}"),
1485+
]
1486+
public async Task<IActionResult> EditIVList(string name)
1487+
{
1488+
if (Request.Method == "GET")
1489+
{
1490+
var ivList = await _ivListRepository.GetByIdAsync(name);
1491+
if (ivList == null)
1492+
{
1493+
// Failed to get IV list by name
1494+
return Redirect("/dashboard/ivlists");
1495+
}
1496+
dynamic obj = BuildDefaultData();
1497+
obj.name = ivList.Name;
1498+
obj.old_name = ivList.Name;
1499+
obj.pokemon_ids = string.Join("\n", ivList.PokemonIDs);
1500+
var data = TemplateRenderer.ParseTemplate("ivlist-edit", obj);
1501+
return new ContentResult
1502+
{
1503+
Content = data,
1504+
ContentType = "text/html",
1505+
StatusCode = 200,
1506+
};
1507+
}
1508+
else
1509+
{
1510+
if (Request.Form.ContainsKey("delete"))
1511+
{
1512+
var ivListToDelete = await _ivListRepository.GetByIdAsync(name).ConfigureAwait(false);
1513+
if (ivListToDelete != null)
1514+
{
1515+
await _ivListRepository.DeleteAsync(ivListToDelete).ConfigureAwait(false);
1516+
await IVListController.Instance.Reload().ConfigureAwait(false);
1517+
_logger.LogDebug($"IV list {name} was deleted");
1518+
}
1519+
return Redirect("/dashboard/ivlists");
1520+
}
1521+
1522+
var newName = Request.Form["name"].ToString();
1523+
var oldName = Request.Form["old_name"].ToString();
1524+
var pokemonIdsValue = Request.Form["pokemon_ids"].ToString();
1525+
var pokemonIds = pokemonIdsValue == "*"
1526+
? Enumerable.Range(1, 999).Select(x => (uint)x).ToList()
1527+
: pokemonIdsValue?.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries)?
1528+
.Select(uint.Parse)
1529+
.ToList();
1530+
var ivList = await _ivListRepository.GetByIdAsync(oldName).ConfigureAwait(false);
1531+
if (ivList == null)
1532+
{
1533+
// Does not exist
1534+
return BuildErrorResponse("ivlist-edit", $"IV List with name '{name}' does not exist");
1535+
}
1536+
ivList = new IVList
1537+
{
1538+
Name = oldName,
1539+
PokemonIDs = pokemonIds,
1540+
};
1541+
await _ivListRepository.AddOrUpdateAsync(ivList);
1542+
await IVListController.Instance.Reload();
1543+
return Redirect("/dashboard/ivlists");
1544+
}
1545+
}
1546+
1547+
#endregion
1548+
14161549
#region Accounts
14171550

14181551
[HttpGet("/dashboard/accounts")]

0 commit comments

Comments
 (0)