-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathDevicesController.cs
More file actions
261 lines (227 loc) · 11.1 KB
/
DevicesController.cs
File metadata and controls
261 lines (227 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OpenShock.API.Models.Requests;
using OpenShock.API.Services;
using OpenShock.Common.Authentication.Attributes;
using OpenShock.Common.Constants;
using OpenShock.Common.Errors;
using OpenShock.Common.Extensions;
using OpenShock.Common.Models;
using OpenShock.Common.Problems;
using OpenShock.Common.Redis;
using OpenShock.Common.Utils;
using System.Net.Mime;
namespace OpenShock.API.Controller.Devices;
public sealed partial class DevicesController
{
/// <summary>
/// Get all devices for the current user
/// </summary>
/// <response code="200">All devices for the current user</response>
[HttpGet]
[MapToApiVersion("1")]
public LegacyDataResponse<IAsyncEnumerable<Models.Response.ResponseDevice>> ListDevices()
{
var devices = _db.Devices
.Where(x => x.OwnerId == CurrentUser.Id)
.Select(x => new Models.Response.ResponseDevice
{
Id = x.Id,
Name = x.Name,
CreatedOn = x.CreatedAt
})
.AsAsyncEnumerable();
return new(devices);
}
/// <summary>
/// Get a device by its id
/// </summary>
/// <param name="deviceId"></param>
/// <response code="200">The device</response>
[HttpGet("{deviceId}")]
[ProducesResponseType<LegacyDataResponse<Models.Response.ResponseDeviceWithToken>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound
[MapToApiVersion("1")]
public async Task<IActionResult> GetDeviceById([FromRoute] Guid deviceId)
{
var hasAuthPerms = IsAllowed(PermissionType.Devices_Auth);
var device = await _db.Devices.Where(x => x.OwnerId == CurrentUser.Id && x.Id == deviceId)
.Select(x => new Models.Response.ResponseDeviceWithToken
{
Id = x.Id,
Name = x.Name,
CreatedOn = x.CreatedAt,
Token = hasAuthPerms ? x.Token : null
}).FirstOrDefaultAsync();
if (device is null) return Problem(HubError.HubNotFound);
return LegacyDataOk(device);
}
/// <summary>
/// Edit a device
/// </summary>
/// <param name="deviceId"></param>
/// <param name="body"></param>
/// <param name="updateService"></param>
/// <response code="200">Successfully updated device</response>
/// <response code="404">Device does not exist</response>
[HttpPatch("{deviceId}")]
[TokenPermission(PermissionType.Devices_Edit)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound
[MapToApiVersion("1")]
public async Task<IActionResult> EditDevice([FromRoute] Guid deviceId, [FromBody] HubEditRequest body, [FromServices] IDeviceUpdateService updateService)
{
var device = await _db.Devices.FirstOrDefaultAsync(x => x.OwnerId == CurrentUser.Id && x.Id == deviceId);
if (device is null) return Problem(HubError.HubNotFound);
device.Name = body.Name;
await _db.SaveChangesAsync();
await updateService.UpdateDeviceForAllShared(CurrentUser.Id, device.Id, DeviceUpdateType.Updated);
return Ok();
}
/// <summary>
/// Regenerate a device token
/// </summary>
/// <param name="deviceId">The id of the device to regenerate the token for</param>
/// <response code="200">Successfully regenerated device token</response>
/// <response code="404">Device does not exist</response>
/// <response code="500">Failed to save regenerated token</response>
[HttpPut("{deviceId}")]
[TokenPermission(PermissionType.Devices_Edit)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound
[MapToApiVersion("1")]
public async Task<IActionResult> RegenerateDeviceToken([FromRoute] Guid deviceId)
{
var device = await _db.Devices.FirstOrDefaultAsync(x => x.OwnerId == CurrentUser.Id && x.Id == deviceId);
if (device is null) return Problem(HubError.HubNotFound);
device.Token = CryptoUtils.RandomString(256);
var affected = await _db.SaveChangesAsync();
if (affected <= 0) throw new Exception("Failed to save regenerated token");
return Ok();
}
/// <summary>
/// Remove a device from current user's account
/// </summary>
/// <param name="deviceId">The id of the device to delete</param>
/// <param name="updateService"></param>
/// <response code="200">Successfully deleted device</response>
/// <response code="404">Device does not exist</response>
[HttpDelete("{deviceId}")]
[TokenPermission(PermissionType.Devices_Edit)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound
[MapToApiVersion("1")]
public async Task<IActionResult> RemoveDevice([FromRoute] Guid deviceId, [FromServices] IDeviceUpdateService updateService)
{
var affected = await _db.Devices.Where(x => x.Id == deviceId).WhereIsUserOrPrivileged(x => x.Owner, CurrentUser).ExecuteDeleteAsync();
if (affected <= 0) return Problem(HubError.HubNotFound);
await updateService.UpdateDeviceForAllShared(CurrentUser.Id, deviceId, DeviceUpdateType.Deleted);
return Ok();
}
/// <summary>
/// Create a new device for the current user
/// </summary>
/// <response code="201">Successfully created device</response>
[HttpPost]
[TokenPermission(PermissionType.Devices_Edit)]
[ProducesResponseType<Guid>(StatusCodes.Status201Created, MediaTypeNames.Text.Plain)]
[MapToApiVersion("1")]
public Task<IActionResult> CreateDevice([FromServices] IDeviceUpdateService updateService)
=> CreateDeviceV2(new HubCreateRequest
{
Name = $"New Hub {DateTimeOffset.UtcNow:d}"
}, updateService);
/// <summary>
/// Create a new device for the current user
/// </summary>
/// <response code="201">Successfully created device</response>
[HttpPost]
[TokenPermission(PermissionType.Devices_Edit)]
[ProducesResponseType<Guid>(StatusCodes.Status201Created, MediaTypeNames.Text.Plain)]
[MapToApiVersion("2")]
public async Task<IActionResult> CreateDeviceV2([FromBody] HubCreateRequest data, [FromServices] IDeviceUpdateService updateService)
{
int nDevices = await _db.Devices.CountAsync(d => d.OwnerId == CurrentUser.Id);
if (nDevices >= HardLimits.MaxHubsPerUser)
{
return Problem(HubError.TooManyHubs);
}
var device = new Common.OpenShockDb.Device
{
Id = Guid.CreateVersion7(),
OwnerId = CurrentUser.Id,
Name = data.Name,
Token = CryptoUtils.RandomString(256)
};
_db.Devices.Add(device);
await _db.SaveChangesAsync();
await updateService.UpdateDevice(CurrentUser.Id, device.Id, DeviceUpdateType.Created);
return Created($"/1/devices/{device.Id}", device.Id);
}
/// <summary>
/// Get a pair code for a device
/// </summary>
/// <param name="deviceId"></param>
/// <response code="200">The pair code</response>
/// <response code="404">Device does not exist or does not belong to you</response>
[HttpGet("{deviceId}/pair")]
[TokenPermission(PermissionType.Devices_Edit)]
[ProducesResponseType<LegacyDataResponse<string>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound
[MapToApiVersion("1")]
public async Task<IActionResult> GetPairCode([FromRoute] Guid deviceId)
{
var devicePairs = _redis.RedisCollection<DevicePair>();
var deviceExists = await _db.Devices.AnyAsync(x => x.Id == deviceId && x.OwnerId == CurrentUser.Id);
if (!deviceExists) return Problem(HubError.HubNotFound);
// replace with unlink?
var existing = await devicePairs.FindByIdAsync(deviceId.ToString());
if (existing is not null) await devicePairs.DeleteAsync(existing);
string pairCode = CryptoUtils.RandomNumericString(6);
var devicePairDto = new DevicePair
{
Id = deviceId,
PairCode = pairCode
};
await devicePairs.InsertAsync(devicePairDto, TimeSpan.FromMinutes(15));
return LegacyDataOk(pairCode);
}
/// <summary>
/// Get LCG info for a device if it is online and connected to a LCG node
/// </summary>
/// <param name="deviceId"></param>
/// <response code="200">LCG node was found and device is online</response>
/// <response code="404">Device does not exist or does not belong to you</response>
/// <response code="404">Device is not online</response>
/// <response code="412">Device is online but not connected to a LCG node, you might need to upgrade your firmware to use this feature</response>
/// <response code="500">Internal server error, lcg node could not be found</response>
[HttpGet("{deviceId}/lcg")]
[ProducesResponseType<LegacyDataResponse<LcgResponse>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound, DeviceIsNotOnline
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status412PreconditionFailed, MediaTypeNames.Application.ProblemJson)] // DeviceNotConnectedToGateway
[MapToApiVersion("1")]
public async Task<IActionResult> GetLiveControlGatewayInfo([FromRoute] Guid deviceId)
{
// Check if user owns device or has a share
var deviceExistsAndYouHaveAccess = await _db.Devices.AnyAsync(x =>
x.Id == deviceId && (x.OwnerId == CurrentUser.Id || x.Shockers.Any(y => y.UserShares.Any(
z => z.SharedWithUserId == CurrentUser.Id))));
if (!deviceExistsAndYouHaveAccess) return Problem(HubError.HubNotFound);
// Check if device is online
var devicesOnline = _redis.RedisCollection<DeviceOnline>();
var online = await devicesOnline.FindByIdAsync(deviceId.ToString());
if (online is null) return Problem(HubError.HubIsNotOnline);
// Check if device is connected to a LCG node
if (online.Gateway is null) return Problem(HubError.HubNotConnectedToGateway);
// Get LCG node info
var lcgNodes = _redis.RedisCollection<LcgNode>();
var gateway = await lcgNodes.FindByIdAsync(online.Gateway);
if (gateway is null) throw new Exception("Internal server error, lcg node could not be found");
return LegacyDataOk(new LcgResponse
{
Gateway = gateway.Fqdn,
Country = gateway.Country
});
}
}