Skip to content

Commit dcc55aa

Browse files
committed
add-following-server
1 parent 4ad9e13 commit dcc55aa

19 files changed

+774
-23
lines changed

API/Controllers/FollowController.cs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Application.Followers;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
namespace API.Controllers
5+
{
6+
public class FollowController : BaseApiController
7+
{
8+
[HttpPost("{username}")]
9+
public async Task<IActionResult> Follow(string username)
10+
{
11+
return HandleResult(await Mediator.Send(new FollowToggle.Command { TargetUsername = username }));
12+
}
13+
14+
[HttpGet("{username}")]
15+
public async Task<IActionResult> GetFollowings(string username, string predicate)
16+
{
17+
return HandleResult(await Mediator.Send(new List.Query{Username = username,
18+
Predicate = predicate}));
19+
}
20+
}
21+
}

API/reactivities.db

12 KB
Binary file not shown.

API/reactivities.db-shm

0 Bytes
Binary file not shown.

API/reactivities.db-wal

-64.4 KB
Binary file not shown.

Application/Activities/AttendeeDto.cs

+3
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ public class AttendeeDto
66
public string DisplayName { get; set; }
77
public string Bio { get; set; }
88
public string Image { get; set; }
9+
public bool Following { get; set; }
10+
public int FollowersCount { get; set; }
11+
public int FollowingCount { get; set; }
912
}
1013
}

Application/Activities/Details.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using Application.Core;
2-
using Domain;
32
using MediatR;
43
using Persistence;
54
using AutoMapper;
65
using AutoMapper.QueryableExtensions;
76
using Microsoft.EntityFrameworkCore;
7+
using Application.Interfaces;
88

