Skip to content

Commit

Permalink
Merge pull request #343 from qccoders/develop
Browse files Browse the repository at this point in the history
Prod deploy 1.2
  • Loading branch information
jpdillingham authored Jan 9, 2019
2 parents 9e91c75 + 8e92678 commit d6e2981
Show file tree
Hide file tree
Showing 26 changed files with 1,679 additions and 1,045 deletions.
14 changes: 13 additions & 1 deletion api/QCVOC.Api/Common/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,23 @@ public static string GetReadableString(this ModelStateDictionary dictionary)
.Select(key => (key, dictionary.Root.GetModelStateForProperty(key)))
.Select(field => field.key + ": " +
field.Item2.Errors
.Select(error => error.ErrorMessage)
.Select(error => error.GetErrorAndOrExceptionMessage())
.Select(error => error.TrimEnd('.'))
.Aggregate((a, b) => a + ", " + b));

return fields.Aggregate((a, b) => a + "; " + b);
}

/// <summary>
/// Returns the <see cref="ModelError.ErrorMessage"/>, the <see cref="Exception.Message"/> for the ModelError, or both if both are present.
/// </summary>
/// <param name="error">The ModelError from which to retrieve the error message.</param>
/// <returns>The retrieved error message.</returns>
public static string GetErrorAndOrExceptionMessage(this ModelError error)
{
var ex = error?.Exception?.Message;
return string.IsNullOrEmpty(error.ErrorMessage) ? ex :
string.IsNullOrEmpty(ex) ? error.ErrorMessage : $"{error.ErrorMessage} ({ex})";
}
}
}
133 changes: 115 additions & 18 deletions api/QCVOC.Api/Scans/Controller/ScansController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,113 @@ public IActionResult GetAll([FromQuery]ScanFilters filters)
return Ok(ScanRepository.GetAll(filters));
}

