15 Commits

Author SHA1 Message Date
50a4c24660 Merge remote-tracking branch 'origin/master' into DtoBuilders 2025-06-02 00:38:24 +02:00
271bf84467 feat: show slightly more information in event list view
instead of event title and organizer, we now also show place and date
2025-06-02 00:37:47 +02:00
AleksDw
fd97b2c2d9 fix apply button when loged off 2025-06-02 00:17:43 +02:00
42fd94e5ac feat: replace manual event search in favor of dto builders 2025-06-01 23:55:43 +02:00
07128948b0 Merge branch 'master' into DtoBuilders 2025-06-01 20:39:24 +02:00
efb71b24d3 fix: offload building DTOs to GUHF
DTO building allows for fully returning correct event's
skills and registrations
2025-06-01 20:33:58 +02:00
AleksDw
aa5caf4375 Update modify.html 2025-06-01 18:21:20 +02:00
AleksDw
26635b4e88 Add leaving event 2025-06-01 17:21:00 +02:00
AleksDw
7e3759927f Add applying to event 2025-06-01 17:13:47 +02:00
AleksDw
b440a0334c Fix api/auth/my_events endpoint 2025-06-01 17:11:01 +02:00
AleksDw
69895f4f35 Revert "Apply to Event"
This reverts commit 5d362e2a39.
2025-06-01 17:10:12 +02:00
AleksDw
5d362e2a39 Apply to Event 2025-06-01 15:06:13 +02:00
AleksDw
a81a57654c Merge branch 'EventRegistrationEndpoints' 2025-06-01 14:20:46 +02:00
AleksDw
48184cd8b6 Add remove endpoint 2025-05-31 02:24:54 +02:00
AleksDw
f2ccde2ea6 Join, leave, registrations endpoints
todo: remove smb from event endpoint
2025-05-31 02:19:01 +02:00
21 changed files with 604 additions and 140 deletions

View File

@@ -1,18 +1,21 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
// Output values in JSON file // Output values in JSON file
public record class EventDetailsDto public record class EventDetailsDto
( {
int EventId, public int EventId { get; set; }
[Required] int? OrganisationId, [Required] public int? OrganisationId { get; set; }
[Required] string? OrganisationName, [Required] public string? OrganisationName { get; set; }
[Required][StringLength(50)] string Title, [Required][StringLength(50)] public string Title { get; set; }
[StringLength(500)] string Description, [StringLength(500)] public string Description { get; set; }
[Required][StringLength(100)] string Location, [Required][StringLength(100)] public string Location { get; set; }
[Required] DateTime? EventDate, [Required] public DateTime? EventDate { get; set; }
ICollection<EventSkill> EventSkills, //ICollection<EventSkill> EventSkills,
ICollection<EventRegistration> EventRegistrations public ICollection<SkillSummaryDto> EventSkills { get; set; }
); public ICollection<EventRegistrationDto> EventRegistrations { get; set; }
public EventDetailsDto() { }
};

View File

@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
using WebApp.Entities;
namespace WebApp.DTOs;
public record class EventRegistrationDto
{
public int EventId { get; set; }
public int UserId { get; set; }
public string UserName { get; set; }
public DateTime RegisteredAt { get; set; }
public EventRegistrationDto() { }
};

View File

@@ -1,17 +1,19 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
// Output values in JSON file // Output values in JSON file
public record class EventSummaryDto( public record class EventSummaryDto {
int EventId, public int EventId { get; set; }
[Required] string Organisation, [Required] public string Organisation { get; set; }
[Required] int OrganisationId, [Required] public int OrganisationId { get; set; }
[Required] [StringLength(50)] string Title, [Required] [StringLength(50)] public string Title { get; set; }
[StringLength(500)] string Description, [StringLength(500)] public string Description { get; set; }
[Required] [StringLength(100)] string Location, [Required] [StringLength(100)] public string Location { get; set; }
[Required] DateTime? EventDate, [Required] public DateTime? EventDate { get; set; }
ICollection<EventSkill> EventSkills, public ICollection<SkillSummaryDto> EventSkills { get; set; }
ICollection<EventRegistration> EventRegistrations public ICollection<EventRegistrationDto> EventRegistrations { get; set; }
);
};

View File

@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
@@ -13,4 +13,5 @@ public record class EventSummaryNoErDto(
[Required][StringLength(100)] string Location, [Required][StringLength(100)] string Location,
[Required] DateTime? EventDate, [Required] DateTime? EventDate,
ICollection<EventSkill> EventSkills ICollection<EventSkill> EventSkills
// ICollection<SkillSummaryDto> EventSkills
); );

View File

@@ -1,9 +1,13 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
public record class SkillSummaryDto public record class SkillSummaryDto
( {
[Required] int SkillId, public int? SkillId { get; set; }
[Required] string SkillName public string? SkillName { get; set; }
);
public SkillSummaryDto() { }
};

