8 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
426288d728 feat: enable adding relevant skills to events 2025-06-01 03:13:53 +02:00
72fbfe982f feat: skills endpoint, stylistic changes 2025-06-01 02:15:20 +02:00
22 changed files with 490 additions and 206 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

@@ -3,9 +3,13 @@ using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
// Output values in JSON file public record class EventRegistrationDto
public record class EventRegistrationDto( {
int EventId, public int EventId { get; set; }
int UserId, public int UserId { get; set; }
DateTime RegisteredAt 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

@@ -18,6 +18,7 @@ namespace WebApp.Endpoints
var group = app.MapGroup("api/auth") var group = app.MapGroup("api/auth")
.WithParameterValidation(); .WithParameterValidation();
// POST /api/auth/login
group.MapPost("/login", async (LoginDto dto, ApplicationDbContext context, GeneralUseHelpers guh) => group.MapPost("/login", async (LoginDto dto, ApplicationDbContext context, GeneralUseHelpers guh) =>
{ {
var user = await context.WebUsers.FirstOrDefaultAsync(u => u.Email == dto.Email); var user = await context.WebUsers.FirstOrDefaultAsync(u => u.Email == dto.Email);
@@ -38,6 +39,7 @@ namespace WebApp.Endpoints
}); });
}); });
// POST /api/auth/logout
group.MapPost("/logout", async (HttpContext httpContext, GeneralUseHelpers guh) => group.MapPost("/logout", async (HttpContext httpContext, GeneralUseHelpers guh) =>
{ {
var token = await guh.GetTokenFromHTTPContext(httpContext); var token = await guh.GetTokenFromHTTPContext(httpContext);
@@ -54,6 +56,7 @@ namespace WebApp.Endpoints
return Results.Ok(new { success = true }); return Results.Ok(new { success = true });
}); });
// GET /api/auth/my_account
group.MapGet("/my_account", async (HttpContext httpContext, GeneralUseHelpers guh) => group.MapGet("/my_account", async (HttpContext httpContext, GeneralUseHelpers guh) =>
{ {
var token = await guh.GetTokenFromHTTPContext(httpContext); var token = await guh.GetTokenFromHTTPContext(httpContext);
@@ -77,6 +80,7 @@ namespace WebApp.Endpoints
}) })
.WithName(GetUserEndpointName); .WithName(GetUserEndpointName);
// GET /api/auth/my_events
group.MapGet("/my_events", async (HttpContext httpContext, GeneralUseHelpers guh, ApplicationDbContext context) => group.MapGet("/my_events", async (HttpContext httpContext, GeneralUseHelpers guh, ApplicationDbContext context) =>
{ {
var token = await guh.GetTokenFromHTTPContext(httpContext); var token = await guh.GetTokenFromHTTPContext(httpContext);
@@ -129,13 +133,14 @@ namespace WebApp.Endpoints
}); });
// POST /api/auth/add_skill
group.MapPost("/add_skill", async (SingleSkillDto dto, HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) => group.MapPost("/add_skill", async (SingleSkillDto dto, HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) =>
{ {
// Uzyskaj użytkownika z tokenu // Uzyskaj użytkownika z tokenu
Token? token = await guh.GetTokenFromHTTPContext(httpContext); Token? token = await guh.GetTokenFromHTTPContext(httpContext);
User? user = await guh.GetUserFromToken(token); User? user = await guh.GetUserFromToken(token);
// Tylko wolontariusze powinno móc dodawać swoje skille // Tylko wolontariusze powinni móc dodawać swoje skille
if (user == null || user.IsOrganisation) { if (user == null || user.IsOrganisation) {
return Results.Json(new { message = "Unauthorized" }, statusCode: 401); return Results.Json(new { message = "Unauthorized" }, statusCode: 401);
} }
@@ -165,14 +170,14 @@ namespace WebApp.Endpoints
return Results.Json(new { message = "Skill added successfully!" }, statusCode: 201); return Results.Json(new { message = "Skill added successfully!" }, statusCode: 201);
}); });
// POST /api/auth/remove_skill
group.MapPost("/remove_skill", async (SingleSkillDto dto, HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) => group.MapPost("/remove_skill", async (SingleSkillDto dto, HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) =>
{ {
// Uzyskaj użytkownika z tokenu // Uzyskaj użytkownika z tokenu
Token? token = await guh.GetTokenFromHTTPContext(httpContext); Token? token = await guh.GetTokenFromHTTPContext(httpContext);
User? user = await guh.GetUserFromToken(token); User? user = await guh.GetUserFromToken(token);
// Tylko wolontariusze powinien móc usuwac swoje skille // Tylko wolontariusze powinni móc usuwać swoje skille
if (user == null || user.IsOrganisation) if (user == null || user.IsOrganisation)
{ {
return Results.Json(new { message = "Unauthorized" }, statusCode: 401); return Results.Json(new { message = "Unauthorized" }, statusCode: 401);
@@ -185,28 +190,28 @@ namespace WebApp.Endpoints
return Results.Json(new { message = "Skill not found" }, statusCode: 404); return Results.Json(new { message = "Skill not found" }, statusCode: 404);
} }
// Sprawdzamy, czy ten użytkownik ma już taki skill. Jeżeli nie ma, nie ma sensu usuwac go kilkukrotnie. // Sprawdzamy, czy ten użytkownik ma już taki skill. Jeżeli nie ma, to nie ma sensu usuwać czegoś, czego nie ma.
VolunteerSkill? vs = await context.VolunteerSkills.FirstOrDefaultAsync(v => v.UserId == user.UserId && v.SkillId == dto.Skill); VolunteerSkill? vs = await context.VolunteerSkills.FirstOrDefaultAsync(v => v.UserId == user.UserId && v.SkillId == dto.Skill);
if (vs is not null) if (vs is not null)
{ {
// Nie ma - zatem musimy dodać nowy VolunteerSkill do bazy // Ma - zatem musimy usunąć otrzymany VolunteerSkill z bazy
VolunteerSkill newVs = dto.ToVolunteerSkillEntity(user.UserId); VolunteerSkill newVs = dto.ToVolunteerSkillEntity(user.UserId);
await context.VolunteerSkills.Where(v => v.SkillId == dto.Skill) await context.VolunteerSkills.Where(v => v.SkillId == dto.Skill)
.ExecuteDeleteAsync(); .ExecuteDeleteAsync();
} }
else else
{ {
// Ma - (ta para UserId <-> SkillId już istnieje w bazie) użytkownik już ma ten skill // Nie ma - (ta para UserId <-> SkillId nie istnieje w bazie). Zwracamy błąd.
return Results.Json(new { message = "You don't have this skill" }, statusCode: 400); return Results.Json(new { message = "You don't have this skill" }, statusCode: 400);
} }
return Results.Json(new { message = "Skill deleted successfully!" }, statusCode: 201); return Results.Json(new { message = "Skill deleted successfully!" }, statusCode: 201);
}); });
group.MapGet("/get_skills", async (HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) => // GET /api/auth/skills
group.MapGet("/skills", async (HttpContext httpContext, ApplicationDbContext context, GeneralUseHelpers guh) =>
{ {
// Uzyskaj użytkownika z tokenu // Uzyskaj użytkownika z tokenu
Token? token = await guh.GetTokenFromHTTPContext(httpContext); Token? token = await guh.GetTokenFromHTTPContext(httpContext);
@@ -224,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

@@ -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);
@@ -134,7 +134,7 @@ namespace WebApp.Endpoints
// Uzyskaj organizację z tokenu // Uzyskaj organizację z tokenu
Token? token = await guhf.GetTokenFromHTTPContext(httpContext); Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
Organisation? org = await guhf.GetOrganisationFromToken(token); Organisation? org = await guhf.GetOrganisationFromToken(token);
if (org is null) return Results.StatusCode(403); if (org is null) return Results.Unauthorized();
// Sprawdź, czy organizacja ma prawo // Sprawdź, czy organizacja ma prawo
// do usunięcia tego (EventId = id) eventu. // do usunięcia tego (EventId = id) eventu.
@@ -155,21 +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 = [];
foreach(EventSummaryDto e in SearchCandidates)
List<Event> AllEvents = await dbContext.Events.ToListAsync();
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
@@ -182,19 +175,19 @@ namespace WebApp.Endpoints
if (query.TitleOrDescription is not null) if (query.TitleOrDescription is not null)
{ {
var TitleMatch = guhf.SearchString(e.Title, query.TitleOrDescription); var TitleMatch = guhf.SearchString(e.Title, query.TitleOrDescription);
var DescMatch = guhf.SearchString(e.Description, query.TitleOrDescription); var DescMatch = guhf.SearchString(e.Description, query.TitleOrDescription);
if (!TitleMatch && !DescMatch) matchFound = false; if (!TitleMatch && !DescMatch) matchFound = false;
} }
//Zakres dat do wyszukiwania // Zakres dat do wyszukiwania
if(query.EventDateFrom is not null) if (query.EventDateFrom is not null)
{ {
if (e.EventDate < query.EventDateFrom) matchFound = false; if (e.EventDate < query.EventDateFrom) matchFound = false;
} }
if(query.EventDateTo is not null) if (query.EventDateTo is not null)
{ {
if (e.EventDate > query.EventDateTo) matchFound = false; if (e.EventDate > query.EventDateTo) matchFound = false;
} }
@@ -208,21 +201,102 @@ 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);
}); });
// POST /events/1/add_skill
group.MapPost("/{id}/add_skill/",
async (int id, SingleSkillDto dto, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{
Event? Eve = await dbContext.Events.FindAsync(id);
if (Eve is null) return Results.Json(new { message = "Event not found" }, statusCode: 404);
// Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
Organisation? org = await guhf.GetOrganisationFromToken(token);
// Jeśli token należy do organizacji, która utworzyła to wydarzenie,
// to zwróć także EventRegistrations. W przeciwnym razie usuń to pole
// przed jego wysłaniem!
if (org is null || org.OrganisationId != Eve.OrganisationId) return Results.Unauthorized();
// Szukamy skilla w bazie o ID takim, jak w otrzymanym DTO
Skill? skill = await dbContext.Skills.FindAsync(dto.Skill);
if (skill is null)
{
return Results.Json(new { message = "Skill not found" }, statusCode: 404);
}
// Sprawdzamy, czy to wydarzenie nie ma już takiego skilla. Jeżeli ma, nie ma sensu dodawać go kilkukrotnie.
EventSkill? es = await dbContext.EventSkills.FirstOrDefaultAsync(e => e.EventId == id && e.SkillId == dto.Skill);
if (es is null)
{
// Nie ma - zatem musimy dodać nowy EventSkill do bazy
EventSkill newEs = dto.ToEventSkillEntity(Eve.EventId);
dbContext.EventSkills.Add(newEs);
await dbContext.SaveChangesAsync();
}
else
{
// Ma - (ta para EventId <-> SkillId już istnieje w bazie); ten Event posiada już ten skill
return Results.Json(new { message = "Skill already assinged to this event!" }, statusCode: 400);
}
return Results.Json(new { message = "Skill added to event successfully!" }, statusCode: 201);
});
// POST /events/1/renive_skill
group.MapPost("/{id}/remove_skill/",
async (int id, SingleSkillDto dto, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{
Event? Eve = await dbContext.Events.FindAsync(id);
if (Eve is null) return Results.Json(new { message = "Event not found" }, statusCode: 404);
// Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
Token? token = await guhf.GetTokenFromHTTPContext(httpContext);
Organisation? org = await guhf.GetOrganisationFromToken(token);
// Jeśli token należy do organizacji, która utworzyła to wydarzenie,
// to zwróć także EventRegistrations. W przeciwnym razie usuń to pole
// przed jego wysłaniem!
if (org is null || org.OrganisationId != Eve.OrganisationId) return Results.Unauthorized();
// Szukamy skilla w bazie o ID takim, jak w otrzymanym DTO
Skill? skill = await dbContext.Skills.FindAsync(dto.Skill);
if (skill is null)
{
return Results.Json(new { message = "Skill not found" }, statusCode: 404);
}
// Sprawdzamy, czy to wydarzenie nie ma już takiego skilla. Jeżeli nie ma, to nie ma sensu kasować czegoś, czego nie ma.
EventSkill? es = await dbContext.EventSkills.FirstOrDefaultAsync(e => e.EventId == id && e.SkillId == dto.Skill);
if (es is not null)
{
// Ma - zatem musimy usunąć ten EventSkill z bazy
await dbContext.EventSkills.Where(e => e.SkillId == dto.Skill)
.ExecuteDeleteAsync();
}
else
{
// Nie ma - (ta para EventId <-> SkillId nie istnieje w bazie); ten Event nie posiada tego skill'a
return Results.Json(new { message = "This skill isn't assinged to this event!" }, statusCode: 400);
}
return Results.Json(new { message = "Skill removed from event successfully!" }, statusCode: 201);
});
return group; return group;
} }
} }

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

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore;
using WebApp.Data;
using WebApp.Mapping;
namespace WebApp.Endpoints;
public static class SkillsEndpoints
{
const string GetSkillEndpointName = "GetSkill";
public static RouteGroupBuilder MapSkillsEndpoints(this WebApplication app)
{
var group = app.MapGroup("api/skills").WithParameterValidation();
// GET /skills
group.MapGet("/",
async (ApplicationDbContext dbContext) =>
await dbContext.Skills
.OrderBy(Sk => Sk.SkillId)
.Select(Sk => Sk.ToSkillSummaryDto()) // SkillSummaryDto
.AsNoTracking()
.ToListAsync());
return group;
}
}

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

