MVP Reached

This commit is contained in:
Tara Wilson 2024-12-07 19:20:24 -05:00
parent 35ddedb5c3
commit 19529085e7
22 changed files with 993 additions and 892 deletions

View File

@ -15,7 +15,8 @@ namespace api.Controllers;
[Route("[controller]")] [Route("[controller]")]
public class TicketController( public class TicketController(
IQrCodeGenerator qr, IQrCodeGenerator qr,
ITicketManager ticketManager) : ControllerBase ITicketManager ticketManager,
IEventManager eventManager) : ControllerBase
{ {
/// <summary> /// <summary>
/// Generates a Base64 String Qr Code and Saves Qr Code and Ticket to DB /// Generates a Base64 String Qr Code and Saves Qr Code and Ticket to DB
@ -41,7 +42,7 @@ public class TicketController(
QrCode = qrCode, QrCode = qrCode,
Type = request.Type, Type = request.Type,
EventId = request.EventId, EventId = request.EventId,
SeasonId = request.SeasonId != Guid.Empty ? request.SeasonId : Guid.Empty, SeasonId = request.SeasonId,
Patron = request.Patron, Patron = request.Patron,
}; };
@ -51,8 +52,8 @@ public class TicketController(
//return //return
var response = new MintResponse var response = new MintResponse
{ {
QrCode = ticket.QrCode, Ticket = ticket,
Type = ticket.Type Details = eventManager.GetEvent(ticket.EventId)
}; };
return Ok(response); return Ok(response);

View File

@ -1,5 +1,6 @@
using models.Core; using models.Core;
using models.Request; using models.Request;
using models.Response;
namespace api.Interfaces; namespace api.Interfaces;
@ -9,4 +10,5 @@ public interface IEventManager
void PatchEvent(PatchEvent request); void PatchEvent(PatchEvent request);
List<Event> GetEvents(DateTime? startDate, DateTime? endDate); List<Event> GetEvents(DateTime? startDate, DateTime? endDate);
List<Event> GetAllEvents(); List<Event> GetAllEvents();
EventDetails GetEvent(Guid id);
} }

View File

@ -2,6 +2,7 @@ using api.Interfaces;
using data.Events; using data.Events;
using models.Core; using models.Core;
using models.Request; using models.Request;
using models.Response;
namespace api.Services; namespace api.Services;
@ -38,4 +39,9 @@ public class EventManager : IEventManager
{ {
return new GetAll().Execute(); return new GetAll().Execute();
} }
public EventDetails GetEvent(Guid id)
{
return new GetDetails().Execute(id);
}
} }

View File

