Last active
July 23, 2020 16:09
-
-
Save jhoerr/fb7a7dd2de2e4e1bc752d3f1073beafc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.Azure.WebJobs; | |
using Microsoft.Azure.WebJobs.Extensions.Http; | |
using Microsoft.AspNetCore.Http; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.EntityFrameworkCore; | |
using Newtonsoft.Json; | |
using Database; | |
using System.Security.Claims; | |
using System.Linq; | |
using System.Collections.Generic; | |
using Models; | |
using System.ComponentModel.DataAnnotations; | |
using CSharpFunctionalExtensions; | |
using System; | |
using System.Net; | |
namespace Functions | |
{ | |
public class Devices | |
{ | |
private readonly PrintContext _printContext; | |
public Devices(PrintContext printContext) | |
{ | |
_printContext = printContext; | |
} | |
[FunctionName("GetDevices")] | |
public async Task<IActionResult> GetDevices( | |
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "devices")] HttpRequest req, | |
ClaimsPrincipal principal, | |
ILogger log) | |
=> await ApiRequest | |
.Authorize(req, principal, new[] { "Read.All", "ReadWrite.All", "Bill.All" }) | |
.Bind(_ => Database.Get(_printContext.Devices)) | |
.Finally(ApiResponse.Ok); | |
[FunctionName("PostDevices")] | |
public async Task<IActionResult> PostDevice( | |
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "devices")] HttpRequest req, | |
ClaimsPrincipal principal, | |
ILogger log) | |
=> await ApiRequest | |
.Authorize(req, principal, new[] { "ReadWrite.All" }) | |
.Bind(req => ApiRequest.ValidateBody<Device>(req)) | |
.Bind(device => Database.Add(_printContext.Devices, device)) | |
.Bind(device => Database.SaveChanges(_printContext, device)) | |
.Finally(ApiResponse.Created); | |
} | |
public static class ApiRequest | |
{ | |
/// <summary>Confirms that the requestor is authenticated and in at least one of the specified roles.</summary> | |
public static Result<HttpRequest, ApiError> Authorize(HttpRequest req, ClaimsPrincipal principal, IEnumerable<string> allowedRoles) | |
{ | |
if (!principal.Identity.IsAuthenticated) | |
return Result.Failure<HttpRequest, ApiError>(ApiError.Unauthorized()); | |
else if (!principal.Claims.Where(c => c.Type == "roles").Select(c => c.Value).Intersect(allowedRoles).Any()) | |
return Result.Failure<HttpRequest, ApiError>(ApiError.Forbidden()); | |
else | |
return Result.Success<HttpRequest, ApiError>(req); | |
} | |
/// <summary>Deserialize the request body to an instance of specified type and validate all properties. If valid, the instance is returned.</summary> | |
public static async Task<Result<T, ApiError>> ValidateBody<T>(HttpRequest req) | |
{ | |
var bodyString = await req.ReadAsStringAsync(); | |
var obj = JsonConvert.DeserializeObject<T>(bodyString); | |
var validationContext = new ValidationContext(obj, null, null); | |
var results = new List<ValidationResult>(); | |
Validator.TryValidateObject(obj, validationContext, results); | |
return results.Count > 0 | |
? Result.Failure<T, ApiError>(ApiError.BadRequest(results.First().ErrorMessage)) | |
: Result.Success<T, ApiError>(obj); | |
} | |
} | |
public static class Database | |
{ | |
/// <summary>Add an entity to the specified database set.</summary> | |
public static async Task<Result<T, ApiError>> Add<T>(DbSet<T> set, T obj) where T : class | |
{ | |
try | |
{ | |
await set.AddAsync(obj); | |
return Result.Success<T, ApiError>(obj); | |
} | |
catch (Exception e) | |
{ | |
return Result.Failure<T, ApiError>(ApiError.InternalServerError($"Failed to add record: {e.Message}")); | |
} | |
} | |
/// <summary>Get all entities from the specified database set.</summary> | |
public static async Task<Result<List<T>, ApiError>> Get<T>(DbSet<T> set) where T : class | |
{ | |
try | |
{ | |
var records = await set.ToListAsync(); | |
return Result.Success<List<T>, ApiError>(records); | |
} | |
catch (Exception e) | |
{ | |
return Result.Failure<List<T>, ApiError>(ApiError.InternalServerError($"Failed to fetch records: {e.Message}")); | |
} | |
} | |
/// <summary>Save all outstanding changes to the database.</summary> | |
public static async Task<Result<T, ApiError>> SaveChanges<T>(DbContext dbContext, T obj) | |
{ | |
try | |
{ | |
await dbContext.SaveChangesAsync(); | |
return Result.Success<T, ApiError>(obj); | |
} | |
catch (Exception e) | |
{ | |
return Result.Failure<T, ApiError>(ApiError.InternalServerError($"Failed to save changes: {e.Message}")); | |
} | |
} | |
} | |
public static class ApiResponse | |
{ | |
/// <summary>Return an HTTP 200 response with content, or an appropriate HTTP error response.</summary> | |
public static IActionResult Ok<T>(Result<T, ApiError> result) | |
=> result.IsSuccess | |
? new OkObjectResult(result.Value) | |
: result.Error.ToActionResult(); | |
/// <summary>Return an HTTP 204 response with content, or an appropriate HTTP error response.</summary> | |
public static IActionResult Created<T>(Result<T, ApiError> result) | |
=> result.IsSuccess | |
? new CreatedResult("location", result.Value) | |
: result.Error.ToActionResult(); | |
} | |
public class ApiError | |
{ | |
internal ApiError(HttpStatusCode statusCode, string message = null) | |
{ | |
StatusCode = statusCode; | |
Message = message; | |
} | |
public HttpStatusCode StatusCode { get; set; } | |
public string Message { get; set; } | |
public IActionResult ToActionResult() | |
{ | |
switch (StatusCode) | |
{ | |
case HttpStatusCode.Unauthorized: return new StatusCodeResult(401); | |
case HttpStatusCode.Forbidden: return new ForbidResult(); | |
case HttpStatusCode.BadRequest: return new BadRequestObjectResult(Message); | |
default: return new StatusCodeResult(500); | |
} | |
} | |
public static ApiError Unauthorized() | |
=> new ApiError(HttpStatusCode.Unauthorized); | |
public static ApiError Forbidden() | |
=> new ApiError(HttpStatusCode.Forbidden); | |
public static ApiError BadRequest(string message) | |
=> new ApiError(HttpStatusCode.BadRequest, message); | |
public static ApiError InternalServerError(string message) | |
=> new ApiError(HttpStatusCode.InternalServerError, message); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment