Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Commit

Permalink
Add DeviceModel, DeviceManufacturer, and DeviceSpecification + Cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
hhvrc committed Jul 22, 2023
1 parent f83e807 commit d11b765
Show file tree
Hide file tree
Showing 18 changed files with 723 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Common/BusinessRules/UIStringValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static bool IsBadUiString(ReadOnlySpan<char> str)

str = str.Trim();

// String was only whitespace
// String had whitespace on one or both ends
if (str.Length != len) return true;

// Check if string contains any unwanted characters
Expand Down
2 changes: 1 addition & 1 deletion Common/DTOs/DeviceDTO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public readonly struct DeviceDto

public RfProtocol Protocol { get; init; }

public Guid ManufacturerId { get; init; }
public Guid? ManufacturerId { get; init; }

public string ManufacturerName { get; init; }

Expand Down
14 changes: 14 additions & 0 deletions Common/DTOs/DeviceManufacturerDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using ZapMe.Enums.Devices;

namespace ZapMe.DTOs;

public readonly struct DeviceManufacturerDto
{
public Guid Id { get; init; }

public string Name { get; init; }

public string ModelWebsiteUrl { get; init; }

public Uri IconUrl { get; init; }
}
30 changes: 30 additions & 0 deletions Common/DTOs/DeviceModelDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using ZapMe.Enums.Devices;

namespace ZapMe.DTOs;

public readonly struct DeviceModelDto
{
public Guid Id { get; init; }

public string Name { get; init; }

public Guid ModelId { get; init; }

public string ModelName { get; init; }

public string ModelNumber { get; init; }

public string ModelWebsiteUrl { get; init; }

public Uri IconUrl { get; init; }

public RfProtocol Protocol { get; init; }

public Guid ManufacturerId { get; init; }

public string ManufacturerName { get; init; }

public string ManufacturerWebsiteUrl { get; init; }

public DateTime RegisteredAt { get; init; }
}
20 changes: 20 additions & 0 deletions Common/DTOs/DeviceSpecificationDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using ZapMe.Database.Documents;

namespace ZapMe.DTOs;