99
namespace Application.Activities
1010
{
@@ -19,15 +19,18 @@ public class Handler : IRequestHandler<Query, Result<ActivityDto>>
1919
{
2020
private readonly DataContext _context;
2121
private readonly IMapper _mapper;
22-
public Handler(DataContext context, IMapper mapper)
22+
private readonly IUserAccessor _userAccessor;
23+
public Handler(DataContext context, IMapper mapper, IUserAccessor userAccessor)
2324
{
25+
_userAccessor = userAccessor;
2426
_mapper = mapper;
2527
_context = context;
2628
}
2729
public async Task<Result<ActivityDto>> Handle(Query request, CancellationToken cancellationToken)
2830
{
2931
var activity = await _context.Activities
30-
.ProjectTo<ActivityDto>(_mapper.ConfigurationProvider)
32+
.ProjectTo<ActivityDto>(_mapper.ConfigurationProvider,
33+
new {currentUsername = _userAccessor.GetUserName()})
3134
.FirstOrDefaultAsync(x => x.Id == request.Id);
3235

3336
return Result<ActivityDto>.Success(activity);

Application/Activities/List.cs

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Application.Core;
2+
using Application.Interfaces;
23
using AutoMapper;
34
using AutoMapper.QueryableExtensions;
4-
using Domain;
55
using MediatR;
66
using Microsoft.EntityFrameworkCore;
77
using Persistence;
@@ -14,19 +14,22 @@ public class Query : IRequest<Result<List<ActivityDto>>> { }
1414

1515
public class Handler : IRequestHandler<Query, Result<List<ActivityDto>>>
1616
{
17-
private readonly DataContext _context;
18-
private readonly IMapper _mapper;
17+
private readonly DataContext _context;
18+
private readonly IMapper _mapper;
19+
private readonly IUserAccessor _userAccessor;
1920

20-
public Handler(DataContext context, IMapper mapper)
21+
public Handler(DataContext context, IMapper mapper, IUserAccessor userAccessor)
2122
{
22-
_mapper = mapper;
23-
_context = context;
23+
_userAccessor = userAccessor;
24+
_mapper = mapper;
25+
_context = context;
2426
}
2527

2628
public async Task<Result<List<ActivityDto>>> Handle(Query request, CancellationToken cancellationToken)
2729
{
2830
var activities = await _context.Activities
29-
.ProjectTo<ActivityDto>(_mapper.ConfigurationProvider)
31+
.ProjectTo<ActivityDto>(_mapper.ConfigurationProvider,
32+
new { currentUsername = _userAccessor.GetUserName() })
3033
.ToListAsync(cancellationToken);
3134

3235
return Result<List<ActivityDto>>.Success(activities);

Application/Core/MappingProfiles.cs

+15-2
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,32 @@ public class MappingProfiles : Profile
1010
{
1111
public MappingProfiles()
1212
{
13+
string currentUsername = null;
14+
1315
CreateMap<Activity, Activity>();
1416
CreateMap<Activity, ActivityDto>()
1517
.ForMember(d => d.HostUsername, o => o.MapFrom(s =>
1618
s.Attendees.FirstOrDefault(x => x.IsHost).AppUser.UserName));
19+
1720
CreateMap<ActivityAttendee, AttendeeDto>()
1821
.ForMember(d => d.DisplayName, o => o.MapFrom(s => s.AppUser.DisplayName))
1922
.ForMember(d => d.Username, o => o.MapFrom(s => s.AppUser.UserName))
2023
.ForMember(d => d.Bio, o => o.MapFrom(s => s.AppUser.Bio))
2124
.ForMember(d => d.Image, o =>
22-
o.MapFrom(s => s.AppUser.Photos.FirstOrDefault(x => x.IsMain).Url));
25+
o.MapFrom(s => s.AppUser.Photos.FirstOrDefault(x => x.IsMain).Url))
26+
.ForMember(d => d.FollowersCount, o => o.MapFrom(s => s.AppUser.Followers.Count))
27+
.ForMember(d => d.FollowingCount, o => o.MapFrom(s => s.AppUser.Followings.Count))
28+
.ForMember(d => d.Following, o => o.MapFrom(s => s.AppUser.Followers.Any(x =>
29+
x.Observer.UserName == currentUsername)));
30+
2331
CreateMap<AppUser, Profiles.Profile>()
2432
.ForMember(d => d.Image, o =>
25-
o.MapFrom(s => s.Photos.FirstOrDefault(x => x.IsMain).Url));
33+
o.MapFrom(s => s.Photos.FirstOrDefault(x => x.IsMain).Url))
34+
.ForMember(d => d.FollowersCount, o => o.MapFrom(s => s.Followers.Count))
35+
.ForMember(d => d.FollowingCount, o => o.MapFrom(s => s.Followings.Count))
36+
.ForMember(d => d.Following, o => o.MapFrom(s => s.Followers.Any(x =>
37+
x.Observer.UserName == currentUsername)));
38+
2639
CreateMap<Comment, CommentDto>()
2740
.ForMember(d => d.DisplayName, o => o.MapFrom(s => s.Author.DisplayName))
2841
.ForMember(d => d.Username, o => o.MapFrom(s => s.Author.UserName))

Application/Followers/FollowToggle.cs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.Followers
9+
{
10+
public class FollowToggle
11+
{
12+
public class Command : IRequest<Result<Unit>>
13+
{
14+
public string TargetUsername { 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 observer = await _context.Users.FirstOrDefaultAsync(x =>
30+
x.UserName == _userAccessor.GetUserName());
31+
var target = await _context.Users.FirstOrDefaultAsync(x =>
32+
x.UserName == request.TargetUsername
33+
);
34+
35+
if (target == null) return null;
36+
37+
var following = await _context.UserFollowings.FindAsync(observer.Id, target.Id);
38+
39+
if (following == null)
40+
{
41+
following = new UserFollowing
42+
{
43+
Observer = observer,
44+
Target = target
45+
};
46+
47+
_context.UserFollowings.Add(following);
48+
}
49+
else
50+
{
51+
_context.UserFollowings.Remove(following);
52+
}
53+
54+
var success = await _context.SaveChangesAsync() > 0;
55+
56+
if (success) return Result<Unit>.Success(Unit.Value);
57+
58+
return Result<Unit>.Failure("Failed to update following");
59+
}
60+
}
61+
}
62+
}

Application/Followers/List.cs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Application.Core;
2+
using Application.Interfaces;
3+
using AutoMapper;
4+
using AutoMapper.QueryableExtensions;
5+
using MediatR;
6+
using Microsoft.EntityFrameworkCore;
7+
using Persistence;
8+
9+
namespace Application.Followers
10+
{
11+
public class List
12+
{
13+
public class Query : IRequest<Result<List<Profiles.Profile>>>
14+
{
15+
public string Predicate { get; set; }
16+
public string Username { get; set; }
17+
}
18+
19+
public class Handler : IRequestHandler<Query, Result<List<Profiles.Profile>>>
20+
{
21+
private readonly DataContext _context;
22+
private readonly IMapper _mapper;
23+
private readonly IUserAccessor _userAccessor;
24+
public Handler(DataContext context, IMapper mapper, IUserAccessor userAccessor)
25+
{
26+
_userAccessor = userAccessor;
27+
_mapper = mapper;
28+
_context = context;
29+
}
30+
31+
public async Task<Result<List<Profiles.Profile>>> Handle(Query request, CancellationToken cancellationToken)
32+
{
33+
var profiles = new List<Profiles.Profile>();
34+
35+
switch (request.Predicate)
36+
{
37+
case "followers":
38+
profiles = await _context.UserFollowings.Where(x => x.Target.UserName == request.Username)
39+
.Select(u => u.Observer)
40+
.ProjectTo<Profiles.Profile>(_mapper.ConfigurationProvider, new { currentUsername = _userAccessor.GetUserName() })
41+
.ToListAsync();
42+
break;
43+
case "following":
44+
profiles = await _context.UserFollowings.Where(x => x.Observer.UserName == request.Username)
45+
.Select(u => u.Target)
46+
.ProjectTo<Profiles.Profile>(_mapper.ConfigurationProvider, new { currentUsername = _userAccessor.GetUserName() })
47+
.ToListAsync();
48+
break;
49+
}
50+
return Result<List<Profiles.Profile>>.Success(profiles);
51+
}
52+
}
53+
}
54+
}

Application/Profiles/Details.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Application.Core;
2+
using Application.Interfaces;
23
using AutoMapper;
34
using AutoMapper.QueryableExtensions;
45
using MediatR;
@@ -18,20 +19,23 @@ public class Handler : IRequestHandler<Query, Result<Profile>>
1819
{
1920
private readonly DataContext _context;
2021
private readonly IMapper _mapper;
22+
private readonly IUserAccessor _userAccessor;
2123

22-
public Handler(DataContext context, IMapper mapper)
24+
public Handler(DataContext context, IMapper mapper, IUserAccessor userAccessor)
2325
{
26+
_userAccessor = userAccessor;
2427
_mapper = mapper;
2528
_context = context;
2629
}
2730

2831
public async Task<Result<Profile>> Handle(Query request, CancellationToken cancellationToken)
2932
{
3033
var user = await _context.Users
31-
.ProjectTo<Profile>(_mapper.ConfigurationProvider)
34+
.ProjectTo<Profile>(_mapper.ConfigurationProvider,
35+
new { currentUsername = _userAccessor.GetUserName() })
3236
.SingleOrDefaultAsync(x => x.Username == request.Username);
3337

34-
if(user == null) return null;
38+
if (user == null) return null;
3539

3640
return Result<Profile>.Success(user);
3741
}

Application/Profiles/Profile.cs

+10-7
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
namespace Application.Profiles
44
{
55
public class Profile
6-
{
7-
public string Username { get; set; }
8-
public string DisplayName { get; set; }
9-
public string Bio { get; set; }
10-
public string Image { get; set; }
11-
public ICollection<Photo> Photos {get; set;}
12-
}
6+
{
7+
public string Username { get; set; }
8+
public string DisplayName { get; set; }
9+
public string Bio { get; set; }
10+
public string Image { get; set; }
11+
public bool Following { get; set; }
12+
public int FollowersCount { get; set; }
13+
public int FollowingCount { get; set; }
14+
public ICollection<Photo> Photos { get; set; }
15+
}
1316
}

Domain/ActivityAttendee.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ public class ActivityAttendee
99
public Guid ActivityId { get; set; }
1010
public Activity Activity { get; set; }
1111
public bool IsHost { get; set; }
12-
}
12+
// public object Followers { get; set; }
13+
}
1314
}

Domain/AppUser.cs

+2
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ public class AppUser : IdentityUser
99
public string Bio { get; set; }
1010
public ICollection<ActivityAttendee> Activities { get; set; }
1111
public ICollection<Photo> Photos { get; set; }
12+
public ICollection<UserFollowing> Followings {get; set;}
13+
public ICollection<UserFollowing> Followers { get; set; }
1214
}
1315
}

Domain/UserFollowing.cs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Persistence;
2+
3+
namespace Domain
4+
{
5+
public class UserFollowing
6+
{
7+
public string ObserverId { get; set; }
8+
public AppUser Observer { get; set; }
9+
public string TargetId { get; set; }
10+
public AppUser Target { get; set; }
11+
}
12+
}

Persistence/DataContext.cs

+16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public DataContext(DbContextOptions options) : base(options)
1414
public DbSet<ActivityAttendee> ActivityAttendees { get; set; }
1515
public DbSet<Photo> Photos { get; set; }
1616
public DbSet<Comment> Comments { get; set; }
17+
public DbSet<UserFollowing> UserFollowings { get; set; }
1718

1819
protected override void OnModelCreating(ModelBuilder builder)
1920
{
@@ -35,6 +36,21 @@ protected override void OnModelCreating(ModelBuilder builder)
3536
.HasOne(a => a.Activity)
3637
.WithMany(c => c.Comments)
3738
.OnDelete(DeleteBehavior.Cascade);
39+
40+
builder.Entity<UserFollowing>(b =>
41+
{
42+
b.HasKey(k => new {k.ObserverId, k.TargetId});
43+
44+
b.HasOne(o => o.Observer)
45+
.WithMany(f => f.Followings)
46+
.HasForeignKey(o => o.ObserverId)
47+
.OnDelete(DeleteBehavior.Cascade);
48+
49+
b.HasOne(o => o.Target)
50+
.WithMany(f => f.Followers)
51+
.HasForeignKey(o => o.TargetId)
52+
.OnDelete(DeleteBehavior.Cascade);
53+
});
3854
}
3955
}
4056
}

0 commit comments

Comments
 (0)