Skip to content

Commit d6e2981

Browse files
authored
Merge pull request #343 from qccoders/develop
Prod deploy 1.2
2 parents 9e91c75 + 8e92678 commit d6e2981

File tree

26 files changed

+1679
-1045
lines changed

26 files changed

+1679
-1045
lines changed

api/QCVOC.Api/Common/Utility.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,23 @@ public static string GetReadableString(this ModelStateDictionary dictionary)
190190
.Select(key => (key, dictionary.Root.GetModelStateForProperty(key)))
191191
.Select(field => field.key + ": " +
192192
field.Item2.Errors
193-
.Select(error => error.ErrorMessage)
193+
.Select(error => error.GetErrorAndOrExceptionMessage())
194194
.Select(error => error.TrimEnd('.'))
195195
.Aggregate((a, b) => a + ", " + b));
196196

197197
return fields.Aggregate((a, b) => a + "; " + b);
198198
}
199+
200+
/// <summary>
201+
/// Returns the <see cref="ModelError.ErrorMessage"/>, the <see cref="Exception.Message"/> for the ModelError, or both if both are present.
202+
/// </summary>
203+
/// <param name="error">The ModelError from which to retrieve the error message.</param>
204+
/// <returns>The retrieved error message.</returns>
205+
public static string GetErrorAndOrExceptionMessage(this ModelError error)
206+
{
207+
var ex = error?.Exception?.Message;
208+
return string.IsNullOrEmpty(error.ErrorMessage) ? ex :
209+
string.IsNullOrEmpty(ex) ? error.ErrorMessage : $"{error.ErrorMessage} ({ex})";
210+
}
199211
}
200212
}

api/QCVOC.Api/Scans/Controller/ScansController.cs

Lines changed: 115 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,26 +66,113 @@ public IActionResult GetAll([FromQuery]ScanFilters filters)
6666
return Ok(ScanRepository.GetAll(filters));
6767
}
6868