public readonly struct DeviceSpecificationDto
{
public Guid Id { get; init; }

public ulong Frequency { get; init; }

public DeviceProprietarySpecification.ModulationType Modulation { get; init; }

public ulong DataRate { get; init; }

public ulong PacketSize { get; init; }

public DeviceProprietarySpecification.EncodingType Encoding { get; init; }

public bool InvertedBits { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

Expand Down
4 changes: 2 additions & 2 deletions Common/Database/Models/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ public sealed class DeviceEntity
{
public Guid Id { get; private set; }

public Guid ModelId { get; private set; }
public Guid ModelId { get; init; }

public Guid OwnerId { get; private set; }
public Guid OwnerId { get; init; }

public required string Name { get; set; }

Expand Down
2 changes: 1 addition & 1 deletion Common/Database/Models/DeviceModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public sealed class DeviceModelEntity

public Guid IconId { get; init; }

public Guid ManufacturerId { get; init; }
public Guid? ManufacturerId { get; init; }

/// <summary>
/// The FCC ID of this device model, if applicable.
Expand Down
2 changes: 2 additions & 0 deletions Common/Services/DiscordBotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ public async Task SetActivityAsync(DiscordActivity activity, UserStatus? status,
}
else
{
#if !DEBUG
await _client.ConnectAsync(activity, status, idleSince);
IsConnected = true;
#endif
}
}
}
2 changes: 0 additions & 2 deletions Common/Utils/StringUtils.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

namespace ZapMe.Utils;
Expand Down
2 changes: 1 addition & 1 deletion Common/Websocket/WebSocketHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private async Task RunDeviceAsync(WebSocket webSocket, CancellationToken cancell
.FirstOrDefaultAsync(cancellationToken);
if (authenticationResult is null)
{
_logger.LogError("Failed to authenticate websocket connection, provided AccessToken was invalid");
_logger.LogError("Failed to authenticate websocket connection, provided AccessToken was invalid");
return;
}

Expand Down
87 changes: 87 additions & 0 deletions RestAPI/Attributes/DisplaynameAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using System.ComponentModel.DataAnnotations;
using ZapMe.BusinessRules;

namespace ZapMe.Attributes;

/// <summary>
/// An attribute used to validate whether a display name is valid.
/// </summary>
/// <remarks>
/// Inherits from <see cref="ValidationAttribute"/>.
/// </remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class DisplaynameAttribute : ValidationAttribute, IParameterAttribute
{
/// <summary>
/// Regular expression for username validation.
/// </summary>
public const string DisplaynameRegex = /* lang=regex */ @"^[^\s].*[^\s]$";

/// <summary>
/// Example username used to generate OpenApi documentation.
/// </summary>
public const string ExampleDisplayname = "String";

private const string _ErrMsgMustBeString = "Must be a string";

/// <summary>
/// Indicates whether validation should be performed.
/// </summary>
public bool ShouldValidate { get; }

/// <summary>
/// Initializes a new instance of the <see cref="DisplaynameAttribute"/> class with the specified validation behavior.
/// </summary>
/// <param name="shouldValidate">True if validation should be performed; otherwise, false.</param>
public DisplaynameAttribute(bool shouldValidate)
{
ShouldValidate = shouldValidate;
}

/// <inheritdoc/>
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (!ShouldValidate) return ValidationResult.Success;

if (value is null)
{
return ValidationResult.Success;
}

if (value is not string displayname)
{
return new ValidationResult(_ErrMsgMustBeString);
}

if (Char.IsWhiteSpace(displayname[0]) || Char.IsWhiteSpace(displayname[^1]))
{
return new ValidationResult("Displayname cannot start or end with whitespace.");
}

if (UIStringValidator.IsBadUiString(displayname))
{
return new ValidationResult("Displayname must not contain obnoxious characters.");
}

return ValidationResult.Success;
}

/// <inheritdoc/>
public void Apply(OpenApiSchema schema)
{
if (ShouldValidate)
{
schema.Pattern = DisplaynameRegex;
}

schema.Example = new OpenApiString(ExampleDisplayname);
}

/// <inheritdoc/>
public void Apply(OpenApiParameter parameter)
{
Apply(parameter.Schema);
}
}
52 changes: 52 additions & 0 deletions RestAPI/Controllers/Api/V1/Device/Register.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using ZapMe.Attributes;
using ZapMe.Database.Models;
using ZapMe.DTOs;
using ZapMe.Utils;

namespace ZapMe.Controllers.Api.V1;

public partial class DeviceController
{
public readonly record struct RegisterDeviceRequest
{
public Guid DeviceModelId { get; init; }

[Displayname(true)]
public string Name { get; init; }
}

/// <summary>
/// Register a device.
/// </summary>
/// <returns></returns>
/// <response code="200">Device</response>
[HttpPost(Name = "RegisterDevice")]
[ProducesResponseType(typeof(DeviceDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> Register(RegisterDeviceRequest data, CancellationToken cancellationToken)
{
var deviceModel = await _dbContext.DeviceModels
.AsNoTracking()
.Where(dm => dm.Id == data.DeviceModelId)
.FirstOrDefaultAsync(cancellationToken);

if (deviceModel is null)
return NotFound();

var device = new DeviceEntity
{
ModelId = deviceModel.Id,
OwnerId = User.GetUserId(),
Name = data.Name,
AccessToken = StringUtils.GenerateUrlSafeRandomString(64)
};

_dbContext.Devices.Add(device);
await _dbContext.SaveChangesAsync(cancellationToken);

return Ok();
}
}
42 changes: 42 additions & 0 deletions RestAPI/Controllers/Api/V1/Device/RegisterManufacturer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Mvc;
using ZapMe.Attributes;
using ZapMe.Database.Models;
using ZapMe.DTOs;

namespace ZapMe.Controllers.Api.V1;

public partial class DeviceController
{
public readonly struct RegisterDeviceManufacturerRequest
{
[Displayname(true)]
public required string Name { get; init; }

public required string WebsiteUrl { get; init; }

public Guid IconId { get; init; }
}

/// <summary>
/// Register a device manufacturer.
/// </summary>
/// <returns></returns>
/// <response code="200">Device</response>
[HttpPost("manufacturer", Name = "RegisterDeviceManufacturer")]
[ProducesResponseType(typeof(DeviceManufacturerDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> RegisterManufacturer(RegisterDeviceManufacturerRequest data, CancellationToken cancellationToken)
{
var deviceManufacturer = new DeviceManufacturerEntity
{
Name = data.Name,
WebsiteUrl = data.WebsiteUrl,
IconId = data.IconId,
};

_dbContext.DeviceManufacturers.Add(deviceManufacturer);
await _dbContext.SaveChangesAsync(cancellationToken);

return Ok();
}
}
Loading

0 comments on commit d11b765

Please sign in to comment.