View File

@@ -99,9 +99,16 @@ namespace WebApp.Endpoints
if(!user.IsOrganisation) if(!user.IsOrganisation)
{ {
var events = await context.EventRegistrations
var eventIds = await context.EventRegistrations
.Where(er => er.UserId == user.UserId) .Where(er => er.UserId == user.UserId)
.Select(er => er.Event.ToEventSummaryNoErDto()) .Select(er => er.EventId)
.ToListAsync();
var events = await context.Events
.Where(e => eventIds.Contains(e.EventId))
.Include(e => e.Organisation)
.Select(e => e.ToEventSummaryDto())
.ToListAsync(); .ToListAsync();
return Results.Ok(events); return Results.Ok(events);
@@ -222,7 +229,7 @@ namespace WebApp.Endpoints
.Include(vs => vs.Skill) .Include(vs => vs.Skill)
.Select(vs => new .Select(vs => new
{ {
skillId = vs.Skill.SkillId, skillId = vs.Skill!.SkillId,
skillName = vs.Skill.Name skillName = vs.Skill.Name
}) })
.ToListAsync(); .ToListAsync();

View File

@@ -0,0 +1,135 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
using WebApp.Data;
using WebApp.DTOs;
using WebApp.Entities;
using WebApp.Mapping;
namespace WebApp.Endpoints
{
public static class EventsRegistrationEndpoints
{
const string GetEventEndpointRegistrationName = "GetEventRegistration";
public static RouteGroupBuilder MapEventsRegistrationEndpoints(this WebApplication app)
{
var group = app.MapGroup("api/events")
.WithParameterValidation();
// POST /api/events/join/{id}
group.MapPost("/join/{id}",
async (int id, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{
Event? Eve = await dbContext.Events.FindAsync(id);
if (Eve is null)
return Results.Json(new { success = false, error_msg = "Event not found." });
Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
User? user = await guhf.GetUserFromToken(token);
if (user is null || user.IsOrganisation)
return Results.Json(new { success = false, error_msg = "Unauthorized or organisations cannot register for events." });
if (await dbContext.EventRegistrations.AnyAsync(er => er.UserId == user.UserId && er.EventId == id))
return Results.Json(new { success = false, error_msg = "You are already registered for this event." });
if (Eve.EventDate < DateTime.UtcNow)
return Results.Json(new { success = false, error_msg = "This event has already ended." });
EventRegistration registration = new EventRegistration
{
UserId = user.UserId,
EventId = id,
RegisteredAt = DateTime.UtcNow
};
dbContext.EventRegistrations.Add(registration);
await dbContext.SaveChangesAsync();
return Results.Json(new { success = true });
});
// POST /api/events/leave/{id}
group.MapPost("/leave/{id}",
async (int id, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{
Event? Eve = await dbContext.Events.FindAsync(id);
if (Eve is null)
return Results.Json(new { success = false, error_msg = "Event not found." });
Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
User? user = await guhf.GetUserFromToken(token);
if (user is null)
return Results.Json(new { success = false, error_msg = "Unauthorized." });
if (!await dbContext.EventRegistrations.AnyAsync(er => er.UserId == user.UserId && er.EventId == id))
return Results.Json(new { success = false, error_msg = "You are not registered for this event." });
if (Eve.EventDate < DateTime.UtcNow)
return Results.Json(new { success = false, error_msg = "This event has already ended." });
EventRegistration? registration = await dbContext.EventRegistrations
.FirstOrDefaultAsync(er => er.UserId == user.UserId && er.EventId == id);
dbContext.EventRegistrations.Remove(registration);
await dbContext.SaveChangesAsync();
return Results.Json(new { success = true });
});
// GET /api/events/registrations/{id}
group.MapGet("/registrations/{id}",
async (int id, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{
Event? Eve = await dbContext.Events.FindAsync(id);
if (Eve is null)
return Results.Json(new { success = false, error_msg = "Event not found." });
Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
Organisation? org = await guhf.GetOrganisationFromToken(token);
if (org is null || org.OrganisationId != Eve.OrganisationId)
return Results.Json(new { success = false, error_msg = "Unauthorized." });
var registrations = await dbContext.EventRegistrations
.Where(er => er.EventId == id)
.Select(er => er.ToEventRegistrationDto())
.ToListAsync();
return Results.Json(new
{
success = true,
registrations
});
});
// POST /api/events/remove/{id}/{userId}
group.MapPost("/remove/{id}/{userId}",
async (int id, int userId, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{
Event? Eve = await dbContext.Events.FindAsync(id);
if (Eve is null)
return Results.Json(new { success = false, error_msg = "Event not found." });
Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
Organisation? org = await guhf.GetOrganisationFromToken(token);
if (org is null || org.OrganisationId != Eve.OrganisationId)
return Results.Json(new { success = false, error_msg = "Unauthorized." });
EventRegistration? registration = await dbContext.EventRegistrations
.FirstOrDefaultAsync(er => er.UserId == userId && er.EventId == id);
if (registration is null)
return Results.Json(new { success = false, error_msg = "Registration not found." });
dbContext.EventRegistrations.Remove(registration);
await dbContext.SaveChangesAsync();
return Results.Json(new { success = true });
});
return group;
}
}
}

View File

@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Intrinsics.Arm;
using WebApp.Data; using WebApp.Data;
using WebApp.DTOs; using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
@@ -18,27 +20,24 @@ namespace WebApp.Endpoints
// GET /events // GET /events
group.MapGet("/", group.MapGet("/",
async (ApplicationDbContext dbContext, HttpContext httpContext) => async (ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{ {
var sort = httpContext.Request.Query["sort"].ToString(); // Sprawdź, czy lista powinna by posortowana rosnąco. Domyślnie: malejąco.
IOrderedQueryable<Event> res; var sort = httpContext.Request.Query["sort"].ToString().ToUpper();
var r = dbContext.Events
.Include(Eve => Eve.Organisation);
if (sort is not null && sort.ToUpper() == "ASC") // Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
{ Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
res = r.OrderBy(Eve => Eve.EventId); Organisation? org = await guhf.GetOrganisationFromToken(token);
}
else List<EventSummaryDto> result = await guhf.BuildSummaryEventsDto(
{ dbContext,
res = r.OrderByDescending(Eve => Eve.EventId); org,
} (sort == "ASC")
);
return Results.Ok(result);
return await res
.Select(Eve => Eve.ToEventSummaryDto()) //EventSummaryDto
.AsNoTracking()
.ToListAsync();
}); });
@@ -46,8 +45,11 @@ namespace WebApp.Endpoints
group.MapGet("/{id}", group.MapGet("/{id}",
async (int id, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) => async (int id, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{ {
Event? Eve = await dbContext.Events.FindAsync(id);
Event? Eve = await dbContext
.Events
.Include(e => e.Organisation)
.FirstOrDefaultAsync(e => e.EventId == id);
if (Eve is null) return Results.NotFound(); if (Eve is null) return Results.NotFound();
// Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której. // Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
@@ -55,16 +57,14 @@ namespace WebApp.Endpoints
Organisation? org = await guhf.GetOrganisationFromToken(token); Organisation? org = await guhf.GetOrganisationFromToken(token);
// Jeśli token należy do organizacji, która utworzyła to wydarzenie, // Jeśli token należy do organizacji, która utworzyła to wydarzenie,
// to zwróć także EventRegistrations. W przeciwnym razie usuń to pole // to zwróć także EventRegistrations. W przeciwnym razie niech będzie to
// przed jego wysłaniem! // puste pole.
if (org is null || org.OrganisationId != Eve.OrganisationId) Eve.EventRegistrations = []; List<EventDetailsDto> result = await guhf.BuildDetailedEventsDto(
dbContext,
org
);
// DLACZEGO? return Results.Ok(result.FirstOrDefault(e => e.EventId == id));
Eve.Organisation = await guhf.GetOrganisationFromId(Eve.OrganisationId);
EventDetailsDto EveDto = Eve.ToEventDetailsDto();
return Results.Ok(EveDto); //EventDetailsDto
}) })
.WithName(GetEventEndpointName); .WithName(GetEventEndpointName);
@@ -155,20 +155,14 @@ namespace WebApp.Endpoints
{ {
// Uzyskaj organizację z tokenu // Uzyskaj organizację z tokenu
var sort = httpContext.Request.Query["sort"].ToString(); var sort = httpContext.Request.Query["sort"].ToString().ToUpper();
Token? token = await guhf.GetTokenFromHTTPContext(httpContext); Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
Organisation? org = await guhf.GetOrganisationFromToken(token); Organisation? org = await guhf.GetOrganisationFromToken(token);
List<EventSummaryDto> SearchCandidates = await guhf.BuildSummaryEventsDto(dbContext, org, sort == "ASC");
List<EventSummaryDto> SearchResults = []; List<EventSummaryDto> SearchResults = [];
List<Event> AllEvents = await dbContext.Events.ToListAsync(); foreach(EventSummaryDto e in SearchCandidates)
if (sort is null || sort.ToUpper() != "ASC")
{
AllEvents.Reverse(); // aby wyświetlało od najnowszych wydarzeń
}
foreach(Event e in AllEvents)
{ {
bool matchFound = true; bool matchFound = true;
// Logika wyszukiwania // Logika wyszukiwania
@@ -207,16 +201,12 @@ namespace WebApp.Endpoints
// Uwaga! Zanim to zrobisz, sprawdź, czy użytkownik // Uwaga! Zanim to zrobisz, sprawdź, czy użytkownik
// jest twórcą danego wydarzenia! Jeżeli nim nie jest, // jest twórcą danego wydarzenia! Jeżeli nim nie jest,
// wyzeruj EventRegistrations! // wyzeruj EventRegistrations!
if (org is null || e.Organisation != org) if (org is null || e.OrganisationId != org.OrganisationId)
{ {
e.EventRegistrations.Clear(); e.EventRegistrations.Clear();
} }
// UWAGA! TO NIE POWINNO TAK DZIAŁAĆ! if (matchFound) SearchResults.Add(e);
// KTOKOLWIEK WIDZIAŁ, KTOKOLWIEK WIE CZEMU Organisation JEST null?
e.Organisation = await guhf.GetOrganisationFromId(e.OrganisationId);
if (matchFound) SearchResults.Add(e.ToEventSummaryDto());
} }
return Results.Ok(SearchResults); return Results.Ok(SearchResults);

View File

@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using WebApp.Data; using WebApp.Data;
using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.Endpoints; namespace WebApp.Endpoints;
@@ -35,12 +36,7 @@ public class GeneralUseHelpers
User? user = await GetUserFromToken(t); User? user = await GetUserFromToken(t);
if (user is not null && user.IsOrganisation) if (user is not null && user.IsOrganisation)
{ {
Organisation? org = await _context.Organisations.FirstOrDefaultAsync(o => o.UserId == t.UserId); Organisation? org = await _context.Organisations.FirstOrDefaultAsync(o => o.UserId == t!.UserId);
if (org is null)
{
Console.WriteLine("!!!");
}
return org; return org;
} }
@@ -112,4 +108,97 @@ public class GeneralUseHelpers
// Sprawdza, czy któreś ze słów pasuje (nawet częściowo) do searchTerm // Sprawdza, czy któreś ze słów pasuje (nawet częściowo) do searchTerm
return words.Any(word => word.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)); return words.Any(word => word.Contains(searchTerm, StringComparison.OrdinalIgnoreCase));
} }
public async Task<List<EventDetailsDto>> BuildDetailedEventsDto(
ApplicationDbContext context,
Organisation? org,
bool sortAscending = false)
{
// https://khalidabuhakmeh.com/ef-core-and-aspnet-core-cycle-issue-and-solution
// Jeśli token należy do organizacji, która utworzyła to wydarzenie,
// to zwróć także EventRegistrations. W przeciwnym razie niech będzie to
// puste pole.
IQueryable<EventDetailsDto> result_iq = context
.Events
.Select(e => new EventDetailsDto
{
EventId = e.EventId,
OrganisationId = e.OrganisationId,
OrganisationName = e.Organisation!.Name,
Title = e.Title,
Description = e.Description ?? "",
Location = e.Location,
EventDate = e.EventDate,
EventSkills = e
.EventSkills
.Select(es => new SkillSummaryDto
{
SkillId = es.SkillId,
SkillName = es.Skill!.Name
}).ToList(),
EventRegistrations = e.Organisation == org ?
e.EventRegistrations
.Select(er => new EventRegistrationDto
{
EventId = er.EventId,
UserId = er.UserId,
UserName = er.User!.FirstName + " " + er.User.LastName,
RegisteredAt = er.RegisteredAt
}).ToList() : null!
});
if (sortAscending) result_iq = result_iq.OrderBy(e => e.EventId);
else result_iq = result_iq.OrderByDescending(e => e.EventId);
return await result_iq.ToListAsync();
}
public async Task<List<EventSummaryDto>> BuildSummaryEventsDto(
ApplicationDbContext context,
Organisation? org,
bool sortAscending = false)
{
// https://khalidabuhakmeh.com/ef-core-and-aspnet-core-cycle-issue-and-solution
// Jeśli token należy do organizacji, która utworzyła to wydarzenie,
// to zwróć także EventRegistrations. W przeciwnym razie niech będzie to
// puste pole.
IQueryable<EventSummaryDto> result_iq = context
.Events
.Select(e => new EventSummaryDto
{
EventId = e.EventId,
OrganisationId = e.OrganisationId,
Organisation = e.Organisation!.Name,
Title = e.Title,
Description = e.Description ?? "",
Location = e.Location,
EventDate = e.EventDate,
EventSkills = e
.EventSkills
.Select(es => new SkillSummaryDto
{
SkillId = es.SkillId,
SkillName = es.Skill!.Name
}).ToList(),
EventRegistrations = e.Organisation == org ?
e.EventRegistrations
.Select(er => new EventRegistrationDto
{
EventId = er.EventId,
UserId = er.UserId,
UserName = er.User!.FirstName + " " + er.User.LastName,
RegisteredAt = er.RegisteredAt
}).ToList() : null!
});
if (sortAscending) result_iq = result_iq.OrderBy(e => e.EventId);
else result_iq = result_iq.OrderByDescending(e => e.EventId);
return await result_iq.ToListAsync();
}
} }

View File

@@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using WebApp.DTOs; using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
@@ -34,44 +34,83 @@ public static class EventMapping
public static EventSummaryDto ToEventSummaryDto(this Event myEvent) public static EventSummaryDto ToEventSummaryDto(this Event myEvent)
{ {
return new EventSummaryDto(
myEvent.EventId, List<SkillSummaryDto> ssdto = new List<SkillSummaryDto>();
myEvent.Organisation!.Name, List<EventRegistrationDto> erdto = new List<EventRegistrationDto>();
myEvent.OrganisationId,
myEvent.Title, foreach (EventSkill es in myEvent.EventSkills)
myEvent.Description, {
myEvent.Location, ssdto.Add(es.ToSkillSummaryDto());
myEvent.EventDate, }
myEvent.EventSkills,
myEvent.EventRegistrations foreach (EventRegistration er in myEvent.EventRegistrations)
); {
erdto.Add(er.ToEventRegistrationDto());
}
return new EventSummaryDto {
EventId = myEvent.EventId,
Organisation = myEvent.Organisation!.Name,
OrganisationId = myEvent.OrganisationId,
Title = myEvent.Title,
Description = myEvent.Description ?? "",
Location = myEvent.Location,
EventDate = myEvent.EventDate,
EventSkills = ssdto,
EventRegistrations = erdto
};
} }
public static EventSummaryNoErDto ToEventSummaryNoErDto(this Event myEvent) public static EventSummaryNoErDto ToEventSummaryNoErDto(this Event myEvent)
{ {
return new EventSummaryNoErDto( return new EventSummaryNoErDto(
myEvent.EventId, myEvent.EventId,
myEvent.Organisation!.Name, myEvent.Organisation!.Name,
myEvent.OrganisationId, myEvent.OrganisationId,
myEvent.Title, myEvent.Title,
myEvent.Description, myEvent.Description ?? "",
myEvent.Location, myEvent.Location,
myEvent.EventDate, myEvent.EventDate,
myEvent.EventSkills myEvent.EventSkills
); );
} }
public static EventRegistrationDto ToEventRegistrationDto(this EventRegistration myER)
{
return new EventRegistrationDto {
EventId = myER.EventId,
UserId = myER.UserId,
UserName = myER.User!.FirstName + " " + myER.User!.LastName,
RegisteredAt = myER.RegisteredAt
};
}
public static EventDetailsDto ToEventDetailsDto(this Event myEvent) public static EventDetailsDto ToEventDetailsDto(this Event myEvent)
{ {
return new EventDetailsDto( List<SkillSummaryDto> ssdto = new List<SkillSummaryDto>();
myEvent.EventId, List<EventRegistrationDto> erdto = new List<EventRegistrationDto>();
myEvent.OrganisationId,
myEvent.Organisation.Name, foreach (EventSkill es in myEvent.EventSkills)
myEvent.Title, {
myEvent.Description, ssdto.Add(es.ToSkillSummaryDto());
myEvent.Location, }
myEvent.EventDate,
myEvent.EventSkills, foreach (EventRegistration er in myEvent.EventRegistrations)
myEvent.EventRegistrations {
); erdto.Add(er.ToEventRegistrationDto());
}
return new EventDetailsDto {
EventId = myEvent.EventId,
OrganisationId = myEvent.OrganisationId,
OrganisationName = myEvent.Organisation!.Name,
Title = myEvent.Title,
Description = myEvent.Description ?? "",
Location = myEvent.Location,
EventDate = myEvent.EventDate,
EventSkills = ssdto,
EventRegistrations = erdto
};
} }
} }

View File

@@ -13,4 +13,12 @@ public static class EventSkillMapping
SkillId = SSDto.Skill, SkillId = SSDto.Skill,
}; };
} }
public static SkillSummaryDto ToSkillSummaryDto(this EventSkill es)
{
return new SkillSummaryDto{
SkillId = es.SkillId,
SkillName = es.Skill.Name
};
}
} }