@@ -1,17 +0,0 @@
using WebApp.DTOs;
using WebApp.Entities;
namespace WebApp.Mapping
{
public static class EventRegistrationMapping
{
public static EventRegistrationDto ToEventRegistrationDto(this EventRegistration er)
{
return new EventRegistrationDto(
er.EventId,
er.UserId,
er.RegisteredAt
);
}
}
}

View File

@@ -0,0 +1,24 @@
using WebApp.DTOs;
using WebApp.Entities;
namespace WebApp.Mapping;
public static class EventSkillMapping
{
public static EventSkill ToEventSkillEntity(this SingleSkillDto SSDto, int eid)
{
return new EventSkill()
{
EventId = eid,
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

@@ -1,17 +1,16 @@
using WebApp.DTOs; using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.Mapping namespace WebApp.Mapping;
public static class VolunteerSkillMapping
{ {
public static class VolunteerSkillMapping public static VolunteerSkill ToVolunteerSkillEntity(this SingleSkillDto SSDto, int uid)
{ {
public static VolunteerSkill ToVolunteerSkillEntity(this SingleSkillDto SSDto, int uid) return new VolunteerSkill()
{ {
return new VolunteerSkill() UserId = uid,
{ SkillId = SSDto.Skill,
UserId = uid, };
SkillId = SSDto.Skill,
};
}
} }
} }

View File

@@ -53,6 +53,7 @@ app.UseRouting(); // Enables routing to match incoming request to endpoints
app.MapEventsEndpoints(); app.MapEventsEndpoints();
app.MapOrganizationsEndpoints(); app.MapOrganizationsEndpoints();
app.MapAuthEndpoints(); app.MapAuthEndpoints();
app.MapSkillsEndpoints();
app.MapEventsRegistrationEndpoints(); 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

@@ -59,14 +59,21 @@ document.addEventListener("DOMContentLoaded", async () => {
unhideElementById(document, "removeBtn"); unhideElementById(document, "removeBtn");
} else if (org_id == -1) { } else if (org_id == -1) {
// Użytkownik jest wolontariuszem // Użytkownik jest wolontariuszem
const registeredIds = await getMyRegisteredEventIds(); try {
const isRegistered = registeredIds.includes(Number(eventId)); const registeredIds = await getMyRegisteredEventIds();
const isRegistered = registeredIds.includes(Number(eventId));
if (isRegistered) { if (isRegistered) {
unhideElementById(document, "leaveBtn"); unhideElementById(document, "leaveBtn");
} else { } else {
unhideElementById(document, "applyBtn");
}
} catch {
unhideElementById(document, "applyBtn"); unhideElementById(document, "applyBtn");
(applyBtn as HTMLButtonElement).textContent = "log in to apply";
(applyBtn as HTMLButtonElement).disabled = true;
} }
} }
unhideElementById(document, "mainContainer"); unhideElementById(document, "mainContainer");

View File

@@ -36,7 +36,7 @@ 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<EFBFBD>ytkownik niezalogowany!"); throw Error("Użytkownik niezalogowany!");
} }
const data = await res.json(); const data = await res.json();
return data; return data;

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

@@ -65,13 +65,20 @@ 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
const registeredIds = yield getMyRegisteredEventIds(); try {
const isRegistered = registeredIds.includes(Number(eventId)); const registeredIds = yield getMyRegisteredEventIds();
if (isRegistered) { const isRegistered = registeredIds.includes(Number(eventId));
unhideElementById(document, "leaveBtn"); if (isRegistered) {
unhideElementById(document, "leaveBtn");
}
else {
unhideElementById(document, "applyBtn");
}
} }
else { catch (_b) {
unhideElementById(document, "applyBtn"); unhideElementById(document, "applyBtn");
applyBtn.textContent = "log in to apply";
applyBtn.disabled = true;
} }
} }
unhideElementById(document, "mainContainer"); unhideElementById(document, "mainContainer");
@@ -107,33 +114,9 @@ document.addEventListener("DOMContentLoaded", () => __awaiter(void 0, void 0, vo
} }
if (applyBtn) { if (applyBtn) {
applyBtn.addEventListener("click", (e) => __awaiter(void 0, void 0, void 0, function* () { applyBtn.addEventListener("click", (e) => __awaiter(void 0, void 0, void 0, function* () {
var _b;
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: ${(_b = result.error_msg) !== null && _b !== void 0 ? _b : "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 _c; var _c;
try { try {
const response = yield fetch(`/api/events/leave/${eventId}`, { const response = yield fetch(`/api/events/join/${eventId}`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
@@ -147,6 +130,30 @@ document.addEventListener("DOMContentLoaded", () => __awaiter(void 0, void 0, vo
alert(`Error: ${(_c = result.error_msg) !== null && _c !== void 0 ? _c : "Unknown error occurred."}`); 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) { catch (error) {
console.error("Failed to leave:", error); console.error("Failed to leave:", error);
alert("Failed to leave."); alert("Failed to leave.");

View File

@@ -29,7 +29,7 @@ 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;

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@@ -68,12 +68,16 @@
<input type="password" id="password" class="form-control input-field" required /> <input type="password" id="password" class="form-control input-field" required />
</div> </div>
<br/> <br />
<button id="logInBtn" class="button" type="submit"> <button id="logInBtn" class="button" type="submit">
<span>Log in</span> <span>Log in</span>
<span>&#11166;</span> <span>&#11166;</span>
</button> </button>
<button id="signUpBtn" class="button" type="button" onclick="alert('Coming soon!')">
<span>Sign up</span>
<span>&#11166;</span>
</button>
<p id="message" style="color: red;"></p> <p id="message" style="color: red;"></p>
</form> </form>