Skip to content

Commit a25c5b9

Browse files
Salvatore112bygu4DedSec256
authored
Интеграция HwProj c университетской базой (#470)
* feat: add student info service implementation * feat: make GetGroups work not only for software engineering * feat: add interface for StudentsStats * feat: add controllers for students information methods * feat: update api.ts for new controllers * feat: add group writing to course creation page * feat: add st's writing to course creation page * feat: Change language to russian for group fetching * feat: add property for getting study program names * feat: implement autocomplete for study program field * feat: implement autocompletion for groups based on study program field * fix: remove extra whitespaces in front of group names * fix: remove "no options" popper * fix: allow custom group names that don't require program name * feat: add ProgramNames property to interface * feat: remove controller for GetSts and its call on frontend * feat: rename GetSts and make it fetch student names as well * feat: convert student st number to an email * feat: add controller to register students and add them to course * feat: fetch, invite and add students to a create course * docs: add comments for StudentStats and its interface * docs: add comments to CreateCourse.tsx * docs: add comments for StudentsStats.cs * test: add tests for StudentsStats * refactor: rename StudentsStats to StudentsInformation * feat: move StudentsInformation client to CoursesController * feat: add model for students * feat: request password recovery in Register * feat: use method instead of propery for groups fetching * fix: change property to method in tests * feat: use model instead of strings for fetching programs * feat: use model instead of strings for fetching groups * feat: invite students to course via CreateCourse controller * feat: move login and password to appsettings * refactor: move projects to a separate folder * fix: remove reference to old project in apigateway * feat: move default password to appsettings * feat: add loading state for course creation * feat: move configuration to startup * fix: initialize studentIDs * fix: don't open extra LDAP connections * feat: optimize course creation with LINQ * feat: make student fetching optional * feat: use less requests to authservice client * refactor: translate "create course" * feat: Do less requests to CoursesService * feat: restrict controllers to Lecturer's role * feat: remove password field from appsettings * chore: format Startup.cs * feat: change default password to guid * chore: format CoursesController * chore: rename studentsInformation class * chore: rename studentID * feat: pass courseId to auth service method * feat: change return type of course service method * feat: change framework version * chore: refactor comments * feat: change framework versions * feat: wrap groups in try catch * fix: enable fetching students from db * feat: move ldap parameters to appsettings.json * feat: don't do extra calls in course creation * feat: add Lazy to constructor * feat: move disconnect to finally * feat: make groups fetching async * feat: don't create extra notifications * feat: do one request to authserviceclient * fix: change file names in sln * revert 6e3dca6 * fix: fix names in .sln * feat: delete password from register model * test * feat: sync branch * fix: remove duplicates * feat: make optimized batch registration * feat: return empty list if there're connection errors * feat: return empty list of connection failed * feat: optimize existing students addition * chore: update package-lock.json * Delete test.txt * Fix 1 * Fixes 2 * Fix docker * Rebase fixes * fixes * Fixes 3 * Fix tests --------- Co-authored-by: Alexander Bugaev <144491096+bygu4@users.noreply.github.com> Co-authored-by: Alex Berezhnykh <berejnih.alex2011@yandex.ru> Co-authored-by: Alexey.Berezhnykh <alexey.berezhnykh@jetbrains.com>
1 parent dd0b3fb commit a25c5b9

27 files changed

Lines changed: 1024 additions & 150 deletions

File tree

HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
using HwProj.AuthService.Client;
88
using HwProj.CoursesService.Client;
99
using HwProj.Models.AuthService.DTO;
10+
using HwProj.Models.AuthService.ViewModels;
1011
using HwProj.Models.CoursesService.DTO;
1112
using HwProj.Models.CoursesService.ViewModels;
1213
using HwProj.Models.Roles;
1314
using Microsoft.AspNetCore.Authorization;
1415
using Microsoft.AspNetCore.Mvc;
16+
using Microsoft.Extensions.Options;
17+
using IStudentsInfo;
1518

1619
namespace HwProj.APIGateway.API.Controllers
1720
{
@@ -21,13 +24,17 @@ public class CoursesController : AggregationController
2124
{
2225
private readonly ICoursesServiceClient _coursesClient;
2326
private readonly IMapper _mapper;
27+
private readonly IStudentsInformationProvider _studentsInfo;
2428

25-
public CoursesController(ICoursesServiceClient coursesClient,
29+
public CoursesController(
30+
ICoursesServiceClient coursesClient,
2631
IAuthServiceClient authServiceClient,
27-
IMapper mapper) : base(authServiceClient)
32+
IMapper mapper,
33+
IStudentsInformationProvider studentsInfo) : base(authServiceClient)
2834
{
2935
_coursesClient = coursesClient;
3036
_mapper = mapper;
37+
_studentsInfo = studentsInfo;
3138
}
3239

3340
[HttpGet]
@@ -70,11 +77,54 @@ public async Task<IActionResult> DeleteCourse(long courseId)
7077
return Ok();
7178
}
7279

80+
[HttpGet("getGroups")]
81+
[Authorize(Roles = Roles.LecturerRole)]
82+
[ProducesResponseType(typeof(List<GroupModel>), (int)HttpStatusCode.OK)]
83+
public async Task<IActionResult> GetGroups(string programName)
84+
{
85+
var groups = await _studentsInfo.GetGroups(programName);
86+
return Ok(groups);
87+
}
88+
89+
[HttpGet("getProgramNames")]
90+
[Authorize(Roles = Roles.LecturerRole)]
91+
[ProducesResponseType(typeof(List<ProgramModel>), (int)HttpStatusCode.OK)]
92+
public IActionResult GetProgramNames()
93+
{
94+
return Ok(_studentsInfo.GetProgramNames());
95+
}
96+
7397
[HttpPost("create")]
7498
[Authorize(Roles = Roles.LecturerRole)]
7599
[ProducesResponseType(typeof(long), (int)HttpStatusCode.OK)]
76100
public async Task<IActionResult> CreateCourse(CreateCourseViewModel model)
77101
{
102+
if (!string.IsNullOrEmpty(model.GroupName) && model.FetchStudents)
103+
{
104+
var students = _studentsInfo.GetStudentInformation(model.GroupName);
105+
if (students.Count > 0)
106+
{
107+
var sortedStudents = students
108+
.Where(student => !string.IsNullOrEmpty(student.Email))
109+
.OrderBy(student => student.Surname)
110+
.ThenBy(student => student.Name)
111+
.ToList();
112+
113+
var registrationModels = sortedStudents
114+
.Select(student => new RegisterViewModel
115+
{
116+
Email = student.Email,
117+
Name = student.Name,
118+
Surname = student.Surname,
119+
MiddleName = student.MiddleName
120+
}).ToList();
121+
122+
var userIds = await AuthServiceClient.GetOrRegisterStudentsBatchAsync(registrationModels);
123+
model.StudentIDs = userIds.Where(x => x.Succeeded).Select(x => x.Value).ToList();
124+
}
125+
}
126+
127+
model.StudentIDs ??= new List<string>();
78128
var result = await _coursesClient.CreateCourse(model);
79129
return result.Succeeded
80130
? Ok(result.Value) as IActionResult

HwProj.APIGateway/HwProj.APIGateway.API/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ COPY ["HwProj.CoursesService/HwProj.CoursesService.Client/HwProj.CoursesService.
1212
COPY ["HwProj.AuthService/HwProj.AuthService.Client/HwProj.AuthService.Client.csproj", "HwProj.AuthService/HwProj.AuthService.Client/"]
1313
COPY ["HwProj.SolutionsService/HwProj.SolutionsService.Client/HwProj.SolutionsService.Client.csproj", "HwProj.SolutionsService/HwProj.SolutionsService.Client/"]
1414
COPY ["HwProj.Common/HwProj.Exceptions/HwProj.Exceptions.csproj", "HwProj.Common/HwProj.Exceptions/"]
15+
COPY ["HwProj.ContentService/HwProj.ContentService.Client/HwProj.ContentService.Client.csproj", "HwProj.ContentService/HwProj.ContentService.Client/"]
16+
COPY ["HwProj.StudentInfo/IStudentsInfo/IStudentsInfo.csproj", "HwProj.StudentInfo/IStudentsInfo/"]
17+
COPY ["HwProj.StudentInfo/StudentsInfo.Tests/StudentsInfo.Tests.csproj", "HwProj.StudentInfo/StudentsInfo.Tests/"]
18+
COPY ["HwProj.StudentInfo/StudentsInfo/StudentsInfo.csproj", "HwProj.StudentInfo/StudentsInfo/"]
1519

1620
COPY . .
1721

HwProj.APIGateway/HwProj.APIGateway.API/HwProj.APIGateway.API.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@
2323
<ProjectReference Include="..\..\HwProj.CoursesService\HwProj.CoursesService.Client\HwProj.CoursesService.Client.csproj" />
2424
<ProjectReference Include="..\..\HwProj.NotificationsService\HwProj.NotificationsService.Client\HwProj.NotificationsService.Client.csproj" />
2525
<ProjectReference Include="..\..\HwProj.SolutionsService\HwProj.SolutionsService.Client\HwProj.SolutionsService.Client.csproj" />
26+
<ProjectReference Include="..\..\HwProj.StudentInfo\IStudentsInfo\IStudentsInfo.csproj" />
27+
<ProjectReference Include="..\..\HwProj.StudentInfo\StudentsInfo.Tests\StudentsInfo.Tests.csproj" />
28+
<ProjectReference Include="..\..\HwProj.StudentInfo\StudentsInfo\StudentsInfo.csproj" />
2629
</ItemGroup>
2730
</Project>

HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
using Microsoft.Extensions.Configuration;
1313
using Microsoft.Extensions.DependencyInjection;
1414
using Microsoft.IdentityModel.Tokens;
15+
using IStudentsInfo;
16+
using StudentsInfo;
1517

1618
namespace HwProj.APIGateway.API
1719
{
@@ -26,14 +28,15 @@ public Startup(IConfiguration configuration)
2628

2729
public void ConfigureServices(IServiceCollection services)
2830
{
29-
services.Configure<FormOptions>(options =>
30-
{
31-
options.MultipartBodyLengthLimit = 200 * 1024 * 1024;
32-
});
31+
services.Configure<FormOptions>(options => { options.MultipartBodyLengthLimit = 200 * 1024 * 1024; });
3332
services.ConfigureHwProjServices("API Gateway");
34-
33+
services.AddSingleton<IStudentsInformationProvider>(provider =>
34+
new StudentsInformationProvider(Configuration["StudentsInfo:Login"],
35+
Configuration["StudentsInfo:Password"],
36+
Configuration["StudentsInfo:LdapHost"], int.Parse(Configuration["StudentsInfo:LdapPort"]),
37+
Configuration["StudentsInfo:SearchBase"]));
3538
const string authenticationProviderKey = "GatewayKey";
36-
39+
3740
services.AddAuthentication()
3841
.AddJwtBearer(authenticationProviderKey, x =>
3942
{
@@ -57,7 +60,7 @@ public void ConfigureServices(IServiceCollection services)
5760
services.AddSolutionServiceClient();
5861
services.AddNotificationsServiceClient();
5962
services.AddContentServiceClient();
60-
63+
6164
services.AddScoped<CourseMentorOnlyAttribute>();
6265
}
6366

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
{
1+
{
22
"Services": {
33
"Auth": "http://localhost:5001",
44
"Courses": "http://localhost:5002",
55
"Notifications": "http://localhost:5006",
66
"Solutions": "http://localhost:5007",
77
"Content": "http://localhost:5008"
8+
},
9+
"StudentsInfo": {
10+
"Login": "",
11+
"Password": "",
12+
"LdapHost": "ad.pu.ru",
13+
"LdapPort": 389,
14+
"SearchBase": "DC=ad,DC=pu,DC=ru"
815
}
9-
}
16+
}

HwProj.AuthService/HwProj.AuthService.API/Controllers/AccountController.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using System.Linq;
1+
using System.Collections.Generic;
2+
using System.Linq;
23
using System.Net;
34
using System.Threading.Tasks;
45
using AutoMapper;
5-
using HwProj.AuthService.API.Repositories;
66
using Microsoft.AspNetCore.Mvc;
77
using HwProj.AuthService.API.Services;
88
using HwProj.Models.AuthService.DTO;
@@ -60,14 +60,21 @@ public async Task<IActionResult> GetUserDataByEmail(string email)
6060
}
6161

6262
[HttpPost("register")]
63-
[ProducesResponseType(typeof(Result<TokenCredentials>), (int)HttpStatusCode.OK)]
63+
[ProducesResponseType(typeof(Result<string>), (int)HttpStatusCode.OK)]
6464
public async Task<IActionResult> Register([FromBody] RegisterViewModel model)
6565
{
6666
var newModel = _mapper.Map<RegisterDataDTO>(model);
6767
var result = await _accountService.RegisterUserAsync(newModel);
6868
return Ok(result);
6969
}
7070

71+
[HttpPost("registerStudentsBatch")]
72+
public async Task<Result<string>[]> GetRegisterStudentsBatch([FromBody] IEnumerable<RegisterViewModel> models)
73+
{
74+
var dtos = _mapper.Map<IEnumerable<RegisterDataDTO>>(models);
75+
return await _accountService.GetOrRegisterStudentsBatchAsync(dtos);
76+
}
77+
7178
[HttpPost("login")]
7279
[ProducesResponseType(typeof(Result<TokenCredentials>), (int)HttpStatusCode.OK)]
7380
public async Task<IActionResult> Login([FromBody] LoginViewModel model)
@@ -158,19 +165,19 @@ public Task<IActionResult> GetGithubLoginUrl(
158165
[FromBody] UrlDto urlDto)
159166
{
160167
var sourceSection = configuration.GetSection("Github");
161-
168+
162169
var clientId = sourceSection["ClientIdGitHub"];
163170
var scope = sourceSection["ScopeGitHub"];
164171
var redirectUrl = urlDto.Url;
165-
172+
166173
var resultUrl =
167174
$"https://github.com/login/oauth/authorize?client_id={clientId}&redirect_uri={redirectUrl}&scope={scope}";
168175

169176
var resultUrlDto = new UrlDto
170177
{
171178
Url = resultUrl
172179
};
173-
180+
174181
return Task.FromResult<IActionResult>(Ok(resultUrlDto));
175182
}
176183

HwProj.AuthService/HwProj.AuthService.API/Services/AccountService.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,33 @@ public async Task<AccountDataDto> GetAccountDataByEmailAsync(string email)
9393
return await GetAccountDataAsync(user);
9494
}
9595

96+
public async Task<Result<string>> RegisterUserAsync(RegisterDataDTO model)
97+
{
98+
if (await _userManager.FindByEmailAsync(model.Email) != null)
99+
return Result<string>.Failed("Пользователь уже зарегистрирован");
100+
101+
return await RegisterUserAsyncInternal(model);
102+
}
103+
104+
public async Task<Result<string>[]> GetOrRegisterStudentsBatchAsync(IEnumerable<RegisterDataDTO> models)
105+
{
106+
var results = new List<Result<string>>();
107+
108+
foreach (var model in models)
109+
{
110+
if (await _userManager.FindByEmailAsync(model.Email) is { } user)
111+
{
112+
results.Add(Result<string>.Success(user.Id));
113+
continue;
114+
}
115+
116+
var result = await RegisterUserAsyncInternal(model);
117+
results.Add(result);
118+
}
119+
120+
return results.ToArray();
121+
}
122+
96123
public async Task<Result> EditAccountAsync(string id, EditDataDTO model)
97124
{
98125
var user = await _userManager.FindByIdAsync(id);
@@ -136,13 +163,8 @@ public async Task<Result<TokenCredentials>> RefreshToken(string userId)
136163
: await GetToken(user);
137164
}
138165

139-
public async Task<Result> RegisterUserAsync(RegisterDataDTO model)
166+
private async Task<Result<string>> RegisterUserAsyncInternal(RegisterDataDTO model)
140167
{
141-
if (await _userManager.FindByEmailAsync(model.Email) != null)
142-
{
143-
return Result.Failed("Пользователь уже зарегистрирован");
144-
}
145-
146168
var user = _mapper.Map<User>(model);
147169
user.UserName = user.Email;
148170

@@ -163,10 +185,10 @@ public async Task<Result> RegisterUserAsync(RegisterDataDTO model)
163185
ChangePasswordToken = changePasswordToken
164186
};
165187
_eventBus.Publish(registerEvent);
166-
return Result.Success();
188+
return Result<string>.Success(user.Id);
167189
}
168190

169-
return Result.Failed(result.Errors.Select(errors => errors.Description).ToArray());
191+
return Result<string>.Failed(result.Errors.Select(errors => errors.Description).ToArray());
170192
}
171193

172194
public async Task<Result> InviteNewLecturer(string emailOfInvitedUser)

HwProj.AuthService/HwProj.AuthService.API/Services/IAccountService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public interface IAccountService
1212
Task<AccountDataDto> GetAccountDataAsync(string userId);
1313
Task<AccountDataDto[]> GetAccountsDataAsync(string[] userIds);
1414
Task<AccountDataDto> GetAccountDataByEmailAsync(string email);
15-
Task<Result> RegisterUserAsync(RegisterDataDTO model);
15+
Task<Result<string>> RegisterUserAsync(RegisterDataDTO model);
1616
Task<Result> EditAccountAsync(string accountId, EditDataDTO model);
1717
Task<Result<TokenCredentials>> LoginUserAsync(LoginViewModel model);
1818
Task<Result<TokenCredentials>> RefreshToken(string userId);
@@ -21,5 +21,6 @@ public interface IAccountService
2121
Task<Result> RequestPasswordRecovery(RequestPasswordRecoveryViewModel model);
2222
Task<Result> ResetPassword(ResetPasswordViewModel model);
2323
Task<GithubCredentials> AuthorizeGithub(string code, string userId);
24+
Task<Result<string>[]> GetOrRegisterStudentsBatchAsync(IEnumerable<RegisterDataDTO> models);
2425
}
2526
}

HwProj.AuthService/HwProj.AuthService.API/Startup.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,8 @@ public void ConfigureServices(IServiceCollection services)
3232

3333
//var appSettingsSection = Configuration.GetSection("AppSettings");
3434
//services.Configure<AppSettings>(appSettingsSection);
35-
36-
services.AddAuthentication(options =>
37-
{
38-
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
39-
})
35+
36+
services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; })
4037
.AddJwtBearer(x =>
4138
{
4239
x.RequireHttpsMetadata = false; //TODO: dev env setting
@@ -52,7 +49,7 @@ public void ConfigureServices(IServiceCollection services)
5249
});
5350

5451
services.AddHttpClient();
55-
52+
5653
var connectionString = ConnectionString.GetConnectionString(Configuration);
5754
services.AddDbContext<IdentityContext>(options =>
5855
options.UseSqlServer(connectionString));
@@ -88,7 +85,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, Identity
8885
{
8986
var userManager = scope.ServiceProvider.GetService(typeof(UserManager<User>)) as UserManager<User>;
9087

91-
var rolesManager = scope.ServiceProvider.GetService(typeof(RoleManager<IdentityRole>)) as RoleManager<IdentityRole>;
88+
var rolesManager =
89+
scope.ServiceProvider.GetService(typeof(RoleManager<IdentityRole>)) as RoleManager<IdentityRole>;
9290
var eventBus = scope.ServiceProvider.GetService<IEventBus>();
9391

9492
if (env.IsDevelopment())

0 commit comments

Comments
 (0)