Skip to content

Commit 513c804

Browse files
committed
bulk user shares stuff
1 parent b8a38a2 commit 513c804

15 files changed

Lines changed: 190 additions & 26 deletions

API/Controller/Shares/DeleteShareCode.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using OpenShock.Common.Errors;
66
using OpenShock.Common.Extensions;
77
using OpenShock.Common.Models;
8+
using OpenShock.Common.OpenShockDb;
89
using OpenShock.Common.Problems;
910

1011
namespace OpenShock.API.Controller.Shares;
@@ -23,8 +24,8 @@ public sealed partial class SharesController
2324
[MapToApiVersion("1")]
2425
public async Task<IActionResult> DeleteShareCode([FromRoute] Guid shareCodeId)
2526
{
26-
var affected = await _db.ShockerShareCodes
27-
.Where(x => x.Id == shareCodeId)
27+
var affected = await Queryable
28+
.Where<ShockerShareCode>(_db.ShockerShareCodes, x => x.Id == shareCodeId)
2829
.WhereIsUserOrPrivileged(x => x.Shocker.Device.Owner, CurrentUser)
2930
.ExecuteDeleteAsync();
3031
if (affected <= 0)

API/Controller/Shares/LinkShareCode.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using Asp.Versioning;
33
using Microsoft.AspNetCore.Mvc;
44
using Microsoft.EntityFrameworkCore;
5-
using OpenShock.API.Services;
65
using OpenShock.API.Services.DeviceUpdate;
76
using OpenShock.Common.Errors;
87
using OpenShock.Common.Models;
@@ -32,13 +31,13 @@ public async Task<IActionResult> LinkShareCode(
3231
[FromServices] IDeviceUpdateService deviceUpdateService
3332
)
3433
{
35-
var shareCode = await _db.ShockerShareCodes.Where(x => x.Id == shareCodeId && x.Shocker.Device.Owner.UserDeactivation == null).Select(x => new
34+
var shareCode = await Queryable.Where<ShockerShareCode>(_db.ShockerShareCodes, x => x.Id == shareCodeId && x.Shocker.Device.Owner.UserDeactivation == null).Select(x => new
3635
{
3736
Share = x, x.Shocker.Device.OwnerId, x.Shocker.DeviceId
3837
}).FirstOrDefaultAsync();
3938
if (shareCode is null) return Problem(ShareCodeError.ShareCodeNotFound);
4039
if (shareCode.OwnerId == CurrentUser.Id) return Problem(ShareCodeError.CantLinkOwnShareCode);
41-
if (await _db.UserShares.AnyAsync(x => x.ShockerId == shareCode.Share.ShockerId && x.SharedWithUserId == CurrentUser.Id))
40+
if (await EntityFrameworkQueryableExtensions.AnyAsync<UserShare>(_db.UserShares, x => x.ShockerId == shareCode.Share.ShockerId && x.SharedWithUserId == CurrentUser.Id))
4241
return Problem(ShareCodeError.ShockerAlreadyLinked);
4342

4443
_db.UserShares.Add(new UserShare

API/Controller/Shares/V2CreateShareInvite.cs renamed to API/Controller/Shares/UserShares/CreateShareInvite.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@
88
using OpenShock.Common.Problems;
99
using Z.EntityFramework.Plus;
1010

11-
namespace OpenShock.API.Controller.Shares;
11+
namespace OpenShock.API.Controller.Shares.UserShares;
1212

13-
public sealed partial class SharesController
13+
public sealed partial class UserSharesController
1414
{
1515
[HttpPost("invites")]
1616
[Consumes(MediaTypeNames.Application.Json)]
1717
[ProducesResponseType<Guid>(StatusCodes.Status200OK, MediaTypeNames.Text.Plain)]
1818
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // UserNotFound, ShareCreateShockerNotFound
1919
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status400BadRequest, MediaTypeNames.Application.ProblemJson)] // ShareCreateCannotShareWithSelf
20-
[ApiVersion("2")]
20+
[MapToApiVersion("2")]
2121
public async Task<IActionResult> CreateShareInvite([FromBody] CreateShareRequest body)
2222
{
2323
if (body.User == CurrentUser.Id)

API/Controller/Shares/V2GetShares.cs renamed to API/Controller/Shares/UserShares/GetShares.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
using Microsoft.AspNetCore.Mvc;
33
using Microsoft.EntityFrameworkCore;
44
using OpenShock.API.Models.Response;
5-
using OpenShock.Common.Extensions;
5+
using OpenShock.API.Utils;
66
using OpenShock.Common.Utils;
77
using Z.EntityFramework.Plus;
88

9-
namespace OpenShock.API.Controller.Shares;
9+
namespace OpenShock.API.Controller.Shares.UserShares;
1010

1111
internal sealed class V2UserSharesListItemDto
1212
{
@@ -25,10 +25,10 @@ internal sealed class V2UserSharesListItemDto
2525
};
2626
}
2727

28-
public sealed partial class SharesController
28+
public sealed partial class UserSharesController
2929
{
3030
[HttpGet]
31-
[ApiVersion("2")]
31+
[MapToApiVersion("2")]
3232
public async Task<V2UserShares> GetSharesByUsers(CancellationToken cancellationToken)
3333
{
3434
var outgoingSharesFuture = _db.UserShares
@@ -57,7 +57,7 @@ public async Task<V2UserShares> GetSharesByUsers(CancellationToken cancellationT
5757
Intensity = y.MaxIntensity,
5858
Duration = y.MaxDuration
5959
},
60-
Paused = y.IsPaused
60+
Paused = UserShareUtils.GetPausedReason(y.IsPaused, y.Shocker.IsPaused)
6161
})
6262
.ToArray()
6363
})
@@ -89,7 +89,7 @@ public async Task<V2UserShares> GetSharesByUsers(CancellationToken cancellationT
8989
Duration = y.MaxDuration,
9090
Intensity = y.MaxIntensity
9191
},
92-
Paused = y.IsPaused
92+
Paused = UserShareUtils.GetPausedReason(y.IsPaused, y.Shocker.IsPaused)
9393
})
9494
.ToArray()
9595
})