69+
/// <summary>
70+
/// Deletes a Check-In Scan.
71+
/// </summary>
72+
/// <param name="eventId">The Id of the Event.</param>
73+
/// <param name="id">Either the Veteran Id or Card Number of the Veteran.</param>
74+
/// <returns>See attributes.</returns>
75+
/// <response code="204">The Scan was deleted successfully.</response>
76+
/// <response code="400">The Veteran Id or Card Number is invalid.</response>
77+
/// <response code="401">Unauthorized.</response>
78+
/// <response code="404">The specified Card Number or Veteran Id doesn't match an enrolled Veteran.</response>
79+
/// <response code="500">The server encountered an error while processing the request.</response>
80+
[HttpDelete("{eventId}/{id}")]
81+
[Authorize]
82+
[ProducesResponseType(400)]
83+
[ProducesResponseType(401)]
84+
[ProducesResponseType(404)]
85+
[ProducesResponseType(typeof(Exception), 500)]
86+
public IActionResult Delete(Guid eventId, string id)
87+
{
88+
return Delete(eventId, id, null);
89+
}
90+
91+
/// <summary>
92+
/// Deletes a Service Scan.
93+
/// </summary>
94+
/// <param name="eventId">The Id of the Event.</param>
95+
/// <param name="id">Either the Veteran Id or Card Number of the Veteran.</param>
96+
/// <param name="serviceId">The optional Service Id.</param>
97+
/// <returns>See attributes.</returns>
98+
/// <response code="204">The Scan was deleted successfully.</response>
99+
/// <response code="400">The Veteran Id or Card Number is invalid.</response>
100+
/// <response code="401">Unauthorized.</response>
101+
/// <response code="404">The specified Card Number or Veteran Id doesn't match an enrolled Veteran.</response>
102+
/// <response code="500">The server encountered an error while processing the request.</response>
103+
[HttpDelete("{eventId}/{id}/{serviceId}")]
104+
[Authorize]
105+
[ProducesResponseType(400)]
106+
[ProducesResponseType(401)]
107+
[ProducesResponseType(404)]
108+
[ProducesResponseType(typeof(Exception), 500)]
109+
public IActionResult Delete(Guid eventId, string id, Guid? serviceId = null)
110+
{
111+
if (string.IsNullOrEmpty(id))
112+
{
113+
return BadRequest($"The card or veteran ID is null or empty.");
114+
}
115+
116+
var veteran = default(Veteran);
117+
118+
if (int.TryParse(id, out var cardNumber))
119+
{
120+
veteran = VeteranRepository
121+
.GetAll(new VeteranFilters() { CardNumber = cardNumber })
122+
.SingleOrDefault();
123+
}
124+
else if (Guid.TryParse(id, out var veteranId))
125+
{
126+
veteran = VeteranRepository.Get(veteranId);
127+
}
128+
else
129+
{
130+
return BadRequest($"The provided ID is neither a Card Number nor Veteran ID.");
131+
}
132+
133+
if (veteran == default(Veteran))
134+
{
135+
return StatusCode(404, $"The specified Card Number or Veteran Id doesn't match an enrolled Veteran.");
136+
}
137+
138+
var scan = ScanRepository.Get(eventId, veteran.Id, serviceId);
139+
140+
if (scan == default(Scan))
141+
{
142+
return StatusCode(404, $"A Scan matching the specified information could not be found.");
143+
}
144+
145+
try
146+
{
147+
ScanRepository.Delete(eventId, veteran.Id, serviceId);
148+
return NoContent();
149+
}
150+
catch (Exception ex)
151+
{
152+
throw new Exception($"Error deleting the Scan matching the specified information: {ex.Message}. See inner Exception for details.", ex);
153+
}
154+
}
155+
69156
/// <summary>
70157
/// Performs an Event Scan.
71158
/// </summary>
72159
/// <param name="scan">The scan context.</param>
73160
/// <returns>See attributes.</returns>
74-
/// <response code="200">The Scan has already been recorded.</response>
75161
/// <response code="201">The Scan was recorded or updated.</response>
76162
/// <response code="400">The specified Scan was invalid.</response>
77163
/// <response code="401">Unauthorized.</response>
78164
/// <response code="403">The Veteran has not checked in for the Event.</response>
79165
/// <response code="404">The specified Veteran, Event or Service was invalid.</response>
166+
/// <response code="409">The Scan has already been recorded.</response>
80167
/// <response code="500">The server encountered an error while processing the request.</response>
81168
[HttpPut("")]
82169
[Authorize]
83-
[ProducesResponseType(typeof(Scan), 200)]
84170
[ProducesResponseType(typeof(Scan), 201)]
85171
[ProducesResponseType(typeof(string), 400)]
86172
[ProducesResponseType(401)]
87173
[ProducesResponseType(403)]
88174
[ProducesResponseType(404)]
175+
[ProducesResponseType(typeof(ScanError), 409)]
89176
[ProducesResponseType(typeof(Exception), 500)]
90177
public IActionResult Scan([FromBody]ScanRequest scan)
91178
{
@@ -94,37 +181,50 @@ public IActionResult Scan([FromBody]ScanRequest scan)
94181
return BadRequest(ModelState.GetReadableString());
95182
}
96183

184+
if (!int.TryParse(scan.CardNumber, out var cardNumber))
185+
{
186+
return BadRequest($"Card Number {scan.CardNumber} is not a valid integer.");
187+
}
188+
97189
var @event = EventRepository.Get((Guid)scan.EventId);
98190

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

196+
var service = ServiceRepository.Get(scan.ServiceId);
197+
198+
if (service == default(Service))
199+
{
200+
return StatusCode(404, "The specified Service could not be found.");
201+
}
202+
104203
var veteran = VeteranRepository
105-
.GetAll(new VeteranFilters() { CardNumber = scan.CardNumber, IncludePhotoBase64 = true })
204+
.GetAll(new VeteranFilters() { CardNumber = cardNumber, IncludePhotoBase64 = true })
106205
.SingleOrDefault();
107206

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

