Skip to content

Commit d9a351e

Browse files
committed
add-attendee
1 parent 7bb5870 commit d9a351e

34 files changed

+1780
-148
lines changed

API/API.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
<ItemGroup>
2121
<ProjectReference Include="..\Application\Application.csproj" />
22+
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
2223
</ItemGroup>
2324

2425
</Project>

API/Controllers/AccountController.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,21 @@ public async Task<ActionResult<UserDto>> Login(LoginDTO loginDto)
4242
[HttpPost("register")]
4343
public async Task<ActionResult<UserDto>> Register(RegisterDTO registerDTO)
4444
{
45-
if (await _userManager.Users.AnyAsync(x => x.UserName == registerDTO.Username))
45+
if (await _userManager.Users.AnyAsync(x => x.UserName.ToLower() == registerDTO.Username.ToLower()))
4646
{
4747
ModelState.AddModelError("userName", "username taken");
4848
return ValidationProblem();
4949
}
5050

51-
if (await _userManager.Users.AnyAsync(x => x.Email == registerDTO.Email))
51+
if (await _userManager.Users.AnyAsync(x => x.Email.ToLower() == registerDTO.Email.ToLower()))
5252
{
5353
ModelState.AddModelError("email", "email taken");
5454
return ValidationProblem();
5555
}
5656

5757
var user = new AppUser
5858
{
59-
DispalyName = registerDTO.DisplayName,
59+
DisplayName = registerDTO.DisplayName,
6060
Email = registerDTO.Email,
6161
UserName = registerDTO.Username
6262
};
@@ -84,7 +84,7 @@ private UserDto CreateUserObject(AppUser user)
8484
{
8585
return new UserDto
8686
{
87-
DisplayName = user.DispalyName,
87+
DisplayName = user.DisplayName,
8888
Image = null,
8989
Token = _tokenService.CreateToken(user),
9090
Username = user.UserName

API/Controllers/ActivitiesController.cs

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
using Domain;
2-
using MediatR;
32
using Microsoft.AspNetCore.Mvc;
43
using Application.Activities;
5-
using Microsoft.EntityFrameworkCore;
6-
using Persistence;
74
using Microsoft.AspNetCore.Authorization;
85

96
namespace API.Controllers
107
{
11-
[AllowAnonymous]
128
public class ActivitiesController : BaseApiController
139
{
1410
[HttpGet] //api/activities
@@ -29,17 +25,25 @@ public async Task<IActionResult> CreateActivity(Activity activity)
2925
return HandleResult(await Mediator.Send(new Create.Command { Activity = activity }));
3026
}
3127

28+
[Authorize(Policy = "IsActivityHost")]
3229
[HttpPut("{id}")]
3330
public async Task<IActionResult> EditActivity(Guid id, Activity activity)
3431
{
3532
activity.Id = id;
3633
return HandleResult(await Mediator.Send(new Edit.Command { Activity = activity }));
3734
}
3835

36+
[Authorize(Policy = "IsActivityHost")]
3937
[HttpDelete("{id}")]
4038
public async Task<IActionResult> DeleteActivity(Guid id)
4139
{
4240
return HandleResult(await Mediator.Send(new Delete.Command { Id = id }));
4341
}
42+
43+
[HttpPost("{id}/attend")]
44+
public async Task<IActionResult> Attend(Guid id)
45+
{
46+
return HandleResult(await Mediator.Send(new UpdateAttendance.Command { Id = id }));
47+
}
4448
}
4549
}

API/DTOs/UserDto.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ public class UserDto
55
public string DisplayName { get; set; }
66
public string Token { get; set; }
77
public string Image { get; set; }
8-
public string Username { get; set; }
8+
public string Username { get; set; }
99
}
1010
}

API/Extentions/ApplicationServiceExtentions.cs

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using Persistence;
66
using FluentValidation;
77
using FluentValidation.AspNetCore;
8+
using Application.Interfaces;
9+
using Infrastructure.Security;
810

911
namespace API.Extentions
1012
{
@@ -32,6 +34,8 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
3234
services.AddAutoMapper(typeof(MappingProfiles).Assembly);
3335
services.AddFluentValidationAutoValidation();
3436
services.AddValidatorsFromAssemblyContaining<Create>();
37+
services.AddHttpContextAccessor();
38+
services.AddScoped<IUserAccessor, UserAccessor>();
3539

3640

3741
return services;

API/Extentions/IdentityServiceExtentsions.cs

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Text;
22
using API.Services;
3+
using Infrastructure.Security;
34
using Microsoft.AspNetCore.Authentication.JwtBearer;
5+
using Microsoft.AspNetCore.Authorization;
46
using Microsoft.IdentityModel.Tokens;
57
using Persistence;
68

@@ -32,6 +34,15 @@ public static IServiceCollection AddIdentityServices(this IServiceCollection ser
3234
};
3335
});
3436