@ -49,20 +49,20 @@ public class TicketManager : ITicketManager
return new MintResponse() return new MintResponse()
{ {
QrCode = ticket.QrCode, Ticket = ticket,
Type = ticket.Type, Details = new data.Events.GetDetails().Execute(ticket.EventId),
}; };
} }
private static TicketValidity DetermineValidity(Event @event) private static TicketValidity DetermineValidity(Event @event)
{ {
if (@event.Date < DateTime.Now) if (@event.Date.Day != DateTime.Today.Day) return TicketValidity.Invalid;
if (@event.Date.Day < DateTime.Now.Day)
{ {
return TicketValidity.Expired; return TicketValidity.Expired;
} }
if (@event.Date.Day != DateTime.Today.Day) return TicketValidity.Invalid;
if (DateTime.Now.Hour < @event.Date.Hour - 2) if (DateTime.Now.Hour < @event.Date.Hour - 2)
{ {
return TicketValidity.Early; return TicketValidity.Early;

View File

@ -0,0 +1,27 @@
using models.Core;
using models.Response;
using MongoDB.Driver;
namespace data.Events;
public class GetDetails
{
public EventDetails Execute(Guid eventId)
{
var database = MongoFactory.GetDatabase();
var collection = database.GetCollection<Event>("events");
var filter = Builders<Event>.Filter.Eq(e => e.Id, eventId);
var @event = collection.Find(filter).FirstOrDefault();
var details = new EventDetails
{
Name = @event.Name,
Description = @event.Description,
Date = @event.Date,
Venue = @event.Venue,
Talent = @event.Talent,
};
return details;
}
}

View File

@ -5,7 +5,7 @@ namespace models.Core;
public class Ticket public class Ticket
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid SeasonId { get; set; } public Guid? SeasonId { get; set; }
public Guid EventId { get; set; } public Guid EventId { get; set; }
public TicketType Type { get; set; } public TicketType Type { get; set; }
public required string QrCode { get; set; } public required string QrCode { get; set; }

View File

@ -7,6 +7,6 @@ public class AddTicket
{ {
public TicketType Type { get; set; } public TicketType Type { get; set; }
public Guid EventId { get; set; } public Guid EventId { get; set; }
public Guid SeasonId { get; set; } = Guid.Empty; public Guid? SeasonId { get; set; } = Guid.Empty;
public required Patron Patron { get; set; } public required Patron Patron { get; set; }
} }

View File

@ -0,0 +1,12 @@
using models.Core;
namespace models.Response;
public class EventDetails
{
public DateTime Date { get; set; }
public required string Name { get; set; }
public string? Description { get; set; }
public required Venue Venue { get; set; }
public required Talent Talent { get; set; }
}

View File

@ -1,9 +1,9 @@
using models.Enumerations; using models.Core;
namespace models.Response; namespace models.Response;
public class MintResponse public class MintResponse
{ {
public required string QrCode { get; set; } public required Ticket Ticket { get; set; }
public TicketType Type { get; set; } public required EventDetails Details { get; set; }
} }

View File

@ -21,6 +21,7 @@
"@zxing/browser": "^0.1.5", "@zxing/browser": "^0.1.5",
"@zxing/library": "^0.21.3", "@zxing/library": "^0.21.3",
"@zxing/ngx-scanner": "^19.0.0", "@zxing/ngx-scanner": "^19.0.0",
"guid-typescript": "^1.0.9",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.15.0" "zone.js": "~0.15.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,7 +1,22 @@
@if(qrCode().length > 0) { @if (ticket$() !== null) {
<div class="card"> <div class="card column">
<div class="card row">
<img src="pso-logo.png" width="250" height="100" alt="Parma Symphony Orchestra Logo"/>
</div>
<div class="row"> <div class="row">
<img class="qrcode" alt="Embedded QR Code" src="data:image/png;base64,{{qrCode().toString()}}" /> <img class="qrcode" alt="Embedded QR Code" src="data:image/png;base64,{{qrCode}}"/>
</div>
<div class="row">
<div class="card">
<span class="row">{{talent?.name}}</span>
<span class="row">{{details?.name}}</span>
<span class="row">{{details?.description}}</span>
<span class="row">{{details?.date | date: 'medium'}}</span>
<span class="row">{{venue?.name}}</span>
<span class="row">{{venue?.description}}</span>
<span class="row">{{venue?.addressOne}}</span>
<span class="row">{{venue?.city}}, {{venue?.state}} {{venue?.zip}}</span>
</div>
</div> </div>
</div> </div>
} }

View File

@ -1,13 +1,20 @@
import {Component, inject} from '@angular/core'; import {Component, inject} from '@angular/core';
import {TicketService} from '../../services/ticket.service'; import {TicketService} from '../../services/ticket.service';
import {DatePipe} from '@angular/common';
@Component({ @Component({
selector: 'app-generated-ticket', selector: 'app-generated-ticket',
imports: [], imports: [
DatePipe
],
templateUrl: './generated-ticket.component.html', templateUrl: './generated-ticket.component.html',
styleUrl: './generated-ticket.component.scss' styleUrl: './generated-ticket.component.scss'
}) })
export class GeneratedTicketComponent { export class GeneratedTicketComponent {
private ticket = inject(TicketService); private ticket = inject(TicketService);
public qrCode = this.ticket.dataSignal$; public ticket$ = this.ticket.mintResponse$;
public qrCode = this.ticket$()?.ticket.qrCode;
public details = this.ticket$()?.details;
public talent = this.ticket$()?.details.talent;
public venue = this.ticket$()?.details.venue;
} }

View File

@ -1,21 +1,29 @@
@if (ticketSearch$() != null) { @if (ticketSearch$() != null) {
<div class="card"> <div class="card">
<!-- Show Validity, If Valid Show Ticket Type and Indicate Season --> <!-- Show Validity, If Valid Show Ticket Type and Indicate Season -->
<div class="column"> <table>
<div class="column"> <tr>
<img [src]="getValidityImage()" width="100px" height="100px" [alt]="getValidityText()"/> <td>
<span class="row">{{getValidityText()}}</span> <img [src]="getValidityImage()" width="100px" height="100px" [alt]="getValidityText()"/>
</div> </td>
@if(ticketValidity() === TicketValidity.Valid) { <td>{{ getValidityText() }}</td>
<div class="column"> </tr>
<img [src]="getTypeImage()" width="50px" height="50px" [alt]="getTypeText()"/> @if (ticketValidity() === TicketValidity.Valid) {
<span class="row">{{getTypeText()}}</span> <tr>
</div> <td>
@if(IsSeasonTicket(ticketType())) { <img [src]="getTypeImage()" width="100px" height="100px" [alt]="getTypeText()"/>
<img src="season.svg" width="50px" height="50px" alt="Season Ticket"/> </td>
<span class="row">Seasonal</span> <td>{{ getTypeText() }}</td>
</tr>
@if (IsSeasonTicket(ticketType())) {
<tr>
<td>
<img src="season.svg" width="100px" height="100px" alt="Season Ticket"/>
</td>
<td>Seasonal</td>
</tr>
} }
} }
</div> </table>
</div> </div>
} }

View File

@ -2,6 +2,7 @@ import {Component, inject} from '@angular/core';
import {ZXingScannerModule} from '@zxing/ngx-scanner'; import {ZXingScannerModule} from '@zxing/ngx-scanner';
import {ScanResultComponent} from '../../components/scan-result/scan-result.component'; import {ScanResultComponent} from '../../components/scan-result/scan-result.component';
import {ScanService} from '../../services/scan.service'; import {ScanService} from '../../services/scan.service';
import {TicketTypeEnum} from '../../../models/enums/ticket-type.enum';
@Component({ @Component({
selector: 'app-scan', selector: 'app-scan',
@ -22,7 +23,7 @@ export class ScanComponent {
public click(): void { public click(): void {
this.hideScanner = !this.hideScanner; this.hideScanner = !this.hideScanner;
this.scan.ticketValid$.set(null); this.scan.ticketValid$.set(null);
this.scan.ticketType$.set(null); this.scan.ticketType$.set(TicketTypeEnum.Null);
this.scan.ticketSearch$.set(null); this.scan.ticketSearch$.set(null);
} }
} }

View File

@ -11,6 +11,6 @@
<button class="button" (click)="saveTicket()">Save Ticket</button> <button class="button" (click)="saveTicket()">Save Ticket</button>
</div> </div>
@if(qrCode().length > 0) { @if(ticket$() !== null) {
<app-generated-ticket /> <app-generated-ticket />
} }

View File

@ -7,6 +7,7 @@ import {AddTicket} from '../../../models/request/add-ticket';
import {SeasonBrowserComponent} from '../../components/season-browser/season-browser.component'; import {SeasonBrowserComponent} from '../../components/season-browser/season-browser.component';
import {IsSeasonTicket} from '../../../models/enums/ticket-type.enum'; import {IsSeasonTicket} from '../../../models/enums/ticket-type.enum';
import {GeneratedTicketComponent} from '../../components/generated-ticket/generated-ticket.component'; import {GeneratedTicketComponent} from '../../components/generated-ticket/generated-ticket.component';
import {Guid} from 'guid-typescript';
@Component({ @Component({
selector: 'app-ticket', selector: 'app-ticket',
@ -27,7 +28,7 @@ export class TicketComponent {
@ViewChild(SeasonBrowserComponent) seasonBrowserComponent!: SeasonBrowserComponent; @ViewChild(SeasonBrowserComponent) seasonBrowserComponent!: SeasonBrowserComponent;
public ticketService = inject(TicketService); public ticketService = inject(TicketService);
public qrCode = this.ticketService.dataSignal$; public ticket$ = this.ticketService.mintResponse$;
public saveTicket(): void { public saveTicket(): void {
const addTicket: AddTicket = { const addTicket: AddTicket = {
@ -35,7 +36,7 @@ export class TicketComponent {
type: this.ticketTypeSelector.selectedTicketType, type: this.ticketTypeSelector.selectedTicketType,
seasonId: seasonId:
IsSeasonTicket(this.ticketTypeSelector.selectedTicketType) IsSeasonTicket(this.ticketTypeSelector.selectedTicketType)
? this.seasonBrowserComponent.selectedSeasonId : '', ? this.seasonBrowserComponent.selectedSeasonId : null,
patron: this.patronInfoComponent.buildPatron() patron: this.patronInfoComponent.buildPatron()
} }

View File

@ -14,7 +14,7 @@ import {SnackbarService} from './snackbar.service';
}) })
export class ScanService extends ApiUtils { export class ScanService extends ApiUtils {
public ticketValid$: WritableSignal<TicketValidity | null> = signal(TicketValidity.Invalid); public ticketValid$: WritableSignal<TicketValidity | null> = signal(TicketValidity.Invalid);
public ticketType$: WritableSignal<TicketTypeEnum | null> = signal(TicketTypeEnum.Null); public ticketType$: WritableSignal<TicketTypeEnum> = signal(TicketTypeEnum.Null);
public ticketSearch$: WritableSignal<TicketSearch | null> = signal(null); public ticketSearch$: WritableSignal<TicketSearch | null> = signal(null);
private httpClient = inject(HttpClient); private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService); private snackbar = inject(SnackbarService);

View File

@ -8,12 +8,13 @@ import {AddTicket} from '../../models/request/add-ticket';
import {ApiUtils} from './api-utils'; import {ApiUtils} from './api-utils';
import {TicketSearch} from '../../models/response/ticket-search'; import {TicketSearch} from '../../models/response/ticket-search';
import {SnackbarService} from './snackbar.service'; import {SnackbarService} from './snackbar.service';
import {Ticket} from '../../models/core/ticket';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class TicketService extends ApiUtils { export class TicketService extends ApiUtils {
public dataSignal$: WritableSignal<string> = signal(''); public mintResponse$: WritableSignal<MintResponse | null> = signal(null);
private httpClient = inject(HttpClient); private httpClient = inject(HttpClient);
private snackbar = inject(SnackbarService); private snackbar = inject(SnackbarService);
@ -34,7 +35,7 @@ export class TicketService extends ApiUtils {
}) })
).subscribe((res: MintResponse) => { ).subscribe((res: MintResponse) => {
if (res !== undefined) { if (res !== undefined) {
this.dataSignal$.set(res.qrCode); this.mintResponse$.set(res);
} }
}); });
} }

View File

@ -0,0 +1,10 @@
import {Venue} from '../core/venue';
import {Talent} from '../core/talent';
export interface EventDetails {
date: Date,
name: string,
description: string | null,
venue: Venue,
talent: Talent
}

View File

@ -1,3 +1,7 @@
import {Ticket} from '../core/ticket';
import {EventDetails} from './event-details';
export interface MintResponse { export interface MintResponse {
qrCode: string, ticket: Ticket,
details: EventDetails
} }

File diff suppressed because it is too large Load Diff