View File

@@ -1,4 +1,4 @@
using WebApp.DTOs; using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.Mapping namespace WebApp.Mapping
@@ -16,10 +16,10 @@ namespace WebApp.Mapping
public static SkillSummaryDto ToSkillSummaryDto(this Skill s) public static SkillSummaryDto ToSkillSummaryDto(this Skill s)
{ {
return new SkillSummaryDto( return new SkillSummaryDto {
s.SkillId, SkillId = s.SkillId,
s.Name SkillName = s.Name
); };
} }
} }
} }

View File

@@ -54,5 +54,6 @@ app.MapEventsEndpoints();
app.MapOrganizationsEndpoints(); app.MapOrganizationsEndpoints();
app.MapAuthEndpoints(); app.MapAuthEndpoints();
app.MapSkillsEndpoints(); app.MapSkillsEndpoints();
app.MapEventsRegistrationEndpoints();
app.Run(); app.Run();

View File

@@ -1,4 +1,4 @@
import { getEvent, getMyAccount, unhideElementById } from './generalUseHelpers.js'; import { getEvent, getMyAccount, unhideElementById } from './generalUseHelpers.js';
var isAscending: boolean = false; var isAscending: boolean = false;
@@ -57,10 +57,17 @@ async function loadEvents(org_id: number, evs?: Promise<any>) {
//card.innerHTML = ` //card.innerHTML = `
// <span>${ev.title}</span>` // <span>${ev.title}</span>`
// Do odkomentowania kiedy widok podglądu wydarzeń będzie gotowy // Do odkomentowania kiedy widok podglądu wydarzeń będzie gotowy
let formattedDate: string = new Intl.DateTimeFormat('en-US', {
weekday: 'long', // "Monday"
year: 'numeric', // "2023"
month: 'long', // "December"
day: 'numeric' // "1"
}).format(ev.eventDate);
console.log(formattedDate);
card.innerHTML = ` card.innerHTML = `
<span> <span>
<a href="/view.html?event=${ev.eventId}" style="color: #2898BD">${ev.title}</a> <a href="/view.html?event=${ev.eventId}" style="color: #2898BD">${ev.title}</a>
<p style="margin: 0">${ev.organisation}</p> <p style="margin: 0">${ev.organisation} | ${ev.location} | ${formattedDate}</p>
</span>` </span>`
if (org_id == ev.organisationId) { if (org_id == ev.organisationId) {
card.innerHTML += ` card.innerHTML += `

View File

@@ -1,4 +1,4 @@
import { getEvent, getMyAccount, unhideElementById } from './generalUseHelpers.js'; import { getEvent, getMyAccount, unhideElementById, getMyRegisteredEventIds } from './generalUseHelpers.js';
const queryString = window.location.search; const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString); const urlParams = new URLSearchParams(queryString);
@@ -9,6 +9,8 @@ document.addEventListener("DOMContentLoaded", async () => {
var container = document.getElementById("mainContainer"); var container = document.getElementById("mainContainer");
const modifyBtn = document.getElementById("editBtn"); const modifyBtn = document.getElementById("editBtn");
const removeBtn = document.getElementById("removeBtn"); const removeBtn = document.getElementById("removeBtn");
const applyBtn = document.getElementById("applyBtn");
const leaveBtn = document.getElementById("leaveBtn");
var org_id: number = -1; var org_id: number = -1;
try { try {
@@ -28,11 +30,11 @@ document.addEventListener("DOMContentLoaded", async () => {
try { try {
if (eventId) thisEvent = await getEvent(eventId); if (eventId) thisEvent = await getEvent(eventId);
} catch (err) { } catch (err) {
if (container !== null) container.innerHTML = `<p class="text-danger">To wydarzenie nie istnieje! <a href="/" style="color:#2898BD;">Powrót -></a></p>`; if (container !== null) container.innerHTML = `<p class="text-danger">To wydarzenie nie istnieje! <a href="/" style="color:#2898BD;">Powr<EFBFBD>t -></a></p>`;
} }
if (thisEvent == null) { if (thisEvent == null) {
if (container !== null) container.innerHTML = `<p class="text-danger">B³¹d we wczytywaniu wydarzenia. <a href="/" style="color:#2898BD;">Powrót -></a></p>`; if (container !== null) container.innerHTML = `<p class="text-danger">Błąd we wczytywaniu wydarzenia. <a href="/" style="color:#2898BD;">Powrót -></a></p>`;
} else { } else {
const titleText = document.getElementById( "titleText") as HTMLElement; const titleText = document.getElementById( "titleText") as HTMLElement;
@@ -51,14 +53,28 @@ document.addEventListener("DOMContentLoaded", async () => {
organizerText.innerHTML = "Organized by: " + thisEvent.organisationName; organizerText.innerHTML = "Organized by: " + thisEvent.organisationName;
if (org_id == thisEvent.organisationId) { if (org_id == thisEvent.organisationId) {
// U¿ytkownik jest organizacj¹, która // Użytkownik jest organizacją, która
// stworzy³a to wydarzenie // stworzyła to wydarzenie
unhideElementById(document, "editBtn"); unhideElementById(document, "editBtn");
unhideElementById(document, "removeBtn"); unhideElementById(document, "removeBtn");
} else if (org_id == -1) { } else if (org_id == -1) {
// U¿ytkownik jest wolontariuszem // Użytkownik jest wolontariuszem
try {
const registeredIds = await getMyRegisteredEventIds();
const isRegistered = registeredIds.includes(Number(eventId));
if (isRegistered) {
unhideElementById(document, "leaveBtn");
} else {
unhideElementById(document, "applyBtn"); unhideElementById(document, "applyBtn");
} }
} catch {
unhideElementById(document, "applyBtn");
(applyBtn as HTMLButtonElement).textContent = "log in to apply";
(applyBtn as HTMLButtonElement).disabled = true;
}
}
unhideElementById(document, "mainContainer"); unhideElementById(document, "mainContainer");
@@ -76,7 +92,7 @@ document.addEventListener("DOMContentLoaded", async () => {
if (!confirmed) return; if (!confirmed) return;
try { try {
// Wysy³a ¿¹danie DELETE do API // Wysyła żądanie DELETE do API
const response = await fetch(`/api/events/${eventId}`, { const response = await fetch(`/api/events/${eventId}`, {
method: "DELETE" method: "DELETE"
}); });
@@ -94,4 +110,59 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
} }
if (applyBtn) {
applyBtn.addEventListener("click", async (e) => {
try {
const response = await fetch(`/api/events/join/${eventId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
});
const result: {
success: boolean;
error_msg?: string;
} = await response.json();
if (result.success) {
window.location.href = `/view.html?event=${eventId}`;
} else {
alert(`Error: ${result.error_msg ?? "Unknown error occurred."}`);
}
} catch (error) {
console.error("Failed to apply:", error);
alert("Failed to apply.");
}
});
}
if (leaveBtn) {
leaveBtn.addEventListener("click", async (e) => {
try {
const response = await fetch(`/api/events/leave/${eventId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
});
const result: {
success: boolean;
error_msg?: string;
} = await response.json();
if (result.success) {
window.location.href = `/view.html?event=${eventId}`;
} else {
alert(`Error: ${result.error_msg ?? "Unknown error occurred."}`);
}
} catch (error) {
console.error("Failed to leave:", error)
alert("Failed to leave.")
}
});
}
}); });

View File

@@ -36,9 +36,20 @@ export async function getEvent(id: string): Promise<EventData> {
export async function getMyAccount(): Promise<MyAccount> { export async function getMyAccount(): Promise<MyAccount> {
const res = await fetch("/api/auth/my_account"); const res = await fetch("/api/auth/my_account");
if (!res.ok) { if (!res.ok) {
throw Error("U¿ytkownik niezalogowany!"); throw Error("Użytkownik niezalogowany!");
} }
const data = await res.json(); const data = await res.json();
return data; return data;
} }
export async function getMyRegisteredEventIds(): Promise<number[]> {
const res = await fetch("/api/auth/my_events");
if (!res.ok) {
throw Error("Użytkownik niezalogowany!");
}
const events = await res.json();
return events.map((event: { eventId: number }) => event.eventId);
}

View File

@@ -59,13 +59,18 @@ function loadEvents(org_id, evs) {
for (const ev of events) { for (const ev of events) {
const card = document.createElement("div"); const card = document.createElement("div");
card.className = "event-card filled"; card.className = "event-card filled";
//card.innerHTML = `
// <span>${ev.title}</span>` let formattedDate = new Intl.DateTimeFormat('en-US', {
// Do odkomentowania kiedy widok podglądu wydarzeń będzie gotowy weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric' // "1"
}).format(new Date(ev.eventDate));
card.innerHTML = ` card.innerHTML = `
<span> <span>
<a href="/view.html?event=${ev.eventId}" style="color: #2898BD">${ev.title}</a> <a href="/view.html?event=${ev.eventId}" style="color: #2898BD">${ev.title}</a>
<p style="margin: 0">${ev.organisation}</p> <p style="margin: 0">👥 ${ev.organisation} | 📍 ${ev.location} | 📅 ${formattedDate}</p>
</span>`; </span>`;
if (org_id == ev.organisationId) { if (org_id == ev.organisationId) {
card.innerHTML += ` card.innerHTML += `

View File

@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
import { getEvent, getMyAccount, unhideElementById } from './generalUseHelpers.js'; import { getEvent, getMyAccount, unhideElementById, getMyRegisteredEventIds } from './generalUseHelpers.js';
const queryString = window.location.search; const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString); const urlParams = new URLSearchParams(queryString);
const eventId = urlParams.get('event'); const eventId = urlParams.get('event');
@@ -15,6 +15,8 @@ document.addEventListener("DOMContentLoaded", () => __awaiter(void 0, void 0, vo
var container = document.getElementById("mainContainer"); var container = document.getElementById("mainContainer");
const modifyBtn = document.getElementById("editBtn"); const modifyBtn = document.getElementById("editBtn");
const removeBtn = document.getElementById("removeBtn"); const removeBtn = document.getElementById("removeBtn");
const applyBtn = document.getElementById("applyBtn");
const leaveBtn = document.getElementById("leaveBtn");
var org_id = -1; var org_id = -1;
try { try {
var user = yield getMyAccount(); var user = yield getMyAccount();
@@ -36,7 +38,7 @@ document.addEventListener("DOMContentLoaded", () => __awaiter(void 0, void 0, vo
} }
catch (err) { catch (err) {
if (container !== null) if (container !== null)
container.innerHTML = `<p class="text-danger">To wydarzenie nie istnieje! <a href="/" style="color:#2898BD;">Powrót -></a></p>`; container.innerHTML = `<p class="text-danger">To wydarzenie nie istnieje! <a href="/" style="color:#2898BD;">Powr<EFBFBD>t -></a></p>`;
} }
if (thisEvent == null) { if (thisEvent == null) {
if (container !== null) if (container !== null)
@@ -63,8 +65,22 @@ document.addEventListener("DOMContentLoaded", () => __awaiter(void 0, void 0, vo
} }
else if (org_id == -1) { else if (org_id == -1) {
// Użytkownik jest wolontariuszem // Użytkownik jest wolontariuszem
try {
const registeredIds = yield getMyRegisteredEventIds();
const isRegistered = registeredIds.includes(Number(eventId));
if (isRegistered) {
unhideElementById(document, "leaveBtn");
}
else {
unhideElementById(document, "applyBtn"); unhideElementById(document, "applyBtn");
} }
}
catch (_b) {
unhideElementById(document, "applyBtn");
applyBtn.textContent = "log in to apply";
applyBtn.disabled = true;
}
}
unhideElementById(document, "mainContainer"); unhideElementById(document, "mainContainer");
} }
if (modifyBtn) { if (modifyBtn) {
@@ -96,4 +112,52 @@ document.addEventListener("DOMContentLoaded", () => __awaiter(void 0, void 0, vo
} }
})); }));
} }
if (applyBtn) {
applyBtn.addEventListener("click", (e) => __awaiter(void 0, void 0, void 0, function* () {
var _c;
try {
const response = yield fetch(`/api/events/join/${eventId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
});
const result = yield response.json();
if (result.success) {
window.location.href = `/view.html?event=${eventId}`;
}
else {
alert(`Error: ${(_c = result.error_msg) !== null && _c !== void 0 ? _c : "Unknown error occurred."}`);
}
}
catch (error) {
console.error("Failed to apply:", error);
alert("Failed to apply.");
}
}));
}
if (leaveBtn) {
leaveBtn.addEventListener("click", (e) => __awaiter(void 0, void 0, void 0, function* () {
var _d;
try {
const response = yield fetch(`/api/events/leave/${eventId}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
});
const result = yield response.json();
if (result.success) {
window.location.href = `/view.html?event=${eventId}`;
}
else {
alert(`Error: ${(_d = result.error_msg) !== null && _d !== void 0 ? _d : "Unknown error occurred."}`);
}
}
catch (error) {
console.error("Failed to leave:", error);
alert("Failed to leave.");
}
}));
}
})); }));

View File

@@ -29,9 +29,19 @@ export function getMyAccount() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const res = yield fetch("/api/auth/my_account"); const res = yield fetch("/api/auth/my_account");
if (!res.ok) { if (!res.ok) {
throw Error("U<EFBFBD>ytkownik niezalogowany!"); throw Error("Użytkownik niezalogowany!");
} }
const data = yield res.json(); const data = yield res.json();
return data; return data;
}); });
} }
export function getMyRegisteredEventIds() {
return __awaiter(this, void 0, void 0, function* () {
const res = yield fetch("/api/auth/my_events");
if (!res.ok) {
throw Error("Użytkownik niezalogowany!");
}
const events = yield res.json();
return events.map((event) => event.eventId);
});
}