37+
services.AddAuthorization(opt =>
38+
{
39+
opt.AddPolicy("IsActivityHost", policy =>
40+
{
41+
policy.Requirements.Add(new IsHostRequirement());
42+
});
43+
});
44+
45+
services.AddTransient<IAuthorizationHandler, IsHostRequirementHandler>();
3546
services.AddScoped<TokenService>();
3647

3748
return services;

API/reactivities.db

-104 KB
Binary file not shown.

API/reactivities.db-shm

0 Bytes
Binary file not shown.

API/reactivities.db-wal

245 KB
Binary file not shown.

Application/Activities/ActivityDto.cs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Application.Profiles;
2+
3+
namespace Application.Activities
4+
{
5+
public class ActivityDto
6+
{
7+
public Guid Id { get; set; }
8+
public string Title { get; set; }
9+
public DateTime Date { get; set; }
10+
public string Description { get; set; }
11+
public string Category { get; set; }
12+
public string City { get; set; }
13+
public string Venue { get; set; }
14+
public string HostUsername { get; set; }
15+
public bool IsCancelled { get; set; }
16+
public ICollection<Profile> Attendees { get; set; }
17+
}
18+
}

Application/Activities/Create.cs

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Application.Core;
2+
using Application.Interfaces;
23
using Domain;
34
using FluentValidation;
45
using MediatR;
6+
using Microsoft.EntityFrameworkCore;
57
using Persistence;
68