212+
var previousScans = ScanRepository.GetAll(new ScanFilters() { EventId = scan.EventId, VeteranId = veteran.Id });
213+
var existingCheckIn = previousScans.Where(s => s.ServiceId == Guid.Empty).SingleOrDefault();
214+
113215
var scanRecord = new Scan()
114216
{
115217
EventId = (Guid)scan.EventId,
116218
VeteranId = veteran.Id,
117219
ServiceId = scan.ServiceId,
118-
PlusOne = scan.PlusOne,
119220
ScanById = User.GetId(),
120221
ScanDate = DateTime.UtcNow,
121222
};
122223

123-
var previousScans = ScanRepository.GetAll(new ScanFilters() { EventId = scan.EventId, VeteranId = veteran.Id });
124-
224+
// check in scan
125225
if (scan.ServiceId == Guid.Empty)
126226
{
127-
var existingCheckIn = previousScans.Where(s => s.ServiceId == Guid.Empty).SingleOrDefault();
227+
scanRecord.PlusOne = scan.PlusOne;
128228

129229
if (existingCheckIn == default(Scan))
130230
{
@@ -136,27 +236,24 @@ public IActionResult Scan([FromBody]ScanRequest scan)
136236
}
137237
else
138238
{
139-
return StatusCode(200, new ScanResponse(existingCheckIn, veteran));
239+
return Conflict(new ScanError(existingCheckIn, veteran, "Duplicate Scan"));
140240
}
141241
}
142242

143-
var service = ServiceRepository.Get(scan.ServiceId);
144-
145-
if (service == default(Service))
243+
// service scan
244+
if (existingCheckIn == default(Scan))
146245
{
147-
return StatusCode(404, "The specified Service could not be found.");
246+
return StatusCode(403, new ScanError(scanRecord, veteran, "The Veteran has not checked in for this Event."));
148247
}
149248

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

155-
if (previousScans.Where(s => s.ServiceId == scan.ServiceId).Any())
251+
if (previousServiceScan != default(Scan))
156252
{
157-
return StatusCode(200, previousScans.Where(s => s.ServiceId == scan.ServiceId).SingleOrDefault());
253+
return Conflict(new ScanError(previousServiceScan, veteran, "Duplicate Scan"));
158254
}
159255

256+
scanRecord.PlusOne = existingCheckIn.PlusOne;
160257
return CreateScan(scanRecord, veteran);
161258
}
162259

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// <copyright file="ScanError.cs" company="QC Coders">
2+
// Copyright (c) QC Coders. All rights reserved. Licensed under the GPLv3 license. See LICENSE file
3+
// in the project root for full license information.
4+
// </copyright>
5+
6+
namespace QCVOC.Api.Scans.Data.DTO
7+
{
8+
using System;
9+
using QCVOC.Api.Scans.Data.Model;
10+
using QCVOC.Api.Veterans.Data.Model;
11+
12+
/// <summary>
13+
/// DTO containing a Scan error.
14+
/// </summary>
15+
public class ScanError
16+
{
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="ScanError"/> class with the specified <paramref name="scan"/>, <paramref name="veteran"/>, and <paramref name="message"/>.
19+
/// </summary>
20+
/// <param name="scan">The Scan to include in the response.</param>
21+
/// <param name="veteran">The Veteran associated with the scan, if applicable.</param>
22+
/// <param name="message">The error message associated with the scan.</param>
23+
public ScanError(Scan scan, Veteran veteran, string message)
24+
{
25+
EventId = scan.EventId;
26+
VeteranId = scan.VeteranId;
27+
Veteran = veteran;
28+
ServiceId = scan.ServiceId;
29+
PlusOne = scan.PlusOne;
30+
Message = message;
31+
}
32+
33+
/// <summary>
34+
/// Gets or sets the id of the Event.
35+
/// </summary>
36+
public Guid EventId { get; set; }
37+
38+
/// <summary>
39+
/// Gets or sets the id of the Veteran.
40+
/// </summary>
41+
public Guid VeteranId { get; set; }
42+
43+
/// <summary>
44+
/// Gets or sets the Veteran associated with the Scan.
45+
/// </summary>
46+
public Veteran Veteran { get; set; }
47+
48+
/// <summary>
49+
/// Gets or sets the id of the Service.
50+
/// </summary>
51+
public Guid? ServiceId { get; set; }
52+
53+
/// <summary>
54+
/// Gets or sets a value indicating whether the Veteran brought a guest.
55+
/// </summary>
56+
public bool PlusOne { get; set; }
57+
58+
/// <summary>
59+
/// Gets or sets an error message.
60+
/// </summary>
61+
public string Message { get; set; }
62+
}
63+
}