API/Controller/Shares/V2Invites.cs renamed to API/Controller/Shares/UserShares/Invites.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
using Microsoft.EntityFrameworkCore;
55
using OpenShock.API.Models.Requests;
66
using OpenShock.API.Models.Response;
7-
using OpenShock.API.Services;
87
using OpenShock.API.Services.DeviceUpdate;
8+
using OpenShock.API.Utils;
99
using OpenShock.Common.Errors;
1010
using OpenShock.Common.Extensions;
1111
using OpenShock.Common.Models;
1212
using OpenShock.Common.OpenShockDb;
1313
using OpenShock.Common.Problems;
1414

15-
namespace OpenShock.API.Controller.Shares;
15+
namespace OpenShock.API.Controller.Shares.UserShares;
1616

1717
file static class QueryHelper
1818
{
@@ -54,10 +54,10 @@ file static class QueryHelper
5454
};
5555
}
5656

57-
public sealed partial class SharesController
57+
public sealed partial class UserSharesController
5858
{
5959
[HttpGet("invites/outgoing")]
60-
[ApiVersion("2")]
60+
[MapToApiVersion("2")]
6161
public IAsyncEnumerable<ShareInviteBaseDetails> GetOutgoingInvitesList()
6262
{
6363
return _db.UserShareInvites
@@ -67,7 +67,7 @@ public IAsyncEnumerable<ShareInviteBaseDetails> GetOutgoingInvitesList()
6767
}
6868

6969
[HttpGet("invites/incoming")]
70-
[ApiVersion("2")]
70+
[MapToApiVersion("2")]
7171
public IAsyncEnumerable<ShareInviteBaseDetails> GetIncomingInvitesList()
7272
{
7373
return _db.UserShareInvites
@@ -79,7 +79,7 @@ public IAsyncEnumerable<ShareInviteBaseDetails> GetIncomingInvitesList()
7979
[HttpDelete("invites/outgoing/{inviteId}")]
8080
[ProducesResponseType(StatusCodes.Status200OK)]
8181
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareRequestNotFound
82-
[ApiVersion("2")]
82+
[MapToApiVersion("2")]
8383
public async Task<IActionResult> DeleteOutgoingInvite([FromRoute] Guid inviteId)
8484
{
8585
var deletedShareRequest = await _db.UserShareInvites
@@ -93,7 +93,7 @@ public async Task<IActionResult> DeleteOutgoingInvite([FromRoute] Guid inviteId)
9393
[HttpDelete("invites/incoming/{inviteId}")]
9494
[ProducesResponseType(StatusCodes.Status200OK)]
9595
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareRequestNotFound
96-
[ApiVersion("2")]
96+
[MapToApiVersion("2")]
9797
public async Task<IActionResult> DenyIncomingInvite([FromRoute] Guid inviteId)
9898
{
9999
var deletedShareRequest = await _db.UserShareInvites
@@ -113,7 +113,7 @@ public async Task<IActionResult> DenyIncomingInvite([FromRoute] Guid inviteId)
113113
[HttpPost("invites/incoming/{inviteId}")]
114114
[ProducesResponseType<V2UserSharesListItem>(StatusCodes.Status200OK)]
115115
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShareRequestNotFound
116-
[ApiVersion("2")]
116+
[MapToApiVersion("2")]
117117
public async Task<IActionResult> RedeemInvite([FromRoute] Guid inviteId, [FromServices] IDeviceUpdateService deviceUpdateService)
118118
{
119119
var shareRequest = await _db.UserShareInvites
@@ -191,7 +191,7 @@ public async Task<IActionResult> RedeemInvite([FromRoute] Guid inviteId, [FromSe
191191
Duration = y.MaxDuration,
192192
Intensity = y.MaxIntensity
193193
},
194-
Paused = y.IsPaused
194+
Paused = UserShareUtils.GetPausedReason(y.IsPaused, y.Shocker.IsPaused)
195195
})
196196
};
197197

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using Asp.Versioning;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.EntityFrameworkCore.Storage;
6+
using OpenShock.API.Services.DeviceUpdate;
7+
using OpenShock.API.Utils;
8+
using OpenShock.Common.DeviceControl;
9+
using OpenShock.Common.Errors;
10+
using OpenShock.Common.Extensions;
11+
using OpenShock.Common.Models;
12+
using OpenShock.Common.Services;
13+
14+
namespace OpenShock.API.Controller.Shares.UserShares;
15+
16+
public sealed partial class UserSharesController
17+
{
18+
[HttpPost("{userId:guid}/shockers/pause")]
19+
[MapToApiVersion("2")]
20+
[ProducesResponseType<PauseUserShareShockersResponse>(StatusCodes.Status200OK)]
21+
public async Task<IActionResult> BulkPauseUserShareShockers(
22+
[FromRoute] Guid userId,
23+
[FromBody] PauseUserShareShockersRequest pauseRequest,
24+
[FromServices] IDeviceUpdateService deviceUpdateService)
25+
{
26+
var affected = await _db.UserShares
27+
.Include(x => x.Shocker)
28+
.Where(x => pauseRequest.Shockers.Contains(x.ShockerId) && x.SharedWithUserId == userId &&
29+
x.Shocker.Device.OwnerId == CurrentUser.Id)
30+
.ToArrayAsync();
31+
32+
// Check if we have all shockers to delete
33+
var missingShockers = pauseRequest.Shockers.Except(affected.Select(x => x.ShockerId)).ToArray();
34+
if (missingShockers.Length > 0)
35+
{
36+
return Problem(ShareError.UserShareNotFound);
37+
}
38+
39+
foreach (var userShare in affected)
40+
{
41+
userShare.IsPaused = pauseRequest.Pause;
42+
_db.UserShares.Update(userShare);
43+
}
44+
45+
var deletedRecords = await _db.SaveChangesAsync();
46+
47+
var uniqueHubIds = affected.Select(x => x.Shocker.DeviceId).Distinct();
48+
var updateTasks = uniqueHubIds.Select(device =>
49+
deviceUpdateService.UpdateDevice(CurrentUser.Id, device, DeviceUpdateType.ShockerUpdated, userId));
50+
await Task.WhenAll(updateTasks);
51+
52+
return Ok(new PauseUserShareShockersResponse()
53+
{
54+
AffectedRecords = deletedRecords,
55+
PauseStates = affected.ToDictionary(x => x.ShockerId, x => UserShareUtils.GetPausedReason(x.IsPaused, x.Shocker.IsPaused))
56+
});
57+
}
58+
}
59+
60+
public sealed class PauseUserShareShockersRequest
61+
{
62+
[Required, MinLength(1)] public required Guid[] Shockers { get; set; }
63+
64+
[Required] public required bool Pause { get; set; }
65+
}
66+
67+
public sealed class PauseUserShareShockersResponse
68+
{
69+
public required int AffectedRecords { get; set; }
70+
public required IReadOnlyDictionary<Guid, PauseReason> PauseStates { get; set; }
71+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using Asp.Versioning;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.EntityFrameworkCore;
5+
using OpenShock.API.Services.DeviceUpdate;
6+
using OpenShock.Common.Errors;
7+
using OpenShock.Common.Extensions;
8+
using OpenShock.Common.Models;
9+
10+
namespace OpenShock.API.Controller.Shares.UserShares;
11+
12+
public sealed partial class UserSharesController
13+
{
14+
[HttpDelete("{userId:guid}/shockers")]
15+
[MapToApiVersion("2")]
16+
[ProducesResponseType<RemoveUserSharesResponse>(StatusCodes.Status200OK)]
17+
public async Task<IActionResult> BulkRemoveUserShareShockers([FromRoute] Guid userId , [FromBody] [MinLength(1)] Guid[] shockerIds, [FromServices] IDeviceUpdateService deviceUpdateService)
18+
{
19+
var affected = await _db.UserShares
20+
.Where(x => shockerIds.Contains(x.ShockerId) && x.SharedWithUserId == userId &&
21+
(x.Shocker.Device.OwnerId == CurrentUser.Id || x.SharedWithUserId == CurrentUser.Id))
22+
.Select(x => new { DeviceId = x.Shocker.Device.Id, OwnerId = x.Shocker.Device.OwnerId, UserShare = x })
23+
.ToArrayAsync();
24+
25+
// Check if we have all shockers to delete
26+
var missingShockers = shockerIds.Except(affected.Select(x => x.UserShare.ShockerId)).ToArray();
27+
if (missingShockers.Length > 0)
28+
{
29+
return Problem(ShareError.UserShareNotFound);
30+
}
31+
32+
_db.UserShares.RemoveRange(affected.Select(x => x.UserShare));
33+
34+
var deletedRecords = await _db.SaveChangesAsync();
35+
36+
var updateTasks = affected.DistinctBy(x => x.DeviceId).Select(x =>
37+
deviceUpdateService.UpdateDevice(x.OwnerId, x.DeviceId, DeviceUpdateType.ShockerUpdated, userId));
38+
await Task.WhenAll(updateTasks);
39+
40+
return Ok(new RemoveUserSharesResponse { AffectedRecords = deletedRecords });
41+
}
42+
}
43+
44+
public sealed class RemoveUserSharesResponse
45+
{
46+
public required int AffectedRecords { get; set; }
47+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Mvc;
4+
using OpenShock.Common.Authentication;
5+
using OpenShock.Common.Authentication.ControllerBase;
6+
using OpenShock.Common.OpenShockDb;
7+
8+
namespace OpenShock.API.Controller.Shares.UserShares;
9+
10+
/// <summary>
11+
/// Shocker share management
12+
/// </summary>
13+
[ApiController]
14+
[Tags("User Shocker Shares")]
15+
[ApiVersion("2")]
16+
[Route("/{version:apiVersion}/shares/user")]
17+
[Authorize(AuthenticationSchemes = OpenShockAuthSchemes.UserSessionCookie)]
18+
public sealed partial class UserSharesController : AuthenticatedSessionControllerBase
19+
{
20+
private readonly OpenShockContext _db;
21+
22+
public UserSharesController(OpenShockContext db)
23+
{
24+
_db = db;
25+
}
26+
}

API/Controller/Shockers/SendControl.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using OpenShock.Common.Hubs;
1010
using OpenShock.Common.Models;
1111
using OpenShock.Common.Problems;
12+
using OpenShock.Common.Services;
1213

1314
namespace OpenShock.API.Controller.Shockers;
1415

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace OpenShock.API.Models.Response;
1+
using OpenShock.Common.Models;
2+
3+
namespace OpenShock.API.Models.Response;
24

35
public sealed class UserShareInfo
46
{
@@ -7,5 +9,5 @@ public sealed class UserShareInfo
79
public required DateTime CreatedOn { get; init; }
810
public required ShockerPermissions Permissions { get; init; }
911
public required ShockerLimits Limits { get; init; }
10-
public required bool Paused { get; init; }
12+
public required PauseReason Paused { get; init; }
1113
}

0 commit comments

Comments
 (0)