79
namespace Application.Activities
@@ -24,14 +26,28 @@ public CommandValidator()
2426
public class Handler : IRequestHandler<Command, Result<Unit>>
2527
{
2628
private readonly DataContext _context;
29+
private readonly IUserAccessor _userAccessor;
2730

28-
public Handler(DataContext context)
31+
public Handler(DataContext context, IUserAccessor userAccessor)
2932
{
30-
_context = context;
33+
_userAccessor = userAccessor;
34+
_context = context;
3135
}
3236

3337
public async Task<Result<Unit>> Handle(Command request, CancellationToken cancellationToken)
3438
{
39+
var user = await _context.Users.FirstOrDefaultAsync(x =>
40+
x.UserName == _userAccessor.GetUserName());
41+
42+
var attendee = new ActivityAttendee
43+
{
44+
AppUser = user,
45+
Activity = request.Activity,
46+
IsHost = true
47+
};
48+
49+
request.Activity.Attendees.Add(attendee);
50+
3551
_context.Activities.Add(request.Activity);
3652

3753
var result = await _context.SaveChangesAsync() > 0;

Application/Activities/Details.cs

+17-9
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,35 @@
22
using Domain;
33
using MediatR;
44
using Persistence;
5+
using AutoMapper;
6+
using AutoMapper.QueryableExtensions;
7+
using Microsoft.EntityFrameworkCore;
58

69
namespace Application.Activities
710
{
811
public class Details
912
{
10-
public class Query : IRequest<Result<Activity>>
13+
public class Query : IRequest<Result<ActivityDto>>
1114
{
1215
public Guid Id { get; set; }
1316
}
1417

15-
public class Handler : IRequestHandler<Query, Result<Activity>>
18+
public class Handler : IRequestHandler<Query, Result<ActivityDto>>
1619
{
17-
private readonly DataContext _context;
18-
public Handler(DataContext context) {
19-
_context = context;
20-
}
21-
public async Task<Result<Activity>> Handle(Query request, CancellationToken cancellationToken)
20+
private readonly DataContext _context;
21+
private readonly IMapper _mapper;
22+
public Handler(DataContext context, IMapper mapper)
2223
{
23-
var activity = await _context.Activities.FindAsync(request.Id);
24+
_mapper = mapper;
25+
_context = context;
26+
}
27+
public async Task<Result<ActivityDto>> Handle(Query request, CancellationToken cancellationToken)
28+
{
29+
var activity = await _context.Activities
30+
.ProjectTo<ActivityDto>(_mapper.ConfigurationProvider)
31+
.FirstOrDefaultAsync(x => x.Id == request.Id);
2432

25-
return Result<Activity>.Success(activity);
33+
return Result<ActivityDto>.Success(activity);
2634
}
2735
}
2836
}

Application/Activities/List.cs

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using Application.Core;
2+
using AutoMapper;
3+
using AutoMapper.QueryableExtensions;
24
using Domain;
35
using MediatR;
46
using Microsoft.EntityFrameworkCore;
@@ -8,20 +10,26 @@ namespace Application.Activities
810
{
911
public class List
1012
{
11-
public class Query : IRequest<Result<List<Activity>>> { }
13+
public class Query : IRequest<Result<List<ActivityDto>>> { }
1214

13-
public class Handler : IRequestHandler<Query, Result<List<Activity>>>
15+
public class Handler : IRequestHandler<Query, Result<List<ActivityDto>>>
1416
{
1517
private readonly DataContext _context;
18+
private readonly IMapper _mapper;
1619

17-
public Handler(DataContext context)
20+
public Handler(DataContext context, IMapper mapper)
1821
{
22+
_mapper = mapper;
1923
_context = context;
2024
}
2125

22-
public async Task<Result<List<Activity>>> Handle(Query request, CancellationToken cancellationToken)
26+
public async Task<Result<List<ActivityDto>>> Handle(Query request, CancellationToken cancellationToken)
2327
{
24-
return Result<List<Activity>>.Success(await _context.Activities.ToListAsync());
28+
var activities = await _context.Activities
29+
.ProjectTo<ActivityDto>(_mapper.ConfigurationProvider)
30+
.ToListAsync(cancellationToken);
31+
32+
return Result<List<ActivityDto>>.Success(activities);
2533
}
2634
}
2735
}
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Application.Core;
2+
using Application.Interfaces;
3+
using Domain;
4+
using MediatR;
5+
using Microsoft.EntityFrameworkCore;
6+
using Persistence;
7+
8+
namespace Application.Activities
9+
{
10+
public class UpdateAttendance
11+
{
12+
public class Command : IRequest<Result<Unit>>
13+
{
14+
public Guid Id { get; set; }
15+
}
16+
17+
public class Handler : IRequestHandler<Command, Result<Unit>>
18+
{
19+
private readonly DataContext _context;
20+
private readonly IUserAccessor _userAccessor;
21+
public Handler(DataContext context, IUserAccessor userAccessor)
22+
{
23+
_userAccessor = userAccessor;
24+
_context = context;
25+
}
26+
27+
public async Task<Result<Unit>> Handle(Command request, CancellationToken cancellationToken)
28+
{
29+
var activity = await _context.Activities
30+
.Include(a => a.Attendees).ThenInclude(u => u.AppUser)
31+
.SingleOrDefaultAsync(x => x.Id == request.Id);
32+
33+
if (activity == null) return null;
34+
35+
var user = await _context.Users.FirstOrDefaultAsync(x =>
36+
x.UserName == _userAccessor.GetUserName());
37+
38+
if (user == null) return null;
39+
40+
var hostUserName = activity.Attendees.FirstOrDefault(x => x.IsHost)?.AppUser?.UserName;
41+
42+
var attendance = activity.Attendees.FirstOrDefault(x =>
43+
x.AppUser.UserName == user.UserName);
44+
45+
if (attendance != null && hostUserName == user.UserName)
46+
activity.IsCancelled = !activity.IsCancelled;
47+
48+
if (attendance != null && hostUserName != user.UserName)
49+
activity.Attendees.Remove(attendance);
50+
51+
if (attendance == null)
52+
{
53+
attendance = new ActivityAttendee
54+
{
55+
AppUser = user,
56+
Activity = activity,
57+
IsHost = false
58+
};
59+
60+
activity.Attendees.Add(attendance);
61+
}
62+
var result = await _context.SaveChangesAsync() > 0;
63+
64+
return result ? Result<Unit>.Success(Unit.Value) :
65+
Result<Unit>.Failure("Problem updating attendance");
66+
}
67+
}
68+
}
69+
}

Application/Core/MappingProfiles.cs

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
using Application.Activities;
12
using AutoMapper;
23
using Domain;
34

45
namespace Application.Core
56
{
67
public class MappingProfiles : Profile
8+
{
9+
public MappingProfiles()
710
{
8-
public MappingProfiles()
9-
{
10-
CreateMap<Activity, Activity>();
11-
}
11+
CreateMap<Activity, Activity>();
12+
CreateMap<Activity, ActivityDto>()
13+
.ForMember(d => d.HostUsername, o => o.MapFrom(s =>
14+
s.Attendees.FirstOrDefault(x => x.IsHost).AppUser.UserName));
15+
CreateMap<ActivityAttendee, Profiles.Profile>()
16+
.ForMember(d => d.DisplayName, o => o.MapFrom(s => s.AppUser.DisplayName))
17+
.ForMember(d => d.Username, o => o.MapFrom(s => s.AppUser.UserName))
18+
.ForMember(d => d.Bio, o => o.MapFrom(s => s.AppUser.Bio));
1219
}
20+
}
1321
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Application.Interfaces
2+
{
3+
public interface IUserAccessor
4+
{
5+
string GetUserName();
6+
}
7+
}

Application/Profiles/Profile.cs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Application.Profiles
2+
{
3+
public class Profile
4+
{
5+
public string Username { get; set; }
6+
public string DisplayName { get; set; }
7+
public string Bio { get; set; }
8+
public string Image { get; set; }
9+
}
10+
}

0 commit comments

Comments
 (0)