diff --git a/source/ticketAPI/api/Controllers/EventController.cs b/source/ticketAPI/api/Controllers/EventController.cs index 2101fe9..5c87a00 100644 --- a/source/ticketAPI/api/Controllers/EventController.cs +++ b/source/ticketAPI/api/Controllers/EventController.cs @@ -56,15 +56,16 @@ public class EventController(IEventManager eventManager) : ControllerBase /// /// Start date for the event search /// End date for the event search + /// Season to find events for /// [HttpGet] - public ActionResult> Get(DateTime? startDate, DateTime? endDate) + public ActionResult> Get(DateTime? startDate, DateTime? endDate, string? seasonId) { try { if (startDate == null && endDate == null) { - return Ok(eventManager.GetAllEvents()); + return Ok(seasonId == null ? eventManager.GetAllEvents() : eventManager.GetBySeason(new Guid(seasonId))); } return Ok(eventManager.GetEvents(startDate, endDate)); } @@ -73,4 +74,24 @@ public class EventController(IEventManager eventManager) : ControllerBase return BadRequest(e.Message); } } + + /// + /// Deletes an event + /// + /// Event to delete + /// + [HttpDelete] + public IActionResult Delete(Guid eventId) + { + // TODO: Protect Endpoint + try + { + eventManager.DeleteEvent(eventId); + return Ok(); + } + catch (Exception e) + { + return BadRequest(e.Message); + } + } } \ No newline at end of file diff --git a/source/ticketAPI/api/Controllers/SeasonController.cs b/source/ticketAPI/api/Controllers/SeasonController.cs index de8154c..5013a06 100644 --- a/source/ticketAPI/api/Controllers/SeasonController.cs +++ b/source/ticketAPI/api/Controllers/SeasonController.cs @@ -53,18 +53,39 @@ public class SeasonController(ISeasonManager seasonManager) : ControllerBase } /// - /// Adds an event to a season + /// Updates a season /// - /// Season Id - /// Event Id + /// Updated season information /// - [HttpPut] - public IActionResult Put(Guid eventId, Guid seasonId) + [HttpPatch] + public IActionResult Patch(PatchSeason request) { //TODO: Protect Endpoint + try { - seasonManager.AddEventToSeason(eventId, seasonId); + seasonManager.PatchSeason(request); + return Ok(); + } + catch (Exception e) + { + return BadRequest(e.Message); + } + } + + /// + /// Deletes a season + /// + /// SeasonId to delete + /// + [HttpDelete] + public IActionResult Delete(Guid seasonId) + { + //TODO: Protect Endpoint + + try + { + seasonManager.DeleteSeason(seasonId); return Ok(); } catch (Exception e) diff --git a/source/ticketAPI/api/Interfaces/IEventManager.cs b/source/ticketAPI/api/Interfaces/IEventManager.cs index 5c8dd6b..ff93f8a 100644 --- a/source/ticketAPI/api/Interfaces/IEventManager.cs +++ b/source/ticketAPI/api/Interfaces/IEventManager.cs @@ -11,4 +11,6 @@ public interface IEventManager List GetEvents(DateTime? startDate, DateTime? endDate); List GetAllEvents(); EventDetails GetEvent(Guid id); + void DeleteEvent(Guid eventId); + List GetBySeason(Guid seasonId); } \ No newline at end of file diff --git a/source/ticketAPI/api/Interfaces/ISeasonManager.cs b/source/ticketAPI/api/Interfaces/ISeasonManager.cs index 65279fd..bab6c61 100644 --- a/source/ticketAPI/api/Interfaces/ISeasonManager.cs +++ b/source/ticketAPI/api/Interfaces/ISeasonManager.cs @@ -7,5 +7,6 @@ public interface ISeasonManager { void AddSeason(AddSeason season); List GetSeasons(); - void AddEventToSeason(Guid eventId, Guid seasonId); + void PatchSeason(PatchSeason request); + void DeleteSeason(Guid seasonId); } \ No newline at end of file diff --git a/source/ticketAPI/api/RestFiles/event.http b/source/ticketAPI/api/RestFiles/event.http index ec530f1..a004bdf 100644 --- a/source/ticketAPI/api/RestFiles/event.http +++ b/source/ticketAPI/api/RestFiles/event.http @@ -62,4 +62,10 @@ Content-Type: application/json GET {{api_HostAddress}}/event Accept: application/json +Content-Type: application/json + +### + +GET {{api_HostAddress}}/event?seasonId={{seasonId}} +Accept: application/json Content-Type: application/json \ No newline at end of file diff --git a/source/ticketAPI/api/Services/EmailService.cs b/source/ticketAPI/api/Services/EmailService.cs index 4ba86fc..2c8fc3b 100644 --- a/source/ticketAPI/api/Services/EmailService.cs +++ b/source/ticketAPI/api/Services/EmailService.cs @@ -18,7 +18,13 @@ public class EmailService(IConfiguration config) : IEmailService config.GetSection("Email:Password").Value); client.Credentials = auth; - var from = new MailAddress(config.GetSection("Email:From").Value); + var from = new MailAddress(config.GetSection("Email:From").Value ?? string.Empty); + + if (from.Address != config.GetSection("Email:From").Value) + { + throw new Exception("Invalid email address"); + } + var to = new MailAddress(ticket.Patron.Email); var emailMessage = new MailMessage(from, to); diff --git a/source/ticketAPI/api/Services/EventManager.cs b/source/ticketAPI/api/Services/EventManager.cs index 13bfd3d..6f55893 100644 --- a/source/ticketAPI/api/Services/EventManager.cs +++ b/source/ticketAPI/api/Services/EventManager.cs @@ -22,7 +22,6 @@ public class EventManager : IEventManager }; new Save().Execute(@event); - new data.Seasons.AddEvent().Execute(@event.Id, request.SeasonId); } public void PatchEvent(PatchEvent request) @@ -44,4 +43,14 @@ public class EventManager : IEventManager { return new GetDetails().Execute(id); } + + public void DeleteEvent(Guid eventId) + { + new Delete().Execute(eventId); + } + + public List GetBySeason(Guid seasonId) + { + return new GetBySeason().Execute(seasonId); + } } \ No newline at end of file diff --git a/source/ticketAPI/api/Services/SeasonManager.cs b/source/ticketAPI/api/Services/SeasonManager.cs index 006444c..7a4297a 100644 --- a/source/ticketAPI/api/Services/SeasonManager.cs +++ b/source/ticketAPI/api/Services/SeasonManager.cs @@ -2,7 +2,6 @@ using api.Interfaces; using data.Seasons; using models.Core; using models.Request; -using AddEvent = data.Seasons.AddEvent; namespace api.Services; @@ -27,8 +26,13 @@ public class SeasonManager : ISeasonManager return new GetAll().Execute(); } - public void AddEventToSeason(Guid eventId, Guid seasonId) + public void PatchSeason(PatchSeason request) { - new AddEvent().Execute(eventId, seasonId); + new Update().Execute(request); + } + + public void DeleteSeason(Guid seasonId) + { + new Delete().Execute(seasonId); } } \ No newline at end of file diff --git a/source/ticketAPI/api/Services/TicketManager.cs b/source/ticketAPI/api/Services/TicketManager.cs index 8119a07..5349b58 100644 --- a/source/ticketAPI/api/Services/TicketManager.cs +++ b/source/ticketAPI/api/Services/TicketManager.cs @@ -10,7 +10,6 @@ public class TicketManager : ITicketManager public void SaveMintedTicket(Ticket ticket) { new data.Tickets.Save().Execute(ticket); - new data.Events.AddTicket().Execute(ticket.Id, ticket.EventId); } public TicketSearch SearchTicket(Guid ticketId) diff --git a/source/ticketAPI/data/Events/AddTicket.cs b/source/ticketAPI/data/Events/Delete.cs similarity index 56% rename from source/ticketAPI/data/Events/AddTicket.cs rename to source/ticketAPI/data/Events/Delete.cs index c024de6..d9f2241 100644 --- a/source/ticketAPI/data/Events/AddTicket.cs +++ b/source/ticketAPI/data/Events/Delete.cs @@ -3,16 +3,14 @@ using MongoDB.Driver; namespace data.Events; -public class AddTicket +public class Delete { - public void Execute(Guid ticketId, Guid eventId) + public void Execute(Guid eventId) { var database = MongoFactory.GetDatabase(); - var collection = database.GetCollection("events"); var filter = Builders.Filter.Eq(e => e.Id, eventId); + var collection = database.GetCollection("events"); - var update = Builders.Update.Push("TicketIds", ticketId); - - collection.FindOneAndUpdate(filter, update); + collection.DeleteOne(filter); } } \ No newline at end of file diff --git a/source/ticketAPI/data/Events/Find.cs b/source/ticketAPI/data/Events/Find.cs index f4da66a..6b9ccb7 100644 --- a/source/ticketAPI/data/Events/Find.cs +++ b/source/ticketAPI/data/Events/Find.cs @@ -9,7 +9,7 @@ public class Find { var database = MongoFactory.GetDatabase(); var collection = database.GetCollection("events"); - var filter = Builders.Filter.Eq("Id", eventId); + var filter = Builders.Filter.Eq(e => e.Id, eventId); return collection.Find(filter).FirstOrDefault(); } } \ No newline at end of file diff --git a/source/ticketAPI/data/Events/GetBySeason.cs b/source/ticketAPI/data/Events/GetBySeason.cs new file mode 100644 index 0000000..acf1985 --- /dev/null +++ b/source/ticketAPI/data/Events/GetBySeason.cs @@ -0,0 +1,16 @@ +using models.Core; +using MongoDB.Driver; + +namespace data.Events; + +public class GetBySeason +{ + public List Execute(Guid seasonId) + { + var database = MongoFactory.GetDatabase(); + var collection = database.GetCollection("events"); + var filter = Builders.Filter.Eq(e => e.SeasonId, seasonId); + + return collection.Find(filter).ToList(); + } +} \ No newline at end of file diff --git a/source/ticketAPI/data/Events/Update.cs b/source/ticketAPI/data/Events/Update.cs index 09b2d36..c14beab 100644 --- a/source/ticketAPI/data/Events/Update.cs +++ b/source/ticketAPI/data/Events/Update.cs @@ -6,23 +6,24 @@ namespace data.Events; public class Update { - public bool Execute(PatchEvent request) + public void Execute(PatchEvent request) { var database = MongoFactory.GetDatabase(); var collection = database.GetCollection("events"); - var filter = Builders.Filter.Eq("_id", request.Id); + var filter = Builders.Filter.Eq(e => e.Id, request.Id); var newEvent = new Event { Id = request.Id, + SeasonId = request.SeasonId, Date = request.Date, Name = request.Name, Description = request.Description, Venue = request.Venue, Talent = request.Talent, }; - - return collection.ReplaceOne(filter, newEvent).IsAcknowledged; + + collection.ReplaceOne(filter, newEvent); } } \ No newline at end of file diff --git a/source/ticketAPI/data/Seasons/AddEvent.cs b/source/ticketAPI/data/Seasons/Delete.cs similarity index 57% rename from source/ticketAPI/data/Seasons/AddEvent.cs rename to source/ticketAPI/data/Seasons/Delete.cs index 14efbd3..a2a163b 100644 --- a/source/ticketAPI/data/Seasons/AddEvent.cs +++ b/source/ticketAPI/data/Seasons/Delete.cs @@ -3,16 +3,14 @@ using MongoDB.Driver; namespace data.Seasons; -public class AddEvent +public class Delete { - public void Execute(Guid eventId, Guid seasonId) + public void Execute(Guid seasonId) { var database = MongoFactory.GetDatabase(); - var collection = database.GetCollection("seasons"); var filter = Builders.Filter.Eq(s => s.Id, seasonId); + var collection = database.GetCollection("seasons"); - var update = Builders.Update.Push("EventIds", eventId); - - collection.FindOneAndUpdate(filter, update); + collection.DeleteOne(filter); } } \ No newline at end of file diff --git a/source/ticketAPI/data/Seasons/Update.cs b/source/ticketAPI/data/Seasons/Update.cs new file mode 100644 index 0000000..a617615 --- /dev/null +++ b/source/ticketAPI/data/Seasons/Update.cs @@ -0,0 +1,27 @@ +using models.Core; +using models.Request; +using MongoDB.Driver; + +namespace data.Seasons; + +public class Update +{ + public void Execute(PatchSeason request) + { + var database = MongoFactory.GetDatabase(); + var collection = database.GetCollection("seasons"); + + var filter = Builders.Filter.Eq(s => s.Id, request.Id); + + var newSeason = new Season + { + Id = request.Id, + Name = request.Name, + Description = request.Description, + StartDate = request.StartDate, + EndDate = request.EndDate, + }; + + collection.ReplaceOne(filter, newSeason); + } +} \ No newline at end of file diff --git a/source/ticketAPI/models/Core/Event.cs b/source/ticketAPI/models/Core/Event.cs index 76d60dc..27a9d89 100644 --- a/source/ticketAPI/models/Core/Event.cs +++ b/source/ticketAPI/models/Core/Event.cs @@ -9,5 +9,4 @@ public class Event public string? Description { get; set; } public required Venue Venue { get; set; } public required Talent Talent { get; set; } - public List TicketIds { get; set; } = []; } \ No newline at end of file diff --git a/source/ticketAPI/models/Core/Season.cs b/source/ticketAPI/models/Core/Season.cs index 130d5c7..81d3df7 100644 --- a/source/ticketAPI/models/Core/Season.cs +++ b/source/ticketAPI/models/Core/Season.cs @@ -7,5 +7,4 @@ public class Season public string? Description { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } - public List EventIds { get; set; } = []; } \ No newline at end of file diff --git a/source/ticketAPI/models/Request/PatchEvent.cs b/source/ticketAPI/models/Request/PatchEvent.cs index 3c51f49..197479d 100644 --- a/source/ticketAPI/models/Request/PatchEvent.cs +++ b/source/ticketAPI/models/Request/PatchEvent.cs @@ -5,6 +5,7 @@ namespace models.Request; public class PatchEvent { public Guid Id { get; set; } + public Guid SeasonId { get; set; } public DateTime Date { get; set; } public required string Name { get; set; } public string? Description { get; set; } diff --git a/source/ticketAPI/models/Request/PatchSeason.cs b/source/ticketAPI/models/Request/PatchSeason.cs new file mode 100644 index 0000000..58fdbba --- /dev/null +++ b/source/ticketAPI/models/Request/PatchSeason.cs @@ -0,0 +1,10 @@ +namespace models.Request; + +public class PatchSeason +{ + 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; } +} \ No newline at end of file diff --git a/source/ticketUI/src/app/components/event-browser/event-browser.component.ts b/source/ticketUI/src/app/components/event-browser/event-browser.component.ts index dcee879..399b1cd 100644 --- a/source/ticketUI/src/app/components/event-browser/event-browser.component.ts +++ b/source/ticketUI/src/app/components/event-browser/event-browser.component.ts @@ -17,7 +17,7 @@ export class EventBrowserComponent implements OnInit { public selectedEventId$: WritableSignal = signal(''); public ngOnInit() { - this.eventService.searchAllEvents(); + this.eventService.searchEvents(undefined, undefined, undefined); } public click(eventId: string): void { diff --git a/source/ticketUI/src/app/services/api-utils.ts b/source/ticketUI/src/app/services/api-utils.ts index 8416aac..dcd02b1 100644 --- a/source/ticketUI/src/app/services/api-utils.ts +++ b/source/ticketUI/src/app/services/api-utils.ts @@ -1,31 +1,13 @@ -import {HttpHeaders, HttpParams} from '@angular/common/http'; +import {HttpHeaders} from '@angular/common/http'; export class ApiUtils { - protected setHttpRequestOptions(params?: any): any { + protected setHttpRequestOptions(): any { let headers: HttpHeaders = new HttpHeaders(); headers = headers.set('Content-Type', 'application/json'); - const options: any = { + return { headers, observe: 'response' }; - - if (params) { - options.params = this.setHttpParams(params); - } - - return options; - } - - private setHttpParams(query: object): HttpParams { - const params = new HttpParams(); - for (const key in query) { - // @ts-ignore - if (query[key] && query.hasOwnProperty(key)) { - // @ts-ignore - params.append(key, query[key]); - } - } - return params; } } diff --git a/source/ticketUI/src/app/services/event.service.ts b/source/ticketUI/src/app/services/event.service.ts index 1eb34c9..2eb0c42 100644 --- a/source/ticketUI/src/app/services/event.service.ts +++ b/source/ticketUI/src/app/services/event.service.ts @@ -7,6 +7,7 @@ import {Endpoints} from '../../models/endpoints'; import {catchError, map, of} from 'rxjs'; import {SnackbarService} from './snackbar.service'; import {Event} from '../../models/core/event'; +import {PatchEvent} from '../../models/request/patch-event'; @Injectable({ providedIn: 'root' @@ -21,7 +22,6 @@ export class EventService extends ApiUtils { } public addEvent(request: AddEventRequest): void { - //TODO: Remove hard coded venue and talent information const options = this.setHttpRequestOptions(); const url = environment.apiBase + Endpoints.EVENT; @@ -34,40 +34,80 @@ export class EventService extends ApiUtils { ).subscribe(res => { if(res !== null) { this.snackbar.showMessage('Event added successfully.'); - this.searchAllEvents(); + this.searchEvents(undefined, undefined, undefined); } }); } - public searchAllEvents(): void { + /** + * Searches for events, returns all events if parameters are not provided + * @param startDate If provided with an end date, will search in the date range + * @param endDate If provided with a start date, will search in the date range + * @param seasonId If provided, returns all events for a season + */ + public searchEvents(startDate: Date | undefined, + endDate: Date | undefined, + seasonId: string | undefined): void { + const options = this.setHttpRequestOptions(); + let url: string = ''; + + if (startDate && endDate) { + url = environment.apiBase + Endpoints.EVENT_DATE_SEARCH(startDate, endDate); + } else if (seasonId) { + url = environment.apiBase + Endpoints.EVENT_SEASON_SEARCH(seasonId); + } else { + url = environment.apiBase + Endpoints.EVENT; + } + + this.httpClient.get(url, options) + .pipe( + map((response: any) => response.body), + catchError(error => { + this.snackbar.showError(error.error); + return of(undefined); + }) + ).subscribe(res => { + if(res !== null) { + this.events$.set(res); + } else { + this.snackbar.showError('No events found.'); + } + }); + } + + public updateEvent(request: PatchEvent): void { const options = this.setHttpRequestOptions(); const url = environment.apiBase + Endpoints.EVENT; - this.httpClient.get(url, options) + this.httpClient.patch(url, JSON.stringify(request), options) .pipe( - map((response: any) => response.body), catchError(error => { this.snackbar.showError(error.error); return of(undefined); }) ).subscribe(res => { - this.events$.set(res); - }); + if(res !== null) { + this.snackbar.showMessage('Event updated successfully.'); + this.searchEvents(undefined, undefined, undefined); + } + }) } - public searchEvents(startDate: Date, endDate: Date): void { + public deleteEvent(eventId: string): void { const options = this.setHttpRequestOptions(); - const url = environment.apiBase + Endpoints.EVENT_DATE_SEARCH(startDate, endDate); + const url = environment.apiBase + Endpoints.EVENT_DELETE(eventId); - this.httpClient.get(url, options) + this.httpClient.delete(url, options) .pipe( - map((response: any) => response.body), catchError(error => { this.snackbar.showError(error.error); return of(undefined); }) ).subscribe(res => { - this.events$.set(res); - }); + if(res !== null) { + this.snackbar.showMessage('Event deleted successfully.'); + this.searchEvents(undefined, undefined, undefined); + } + }) } } diff --git a/source/ticketUI/src/app/services/season.service.ts b/source/ticketUI/src/app/services/season.service.ts index ba6b04d..23f895a 100644 --- a/source/ticketUI/src/app/services/season.service.ts +++ b/source/ticketUI/src/app/services/season.service.ts @@ -7,6 +7,7 @@ import {environment} from '../../environments/environment'; import {Endpoints} from '../../models/endpoints'; import {catchError, map, of} from 'rxjs'; import {AddSeason} from '../../models/request/add-season'; +import {PatchSeason} from '../../models/request/patch-season'; @Injectable({ providedIn: 'root' @@ -39,7 +40,7 @@ export class SeasonService extends ApiUtils { } public saveSeason(request: AddSeason): void { - const options = this.setHttpRequestOptions(request); + const options = this.setHttpRequestOptions(); const url = environment.apiBase + Endpoints.SEASON; this.httpClient.post(url, JSON.stringify(request), options) @@ -68,4 +69,40 @@ export class SeasonService extends ApiUtils { }) ).subscribe(); } + + public updateSeason(request: PatchSeason): void { + const options = this.setHttpRequestOptions(); + const url = environment.apiBase + Endpoints.SEASON; + + this.httpClient.patch(url, JSON.stringify(request), options) + .pipe( + catchError(error => { + this.snackbar.showError(error.error); + return of(undefined); + }) + ).subscribe(res => { + if (res !== null) { + this.snackbar.showMessage('Season updated successfully.'); + this.getSeasons(); + } + }); + } + + public deleteSeason(seasonId: string): void { + const options = this.setHttpRequestOptions(); + const url = environment.apiBase + Endpoints.SEASON_DELETE(seasonId); + + this.httpClient.delete(url, options) + .pipe( + catchError(error => { + this.snackbar.showError(error.error); + return of(undefined); + }) + ).subscribe(res => { + if (res !== null) { + this.snackbar.showMessage('Season deleted successfully.'); + this.getSeasons(); + } + }); + } } diff --git a/source/ticketUI/src/models/core/event.ts b/source/ticketUI/src/models/core/event.ts index a5769dc..95ef5d0 100644 --- a/source/ticketUI/src/models/core/event.ts +++ b/source/ticketUI/src/models/core/event.ts @@ -8,6 +8,5 @@ export interface Event { name: string, description: string | null, venue: Venue, - talent: Talent, - ticketIds: Array + talent: Talent } diff --git a/source/ticketUI/src/models/endpoints.ts b/source/ticketUI/src/models/endpoints.ts index 5d46340..d1a04bd 100644 --- a/source/ticketUI/src/models/endpoints.ts +++ b/source/ticketUI/src/models/endpoints.ts @@ -22,6 +22,14 @@ export class Endpoints { return `${Endpoints.EVENT}?startDate=${startDate}&endDate=${endDate}`; } + public static EVENT_SEASON_SEARCH(seasonId: string): string { + return `${Endpoints.EVENT}?seasonId=${seasonId}`; + } + + public static EVENT_DELETE(eventId: string): string { + return `${Endpoints.EVENT}?eventId=${eventId}`; + } + /* Season Routes */ public static readonly SEASON: string = 'season'; @@ -29,4 +37,8 @@ export class Endpoints { public static SEASON_ADD_EVENT(eventId: string, seasonId: string): string { return `${Endpoints.SEASON}?eventId=${eventId}&seasonId=${seasonId}`; } + + public static SEASON_DELETE(seasonId: string): string { + return `${Endpoints.SEASON}?seasonId=${seasonId}`; + } } diff --git a/source/ticketUI/src/models/request/patch-season.ts b/source/ticketUI/src/models/request/patch-season.ts new file mode 100644 index 0000000..a40dd41 --- /dev/null +++ b/source/ticketUI/src/models/request/patch-season.ts @@ -0,0 +1,7 @@ +export interface PatchSeason { + id: string, + name: string, + description: string | null, + startDate: Date, + endDate: Date +}