View File

@@ -80,6 +80,7 @@
<script type="module" src="/js/eventModify.js"></script> <script type="module" src="/js/eventModify.js"></script>
<script type="module" src="/js/generalUseHelpers.js"></script> <script type="module" src="/js/generalUseHelpers.js"></script>
<script type="module" src="/js/auth.js"></script>
</body> </body>

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="pl"> <html lang="pl">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@@ -64,6 +64,7 @@
<h4 id="descText"></h4><br /> <h4 id="descText"></h4><br />
<button id="applyBtn" class="button hidden-before-load"><span>Apply</span><span>&#11166;</span></button> <button id="applyBtn" class="button hidden-before-load"><span>Apply</span><span>&#11166;</span></button>
<button id="leaveBtn" class="button hidden-before-load"><span>Leave</span><span>&#11166;</span></button>
<button id="editBtn" class="button hidden-before-load"><span>Modify</span><span>&#11166;</span></button> <button id="editBtn" class="button hidden-before-load"><span>Modify</span><span>&#11166;</span></button>
<button id="removeBtn" class="button hidden-before-load" style="background-color: red;"><span>Remove permanently</span><span>&#11166;</span></button> <button id="removeBtn" class="button hidden-before-load" style="background-color: red;"><span>Remove permanently</span><span>&#11166;</span></button>