Adding Season support

This commit is contained in:
Tara Wilson 2024-12-05 16:47:23 -05:00
parent da24314614
commit 683085b5d3
27 changed files with 349 additions and 19 deletions

View File

@ -14,7 +14,7 @@ namespace api.Controllers;
public class EventController(IEventManager eventManager) : ControllerBase
{
[HttpPost]
public ActionResult Post([FromBody] AddEvent request)
public IActionResult Post([FromBody] AddEvent request)
{
//TODO: Protect Endpoint
@ -35,7 +35,7 @@ public class EventController(IEventManager eventManager) : ControllerBase
/// <param name="request">New Event Information</param>
/// <returns></returns>
[HttpPatch]
public ActionResult Patch([FromBody] PatchEvent request)
public IActionResult Patch([FromBody] PatchEvent request)
{
//TODO: Protect Endpoint
@ -51,16 +51,21 @@ public class EventController(IEventManager eventManager) : ControllerBase
}
/// <summary>
/// Gets a list of all events between a start date and an end date
/// Gets a list of all events between a start date and an end date.
/// If the values are null, all events are returned.
/// </summary>
/// <param name="startDate">Start date for the event search</param>
/// <param name="endDate">End date for the event search</param>
/// <returns></returns>
[HttpGet]
public ActionResult<List<Event>> Get(DateTime startDate, DateTime endDate)
public ActionResult<List<Event>> Get(DateTime? startDate, DateTime? endDate)
{
try
{
if (startDate == null && endDate == null)
{
return Ok(eventManager.GetAllEvents());
}
return Ok(eventManager.GetEvents(startDate, endDate));
}
catch (Exception e)

View File

@ -0,0 +1,75 @@
using api.Interfaces;
using Microsoft.AspNetCore.Mvc;
using models.Core;
using models.Request;
namespace api.Controllers;
/// <summary>
/// Endpoints for Season Management
/// </summary>
/// <param name="seasonManager">Event Manager Service</param>
[ApiController]
[Route("[controller]")]
public class SeasonController(ISeasonManager seasonManager) : ControllerBase
{
/// <summary>
/// Adds a season to the database
/// </summary>
/// <param name="request">Season to add</param>
/// <returns></returns>
[HttpPost]
public IActionResult Post([FromBody] AddSeason request)
{
//TODO: Protect Endpoint
try
{
seasonManager.AddSeason(request);
return Ok();
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}
/// <summary>
/// Gets all seasons
/// </summary>
/// <returns>List of Seasons</returns>
[HttpGet]
public ActionResult<List<Season>> Get()
{
//TODO: Protect Endpoint
try
{
return Ok(seasonManager.GetSeasons());
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}
/// <summary>
/// Adds an event to a season
/// </summary>
/// <param name="seasonId">Season Id</param>
/// <param name="eventId">Event Id</param>
/// <returns></returns>
[HttpPut]
public IActionResult Put(Guid eventId, Guid seasonId)
{
//TODO: Protect Endpoint
try
{
seasonManager.AddEventToSeason(eventId, seasonId);
return Ok();
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}
}

View File

@ -10,5 +10,6 @@ public static class ServiceCollectionExtensions
services.AddScoped<IQrCodeGenerator, QrCodeGenerator>();
services.AddScoped<ITicketManager, TicketManager>();
services.AddScoped<IEventManager, EventManager>();
services.AddScoped<ISeasonManager, SeasonManager>();
}
}

View File

@ -7,5 +7,6 @@ public interface IEventManager
{
void AddEvent(AddEvent request);
void PatchEvent(PatchEvent request);
List<Event> GetEvents(DateTime startDate, DateTime endDate);
List<Event> GetEvents(DateTime? startDate, DateTime? endDate);
List<Event> GetAllEvents();
}

View File

@ -0,0 +1,11 @@
using models.Core;
using models.Request;
namespace api.Interfaces;
public interface ISeasonManager
{
void AddSeason(AddSeason season);
List<Season> GetSeasons();
void AddEventToSeason(Guid eventId, Guid seasonId);
}

View File

@ -54,4 +54,10 @@ Content-Type: application/json
GET {{api_HostAddress}}/event?startDate=2024-12-01&endDate=2024-12-31
Accept: application/json
Content-Type: application/json
###
GET {{api_HostAddress}}/event
Accept: application/json
Content-Type: application/json

View File

@ -0,0 +1,26 @@
@api_HostAddress = http://localhost:5168
@seasonId = 76178cf4-3805-4a66-a3d9-a0223d99c2fa
@eventId = b9f4478b-701b-4223-9aaa-042b6f53b83a
GET {{api_HostAddress}}/season
Accept: application/json
Content-Type: application/json
###
POST {{api_HostAddress}}/season
Accept: application/json
Content-Type: application/json
{
"SeasonName": "PSO 25 - 26",
"SeasonDescription": "Parma Symphony Orchestra's 53rd Season",
"StartDate": "2025-08-01T00:00:00.991Z",
"EndDate": "2026-05-31T00:00:00.991Z"
}
###
PUT {{api_HostAddress}}/season?eventId={{eventId}}&seasonId={{seasonId}}
Accept: application/json
Content-Type: application/json

View File

@ -27,8 +27,13 @@ public class EventManager : IEventManager
new Update().Execute(request);
}
public List<Event> GetEvents(DateTime startDate, DateTime endDate)
public List<Event> GetEvents(DateTime? startDate, DateTime? endDate)
{
return new GetInDates().Execute(startDate, endDate);
}
public List<Event> GetAllEvents()
{
return new GetAll().Execute();
}
}

View File

@ -0,0 +1,34 @@
using api.Interfaces;
using data.Seasons;
using models.Core;
using models.Request;
using AddEvent = data.Seasons.AddEvent;
namespace api.Services;
public class SeasonManager : ISeasonManager
{
public void AddSeason(AddSeason request)
{
var season = new Season
{
Id = Guid.NewGuid(),
Name = request.SeasonName,
Description = request.SeasonDescription,
StartDate = request.StartDate,
EndDate = request.EndDate,
};
new Save().Execute(season);
}
public List<Season> GetSeasons()
{
return new GetAll().Execute();
}
public void AddEventToSeason(Guid eventId, Guid seasonId)
{
new AddEvent().Execute(eventId, seasonId);
}
}

View File

@ -0,0 +1,15 @@
using models.Core;
using MongoDB.Driver;
namespace data.Events;
public class GetAll
{
public List<Event> Execute()
{
var database = MongoFactory.GetDatabase();
var collection = database.GetCollection<Event>("events");
return collection.Find(_ => true).ToList();
}
}

View File

@ -6,7 +6,7 @@ namespace data.Events;
public class GetInDates
{
public List<Event> Execute(DateTime startDate, DateTime endDate)
public List<Event> Execute(DateTime? startDate, DateTime? endDate)
{
var database = MongoFactory.GetDatabase();
var collection = database.GetCollection<Event>("events");

View File

@ -0,0 +1,18 @@
using models.Core;
using MongoDB.Driver;
namespace data.Seasons;
public class AddEvent
{
public void Execute(Guid eventId, Guid seasonId)
{
var database = MongoFactory.GetDatabase();
var collection = database.GetCollection<Season>("seasons");
var filter = Builders<Season>.Filter.Eq(s => s.Id, seasonId);
var update = Builders<Season>.Update.Push("EventIds", eventId);
collection.FindOneAndUpdate(filter, update);
}
}

View File

@ -0,0 +1,15 @@
using models.Core;
using MongoDB.Driver;
namespace data.Seasons;
public class GetAll
{
public List<Season> Execute()
{
var database = MongoFactory.GetDatabase();
var collection = database.GetCollection<Season>("seasons");
return collection.Find(_ => true).ToList();
}
}

View File

@ -0,0 +1,14 @@
using models.Core;
namespace data.Seasons;
public class Save
{
public void Execute(Season season)
{
var database = MongoFactory.GetDatabase();
var collection = database.GetCollection<Season>("seasons");
collection.InsertOne(season);
}
}

View File

@ -0,0 +1,11 @@
namespace models.Core;
public class Season
{
public Guid Id { get; set; }
public required string Name { get; set; }
public string? Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<Guid> EventIds { get; set; } = [];
}

View File

@ -0,0 +1,9 @@
namespace models.Request;
public class AddSeason
{
public required string SeasonName { get; set; }
public string? SeasonDescription { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}

View File

@ -18,6 +18,9 @@
"@angular/platform-browser": "^19.0.0",
"@angular/platform-browser-dynamic": "^19.0.0",
"@angular/router": "^19.0.0",
"@zxing/browser": "^0.1.5",
"@zxing/library": "^0.21.3",
"@zxing/ngx-scanner": "^19.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"

View File

@ -1 +1,14 @@
<p>scan works!</p>
<button class="button" (click)="hideScanner = !hideScanner">
@if(hideScanner) {
Stop
} @else {
Scan
}
</button>
@if(hideScanner) {
<div class="card">
<zxing-scanner (scanSuccess)="onCodeResult($event)"></zxing-scanner>
</div>
} @else {
<app-scan-result />
}

View File

@ -1,11 +1,18 @@
import { Component } from '@angular/core';
import {ZXingScannerModule} from '@zxing/ngx-scanner';
import {ScanResultComponent} from '../../scan-result/scan-result.component';
@Component({
selector: 'app-scan',
imports: [],
imports: [ZXingScannerModule, ScanResultComponent],
templateUrl: './scan.component.html',
styleUrl: './scan.component.scss'
})
export class ScanComponent {
public hideScanner: boolean = false;
public onCodeResult(resultString: string): void {
this.hideScanner = false;
console.log(resultString);
}
}

View File

@ -0,0 +1,5 @@
@if(ticketValidity() !== TicketValidity.Invalid && ticketType() !== TicketTypeEnum.Null) {
<div class="card">
</div>
}

View File

@ -0,0 +1,18 @@
import {Component, inject} from '@angular/core';
import {TicketService} from '../services/ticket.service';
import {TicketValidity} from '../../models/enums/ticket-validity.enum';
import {TicketTypeEnum} from '../../models/enums/ticket-type.enum';
@Component({
selector: 'app-scan-result',
imports: [],
templateUrl: './scan-result.component.html',
styleUrl: './scan-result.component.scss'
})
export class ScanResultComponent {
private ticket = inject(TicketService);
public ticketValidity = this.ticket.ticketValid$;
public ticketType = this.ticket.ticketType$;
protected readonly TicketValidity = TicketValidity;
protected readonly TicketTypeEnum = TicketTypeEnum;
}

View File

@ -1,6 +1,5 @@
import {inject, Injectable, WritableSignal} from '@angular/core';
import {inject, Injectable, signal, WritableSignal} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {signal} from '@angular/core';
import {catchError, map, of} from 'rxjs';
import {environment} from '../../environments/environment';
import {MintResponse} from '../../models/response/mint-response';
@ -8,13 +7,16 @@ import {Endpoints} from '../../models/endpoints';
import {MintRequest} from '../../models/request/mint-request';
import {ApiUtils} from './api-utils';
import {TicketValidity} from '../../models/enums/ticket-validity.enum';
import {TicketTypeEnum} from '../../models/enums/ticket-type.enum';
import {TicketSearch} from '../../models/response/ticket-search';
@Injectable({
providedIn: 'root'
})
export class TicketService extends ApiUtils {
public dataSignal: WritableSignal<string> = signal('');
public validitySignal: WritableSignal<TicketValidity | null> = signal(null);
public dataSignal$: WritableSignal<string> = signal('');
public ticketValid$: WritableSignal<TicketValidity> = signal(TicketValidity.Null);
public ticketType$: WritableSignal<TicketTypeEnum> = signal(TicketTypeEnum.Null);
private httpClient = inject(HttpClient);
constructor() {
@ -34,7 +36,7 @@ export class TicketService extends ApiUtils {
})
).subscribe((res: MintResponse) => {
if (res !== undefined) {
this.dataSignal.set(res.qrCode);
this.dataSignal$.set(res.qrCode);
}
});
}
@ -43,16 +45,17 @@ export class TicketService extends ApiUtils {
const options = this.setHttpRequestOptions();
const url = environment.apiBase + Endpoints.TICKET_SEARCH(ticketId);
this.httpClient.get<TicketValidity>(url, options)
this.httpClient.get<TicketSearch>(url, options)
.pipe(
map((response: any) => response.body),
catchError(error => {
console.log(error);
return of(undefined);
})
).subscribe((res: TicketValidity) => {
).subscribe((res: TicketSearch) => {
if (res !== undefined) {
this.validitySignal.set(res);
this.ticketType$.set(res.ticketType);
this.ticketValid$.set(res.ticketValidity)
}
});
}

View File

@ -8,6 +8,6 @@ import {TicketService} from '../services/ticket.service';
styleUrl: './ticket.component.scss'
})
export class TicketComponent {
private ticketMinter = inject(TicketService);
public qrCode = this.ticketMinter.dataSignal;
private ticket = inject(TicketService);
public qrCode = this.ticket.dataSignal$;
}

View File

@ -1,4 +1,5 @@
export enum TicketTypeEnum {
Null,
Single,
SingleSeason,
Family,

View File

@ -1,4 +1,5 @@
export enum TicketValidity {
Null,
Valid,
Expired,
Early,

View File

@ -2508,6 +2508,34 @@
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
"@zxing/browser@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@zxing/browser/-/browser-0.1.5.tgz#94c0cdbc8ab5114a82dadee54939de6d9bf7e313"
integrity sha512-4Lmrn/il4+UNb87Gk8h1iWnhj39TASEHpd91CwwSJtY5u+wa0iH9qS0wNLAWbNVYXR66WmT5uiMhZ7oVTrKfxw==
optionalDependencies:
"@zxing/text-encoding" "^0.9.0"
"@zxing/library@^0.21.3":
version "0.21.3"
resolved "https://registry.yarnpkg.com/@zxing/library/-/library-0.21.3.tgz#0a4cb777701884131832b05922d7e88ef1b87ab4"
integrity sha512-hZHqFe2JyH/ZxviJZosZjV+2s6EDSY0O24R+FQmlWZBZXP9IqMo7S3nb3+2LBWxodJQkSurdQGnqE7KXqrYgow==
dependencies:
ts-custom-error "^3.2.1"
optionalDependencies:
"@zxing/text-encoding" "~0.9.0"
"@zxing/ngx-scanner@^19.0.0":
version "19.0.0"
resolved "https://registry.yarnpkg.com/@zxing/ngx-scanner/-/ngx-scanner-19.0.0.tgz#7a3fb054bfdba661b00ecb0a100af12fc76ec513"
integrity sha512-2V0617jKVTjZ3ekEf5uMjfjOE0jjazt2GT/zvfGt68ZxjDMYyGMuKj6p+0KP+pjnxiXHE6NwWaVUWfW43uKtvA==
dependencies:
tslib "^2.3.0"
"@zxing/text-encoding@^0.9.0", "@zxing/text-encoding@~0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b"
integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==
abbrev@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf"
@ -6492,6 +6520,11 @@ tree-kill@1.2.2:
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
ts-custom-error@^3.2.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-3.3.1.tgz#8bd3c8fc6b8dc8e1cb329267c45200f1e17a65d1"
integrity sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==
tslib@2.8.1, tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"