/// <summary>
/// Deletes a Check-In Scan.
/// </summary>
/// <param name="eventId">The Id of the Event.</param>
/// <param name="id">Either the Veteran Id or Card Number of the Veteran.</param>
/// <returns>See attributes.</returns>
/// <response code="204">The Scan was deleted successfully.</response>
/// <response code="400">The Veteran Id or Card Number is invalid.</response>
/// <response code="401">Unauthorized.</response>
/// <response code="404">The specified Card Number or Veteran Id doesn't match an enrolled Veteran.</response>
/// <response code="500">The server encountered an error while processing the request.</response>
[HttpDelete("{eventId}/{id}")]
[Authorize]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
[ProducesResponseType(404)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Delete(Guid eventId, string id)
{
return Delete(eventId, id, null);
}

/// <summary>
/// Deletes a Service Scan.
/// </summary>
/// <param name="eventId">The Id of the Event.</param>
/// <param name="id">Either the Veteran Id or Card Number of the Veteran.</param>
/// <param name="serviceId">The optional Service Id.</param>
/// <returns>See attributes.</returns>
/// <response code="204">The Scan was deleted successfully.</response>
/// <response code="400">The Veteran Id or Card Number is invalid.</response>
/// <response code="401">Unauthorized.</response>
/// <response code="404">The specified Card Number or Veteran Id doesn't match an enrolled Veteran.</response>
/// <response code="500">The server encountered an error while processing the request.</response>
[HttpDelete("{eventId}/{id}/{serviceId}")]
[Authorize]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
[ProducesResponseType(404)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Delete(Guid eventId, string id, Guid? serviceId = null)
{
if (string.IsNullOrEmpty(id))
{
return BadRequest($"The card or veteran ID is null or empty.");
}

var veteran = default(Veteran);

if (int.TryParse(id, out var cardNumber))
{
veteran = VeteranRepository
.GetAll(new VeteranFilters() { CardNumber = cardNumber })
.SingleOrDefault();
}
else if (Guid.TryParse(id, out var veteranId))
{
veteran = VeteranRepository.Get(veteranId);
}
else
{
return BadRequest($"The provided ID is neither a Card Number nor Veteran ID.");
}

if (veteran == default(Veteran))
{
return StatusCode(404, $"The specified Card Number or Veteran Id doesn't match an enrolled Veteran.");
}

var scan = ScanRepository.Get(eventId, veteran.Id, serviceId);

if (scan == default(Scan))
{
return StatusCode(404, $"A Scan matching the specified information could not be found.");
}

try
{
ScanRepository.Delete(eventId, veteran.Id, serviceId);
return NoContent();
}
catch (Exception ex)
{
throw new Exception($"Error deleting the Scan matching the specified information: {ex.Message}. See inner Exception for details.", ex);
}
}

/// <summary>
/// Performs an Event Scan.
/// </summary>
/// <param name="scan">The scan context.</param>
/// <returns>See attributes.</returns>
/// <response code="200">The Scan has already been recorded.</response>
/// <response code="201">The Scan was recorded or updated.</response>
/// <response code="400">The specified Scan was invalid.</response>
/// <response code="401">Unauthorized.</response>
/// <response code="403">The Veteran has not checked in for the Event.</response>
/// <response code="404">The specified Veteran, Event or Service was invalid.</response>
/// <response code="409">The Scan has already been recorded.</response>
/// <response code="500">The server encountered an error while processing the request.</response>
[HttpPut("")]
[Authorize]
[ProducesResponseType(typeof(Scan), 200)]
[ProducesResponseType(typeof(Scan), 201)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(403)]
[ProducesResponseType(404)]
[ProducesResponseType(typeof(ScanError), 409)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Scan([FromBody]ScanRequest scan)
{
Expand All @@ -94,37 +181,50 @@ public IActionResult Scan([FromBody]ScanRequest scan)
return BadRequest(ModelState.GetReadableString());
}

if (!int.TryParse(scan.CardNumber, out var cardNumber))
{
return BadRequest($"Card Number {scan.CardNumber} is not a valid integer.");
}

var @event = EventRepository.Get((Guid)scan.EventId);

if (@event == default(Event))
{
StatusCode(404, "The specified Event could not be found.");
}

var service = ServiceRepository.Get(scan.ServiceId);

if (service == default(Service))
{
return StatusCode(404, "The specified Service could not be found.");
}

var veteran = VeteranRepository
.GetAll(new VeteranFilters() { CardNumber = scan.CardNumber, IncludePhotoBase64 = true })
.GetAll(new VeteranFilters() { CardNumber = cardNumber, IncludePhotoBase64 = true })
.SingleOrDefault();

if (veteran == default(Veteran))
{
return StatusCode(404, $"Card Number {scan.CardNumber} doesn't match an enrolled Veteran.");
}

var previousScans = ScanRepository.GetAll(new ScanFilters() { EventId = scan.EventId, VeteranId = veteran.Id });
var existingCheckIn = previousScans.Where(s => s.ServiceId == Guid.Empty).SingleOrDefault();

var scanRecord = new Scan()
{
EventId = (Guid)scan.EventId,
VeteranId = veteran.Id,
ServiceId = scan.ServiceId,
PlusOne = scan.PlusOne,
ScanById = User.GetId(),
ScanDate = DateTime.UtcNow,
};

var previousScans = ScanRepository.GetAll(new ScanFilters() { EventId = scan.EventId, VeteranId = veteran.Id });

// check in scan
if (scan.ServiceId == Guid.Empty)
{
var existingCheckIn = previousScans.Where(s => s.ServiceId == Guid.Empty).SingleOrDefault();
scanRecord.PlusOne = scan.PlusOne;

if (existingCheckIn == default(Scan))
{
Expand All @@ -136,27 +236,24 @@ public IActionResult Scan([FromBody]ScanRequest scan)
}
else
{
return StatusCode(200, new ScanResponse(existingCheckIn, veteran));
return Conflict(new ScanError(existingCheckIn, veteran, "Duplicate Scan"));
}
}

var service = ServiceRepository.Get(scan.ServiceId);

if (service == default(Service))
// service scan
if (existingCheckIn == default(Scan))
{
return StatusCode(404, "The specified Service could not be found.");
return StatusCode(403, new ScanError(scanRecord, veteran, "The Veteran has not checked in for this Event."));
}

if (!previousScans.Where(s => s.ServiceId == Guid.Empty).Any())
{
return StatusCode(403, "The Veteran has not checked in for this Event.");
}
var previousServiceScan = previousScans.Where(s => s.ServiceId == scan.ServiceId).SingleOrDefault();

if (previousScans.Where(s => s.ServiceId == scan.ServiceId).Any())
if (previousServiceScan != default(Scan))
{
return StatusCode(200, previousScans.Where(s => s.ServiceId == scan.ServiceId).SingleOrDefault());
return Conflict(new ScanError(previousServiceScan, veteran, "Duplicate Scan"));
}

scanRecord.PlusOne = existingCheckIn.PlusOne;
return CreateScan(scanRecord, veteran);
}

Expand Down
63 changes: 63 additions & 0 deletions api/QCVOC.Api/Scans/Data/DTO/ScanError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// <copyright file="ScanError.cs" company="QC Coders">
// Copyright (c) QC Coders. All rights reserved. Licensed under the GPLv3 license. See LICENSE file
// in the project root for full license information.
// </copyright>

namespace QCVOC.Api.Scans.Data.DTO
{
using System;
using QCVOC.Api.Scans.Data.Model;
using QCVOC.Api.Veterans.Data.Model;

/// <summary>
/// DTO containing a Scan error.
/// </summary>
public class ScanError
{
/// <summary>
/// Initializes a new instance of the <see cref="ScanError"/> class with the specified <paramref name="scan"/>, <paramref name="veteran"/>, and <paramref name="message"/>.
/// </summary>
/// <param name="scan">The Scan to include in the response.</param>
/// <param name="veteran">The Veteran associated with the scan, if applicable.</param>
/// <param name="message">The error message associated with the scan.</param>
public ScanError(Scan scan, Veteran veteran, string message)
{
EventId = scan.EventId;
VeteranId = scan.VeteranId;
Veteran = veteran;
ServiceId = scan.ServiceId;
PlusOne = scan.PlusOne;
Message = message;
}

/// <summary>
/// Gets or sets the id of the Event.
/// </summary>
public Guid EventId { get; set; }

/// <summary>
/// Gets or sets the id of the Veteran.
/// </summary>
public Guid VeteranId { get; set; }

/// <summary>
/// Gets or sets the Veteran associated with the Scan.
/// </summary>
public Veteran Veteran { get; set; }

/// <summary>
/// Gets or sets the id of the Service.
/// </summary>
public Guid? ServiceId { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the Veteran brought a guest.
/// </summary>
public bool PlusOne { get; set; }

/// <summary>
/// Gets or sets an error message.
/// </summary>
public string Message { get; set; }
}
}
2 changes: 1 addition & 1 deletion api/QCVOC.Api/Scans/Data/DTO/ScanRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class ScanRequest
/// Gets or sets the Veteran's card number.
/// </summary>
[Required]
public int CardNumber { get; set; }
public string CardNumber { get; set; }

/// <summary>
/// Gets or sets the id of the Service.
Expand Down
5 changes: 1 addition & 4 deletions api/QCVOC.Api/Scans/Data/DTO/ScanResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@ public ScanResponse(Scan scan, Veteran veteran)
/// <summary>
/// Gets or sets the id of the Service.
/// </summary>
/// <remarks>
/// If null, represents a check-in Scan.
/// </remarks>
public Guid ServiceId { get; set; }
public Guid? ServiceId { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the Veteran brought a guest.
Expand Down
11 changes: 3 additions & 8 deletions api/QCVOC.Api/Scans/Data/Repository/ScanRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ public Scan Create(Scan scan)

var query = builder.AddTemplate(@"
INSERT INTO scans
(eventid, veteranid, serviceid, plusone, scandate, scanbyid, deleted)
(eventid, veteranid, serviceid, plusone, scandate, scanbyid)
VALUES
(@eventid, @veteranid, @serviceid, @plusone, @scandate, @scanbyid, @deleted)
(@eventid, @veteranid, @serviceid, @plusone, @scandate, @scanbyid)
");

builder.AddParameters(new
Expand All @@ -54,7 +54,6 @@ INSERT INTO scans
plusone = scan.PlusOne,
scandate = scan.ScanDate,
scanbyid = scan.ScanById,
deleted = false,
});

using (var db = ConnectionFactory.CreateConnection())
Expand Down Expand Up @@ -85,9 +84,7 @@ public void Delete(Guid eventId, Guid veteranId, Guid? serviceId)
var builder = new SqlBuilder();

var query = builder.AddTemplate($@"
UPDATE events
SET
deleted = true
DELETE FROM scans
WHERE eventid = @eventid
AND veteranid = @veteranid
{(serviceId != null ? "AND serviceid = @serviceid" : string.Empty)}
Expand Down Expand Up @@ -153,8 +150,6 @@ LIMIT @limit OFFSET @offset
orderby = filters.OrderBy.ToString(),
});

builder.ApplyFilter(FilterType.Equals, "s.deleted", false);

if (filters is ScanFilters scanFilters)
{
builder
Expand Down
4 changes: 2 additions & 2 deletions mobile/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ android {
applicationId "org.qccoders.qcvoc"
minSdkVersion 21
targetSdkVersion 26
versionCode 8
versionName "0.8"
versionCode 11
versionName "1.0.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
Expand Down
Binary file modified mobile/app/release/app-release.apk
Binary file not shown.
2 changes: 1 addition & 1 deletion mobile/app/release/output.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":8,"versionName":"0.8","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":11,"versionName":"1.0.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
Loading

0 comments on commit d6e2981

Please sign in to comment.