api/QCVOC.Api/Scans/Data/DTO/ScanRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class ScanRequest
2323
/// Gets or sets the Veteran's card number.
2424
/// </summary>
2525
[Required]
26-
public int CardNumber { get; set; }
26+
public string CardNumber { get; set; }
2727

2828
/// <summary>
2929
/// Gets or sets the id of the Service.

api/QCVOC.Api/Scans/Data/DTO/ScanResponse.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,7 @@ public ScanResponse(Scan scan, Veteran veteran)
4949
/// <summary>
5050
/// Gets or sets the id of the Service.
5151
/// </summary>
52-
/// <remarks>
53-
/// If null, represents a check-in Scan.
54-
/// </remarks>
55-
public Guid ServiceId { get; set; }
52+
public Guid? ServiceId { get; set; }
5653

5754
/// <summary>
5855
/// Gets or sets a value indicating whether the Veteran brought a guest.

api/QCVOC.Api/Scans/Data/Repository/ScanRepository.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ public Scan Create(Scan scan)
4141

4242
var query = builder.AddTemplate(@"
4343
INSERT INTO scans
44-
(eventid, veteranid, serviceid, plusone, scandate, scanbyid, deleted)
44+
(eventid, veteranid, serviceid, plusone, scandate, scanbyid)
4545
VALUES
46-
(@eventid, @veteranid, @serviceid, @plusone, @scandate, @scanbyid, @deleted)
46+
(@eventid, @veteranid, @serviceid, @plusone, @scandate, @scanbyid)
4747
");
4848

4949
builder.AddParameters(new
@@ -54,7 +54,6 @@ INSERT INTO scans
5454
plusone = scan.PlusOne,
5555
scandate = scan.ScanDate,
5656
scanbyid = scan.ScanById,
57-
deleted = false,
5857
});
5958

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

8786
var query = builder.AddTemplate($@"
88-
UPDATE events
89-
SET
90-
deleted = true
87+
DELETE FROM scans
9188
WHERE eventid = @eventid
9289
AND veteranid = @veteranid
9390
{(serviceId != null ? "AND serviceid = @serviceid" : string.Empty)}
@@ -153,8 +150,6 @@ LIMIT @limit OFFSET @offset
153150
orderby = filters.OrderBy.ToString(),
154151
});
155152

156-
builder.ApplyFilter(FilterType.Equals, "s.deleted", false);
157-
158153
if (filters is ScanFilters scanFilters)
159154
{
160155
builder

mobile/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ android {
66
applicationId "org.qccoders.qcvoc"
77
minSdkVersion 21
88
targetSdkVersion 26
9-
versionCode 8
10-
versionName "0.8"
9+
versionCode 11
10+
versionName "1.0.1"
1111
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
1212
}
1313
buildTypes {

mobile/app/release/app-release.apk

264 Bytes
Binary file not shown.

mobile/app/release/output.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +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":{}}]
1+
[{"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":{}}]

0 commit comments

Comments
 (0)