Skip to content

Commit dbbd126

Browse files
committed
Solutions: автоматизированная отправка с GitHub
1 parent 0767456 commit dbbd126

File tree

6 files changed

+242
-1
lines changed

6 files changed

+242
-1
lines changed

HwProj.APIGateway/HwProj.APIGateway.API/Controllers/SolutionsController.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,53 @@ await _coursesServiceClient.CreateCourseGroup(new CreateGroupViewModel(arrFullSt
323323
return Ok(solutionModel);
324324
}
325325

326+
[HttpPost("automated/{courseId}")]
327+
[Authorize(Roles = Roles.LecturerOrExpertRole)]
328+
[ProducesResponseType(typeof(void), (int)HttpStatusCode.OK)]
329+
public async Task<IActionResult> PostAutomatedSolution(PostAutomatedSolutionModel model, long courseId)
330+
{
331+
var course = await _coursesServiceClient.GetCourseById(courseId);
332+
if (course is null) return BadRequest($"Курс с Id {courseId} не найден");
333+
334+
var tasks = course.Homeworks.SelectMany(t => t.Tasks);
335+
var task = model.TaskIdType switch
336+
{
337+
TaskIdType.Id when long.TryParse(model.TaskId, out var taskId) => tasks.FirstOrDefault(x => x.Id == taskId),
338+
TaskIdType.Title => tasks.FirstOrDefault(x => x.Title == model.TaskId),
339+
_ => null
340+
};
341+
if (task is null) return BadRequest($"Задача с {model.TaskIdType} = {model.TaskId} не найдена");
342+
343+
var students =
344+
await AuthServiceClient.GetAccountsData(course.AcceptedStudents.Select(x => x.StudentId).ToArray());
345+
var student = model.StudentIdType switch
346+
{
347+
StudentIdType.Id => students.FirstOrDefault(x => x.UserId == model.StudentId),
348+
StudentIdType.FullName => students.FirstOrDefault(x =>
349+
model.StudentId.Contains(x.Name) &&
350+
model.StudentId.Contains(x.Surname) &&
351+
(string.IsNullOrEmpty(x.MiddleName) || model.StudentId.Contains(x.MiddleName))),
352+
StudentIdType.GitHub => students.FirstOrDefault(x => x.GithubId == model.TaskId),
353+
_ => null
354+
};
355+
if (student == null)
356+
return BadRequest($"Студент с {model.StudentIdType} = {model.StudentId} не записан на курс");
357+
358+
var solutions = await _solutionsClient.GetUserSolutions(task.Id, student.UserId);
359+
if (solutions.OrderBy(x => x.PublicationDate).LastOrDefault()?.State == SolutionState.Posted)
360+
return Ok(
361+
"Последнее решение студента по задаче ещё не проверено. Все хорошо, но новое решение не будет добавлено");
362+
363+
await _solutionsClient.PostSolution(task.Id, new HwProj.Models.SolutionsService.PostSolutionModel
364+
{
365+
GithubUrl = model.GithubUrl,
366+
Comment = model.Comment,
367+
StudentId = student.UserId
368+
});
369+
370+
return Ok("Решение успешно добавлено в очередь на проверку!");
371+
}
372+
326373
[HttpPost("rateEmptySolution/{taskId}")]
327374
[Authorize(Roles = Roles.LecturerOrExpertRole)]
328375
public async Task<IActionResult> PostEmptySolutionWithRate(long taskId, SolutionViewModel solution)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
77
<DockerfileContext>..\..</DockerfileContext>
88
<Nullable>$(NullableReferenceTypes)</Nullable>
9+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
10+
<NoWarn>$(NoWarn);1591</NoWarn>
911
</PropertyGroup>
1012

1113
<ItemGroup>

HwProj.APIGateway/HwProj.APIGateway.API/Models/Solutions/PostSolutionModel.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Text.Json.Serialization;
2+
13
namespace HwProj.APIGateway.API.Models.Solutions;
24

35
public class PostSolutionModel
@@ -6,3 +8,48 @@ public class PostSolutionModel
68
public string? Comment { get; set; }
79
public string[]? GroupMateIds { get; set; }
810
}
11+
12+
public class PostAutomatedSolutionModel
13+
{
14+
/// Идентификатор задачи: название или id на HwProj, в зависимости от параметра TaskIdType
15+
public required string TaskId { get; init; }
16+
17+
/// Тип идентификатора задачи (TaskId): Title или Id на HwProj
18+
[JsonConverter(typeof(JsonStringEnumConverter))]
19+
public TaskIdType TaskIdType { get; init; } = TaskIdType.Id;
20+
21+
/// Идентификатор студента: ФИО (в любом порядке), id на HwProj или привязанный GitHub-логин;
22+
/// в зависимости от параметра StudentIdType
23+
public required string StudentId { get; init; }
24+
25+
/// Тип идентификатора студента (StudentId): ФИО (в любом порядке), id на HwProj или привязанный GitHub-логин
26+
[JsonConverter(typeof(JsonStringEnumConverter))]
27+
public StudentIdType StudentIdType { get; init; } = StudentIdType.Id;
28+
29+
/// Ссылка на решение, будь то PR, репозиторий или другой источник
30+
public string? GithubUrl { get; init; }
31+
32+
/// Комментарий к решению, здесь можно оставить полезную информацию, которая будет отображаться при проверке решения в сервисе
33+
public string? Comment { get; init; }
34+
}
35+
36+
public enum TaskIdType
37+
{
38+
/// Внутренний идентификатор задачи на HwProj
39+
Id,
40+
41+
/// Полное название назади
42+
Title
43+
}
44+
45+
public enum StudentIdType
46+
{
47+
/// Внутренний идентификатор студента на HwProj
48+
Id,
49+
50+
/// Полное имя студента
51+
FullName,
52+
53+
/// Привязанный к HwProj логин студента на GitHub
54+
GitHub
55+
}

HwProj.AuthService/HwProj.AuthService.API/HwProj.AuthService.API.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.20" />
15+
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
1516
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.20" />
1617
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.9" />
1718
<PackageReference Include="Octokit" Version="10.0.0" />

hwproj.front/src/api/api.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,49 @@ export interface NotificationsSettingDto {
15761576
*/
15771577
isEnabled?: boolean;
15781578
}
1579+
/**
1580+
*
1581+
* @export
1582+
* @interface PostAutomatedSolutionModel
1583+
*/
1584+
export interface PostAutomatedSolutionModel {
1585+
/**
1586+
*
1587+
* @type {string}
1588+
* @memberof PostAutomatedSolutionModel
1589+
*/
1590+
taskId: string;
1591+
/**
1592+
*
1593+
* @type {TaskIdType}
1594+
* @memberof PostAutomatedSolutionModel
1595+
*/
1596+
taskIdType?: TaskIdType;
1597+
/**
1598+
*
1599+
* @type {string}
1600+
* @memberof PostAutomatedSolutionModel
1601+
*/
1602+
studentId: string;
1603+
/**
1604+
*
1605+
* @type {StudentIdType}
1606+
* @memberof PostAutomatedSolutionModel
1607+
*/
1608+
studentIdType?: StudentIdType;
1609+
/**
1610+
*
1611+
* @type {string}
1612+
* @memberof PostAutomatedSolutionModel
1613+
*/
1614+
githubUrl?: string;
1615+
/**
1616+
*
1617+
* @type {string}
1618+
* @memberof PostAutomatedSolutionModel
1619+
*/
1620+
comment?: string;
1621+
}
15791622
/**
15801623
*
15811624
* @export
@@ -2316,6 +2359,16 @@ export interface StudentDataDto {
23162359
*/
23172360
characteristics?: StudentCharacteristicsDto;
23182361
}
2362+
/**
2363+
*
2364+
* @export
2365+
* @enum {string}
2366+
*/
2367+
export enum StudentIdType {
2368+
NUMBER_0 = <any> 0,
2369+
NUMBER_1 = <any> 1,
2370+
NUMBER_2 = <any> 2
2371+
}
23192372
/**
23202373
*
23212374
* @export
@@ -2433,6 +2486,15 @@ export interface TaskDeadlineView {
24332486
*/
24342487
deadlinePast?: boolean;
24352488
}
2489+
/**
2490+
*
2491+
* @export
2492+
* @enum {string}
2493+
*/
2494+
export enum TaskIdType {
2495+
NUMBER_0 = <any> 0,
2496+
NUMBER_1 = <any> 1
2497+
}
24362498
/**
24372499
*
24382500
* @export
@@ -8035,6 +8097,47 @@ export const SolutionsApiFetchParamCreator = function (configuration?: Configura
80358097
options: localVarRequestOptions,
80368098
};
80378099
},
8100+
/**
8101+
*
8102+
* @param {number} courseId
8103+
* @param {PostAutomatedSolutionModel} [body]
8104+
* @param {*} [options] Override http request option.
8105+
* @throws {RequiredError}
8106+
*/
8107+
solutionsPostAutomatedSolution(courseId: number, body?: PostAutomatedSolutionModel, options: any = {}): FetchArgs {
8108+
// verify required parameter 'courseId' is not null or undefined
8109+
if (courseId === null || courseId === undefined) {
8110+
throw new RequiredError('courseId','Required parameter courseId was null or undefined when calling solutionsPostAutomatedSolution.');
8111+
}
8112+
const localVarPath = `/api/Solutions/automated/{courseId}`
8113+
.replace(`{${"courseId"}}`, encodeURIComponent(String(courseId)));
8114+
const localVarUrlObj = url.parse(localVarPath, true);
8115+
const localVarRequestOptions = Object.assign({ method: 'POST' }, options);
8116+
const localVarHeaderParameter = {} as any;
8117+
const localVarQueryParameter = {} as any;
8118+
8119+
// authentication Bearer required
8120+
if (configuration && configuration.apiKey) {
8121+
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
8122+
? configuration.apiKey("Authorization")
8123+
: configuration.apiKey;
8124+
localVarHeaderParameter["Authorization"] = localVarApiKeyValue;
8125+
}
8126+
8127+
localVarHeaderParameter['Content-Type'] = 'application/json';
8128+
8129+
localVarUrlObj.query = Object.assign({}, localVarUrlObj.query, localVarQueryParameter, options.query);
8130+
// fix override query string Detail: https://stackoverflow.com/a/7517673/1077943
8131+
localVarUrlObj.search = null;
8132+
localVarRequestOptions.headers = Object.assign({}, localVarHeaderParameter, options.headers);
8133+
const needsSerialization = (<any>"PostAutomatedSolutionModel" !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
8134+
localVarRequestOptions.body = needsSerialization ? JSON.stringify(body || {}) : (body || "");
8135+
8136+
return {
8137+
url: url.format(localVarUrlObj),
8138+
options: localVarRequestOptions,
8139+
};
8140+
},
80388141
/**
80398142
*
80408143
* @param {number} taskId
@@ -8332,6 +8435,25 @@ export const SolutionsApiFp = function(configuration?: Configuration) {
83328435
});
83338436
};
83348437
},
8438+
/**
8439+
*
8440+
* @param {number} courseId
8441+
* @param {PostAutomatedSolutionModel} [body]
8442+
* @param {*} [options] Override http request option.
8443+
* @throws {RequiredError}
8444+
*/
8445+
solutionsPostAutomatedSolution(courseId: number, body?: PostAutomatedSolutionModel, options?: any): (fetch?: FetchAPI, basePath?: string) => Promise<Response> {
8446+
const localVarFetchArgs = SolutionsApiFetchParamCreator(configuration).solutionsPostAutomatedSolution(courseId, body, options);
8447+
return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
8448+
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
8449+
if (response.status >= 200 && response.status < 300) {
8450+
return response;
8451+
} else {
8452+
throw response;
8453+
}
8454+
});
8455+
};
8456+
},
83358457
/**
83368458
*
83378459
* @param {number} taskId
@@ -8482,6 +8604,16 @@ export const SolutionsApiFactory = function (configuration?: Configuration, fetc
84828604
solutionsMarkSolution(solutionId: number, options?: any) {
84838605
return SolutionsApiFp(configuration).solutionsMarkSolution(solutionId, options)(fetch, basePath);
84848606
},
8607+
/**
8608+
*
8609+
* @param {number} courseId
8610+
* @param {PostAutomatedSolutionModel} [body]
8611+
* @param {*} [options] Override http request option.
8612+
* @throws {RequiredError}
8613+
*/
8614+
solutionsPostAutomatedSolution(courseId: number, body?: PostAutomatedSolutionModel, options?: any) {
8615+
return SolutionsApiFp(configuration).solutionsPostAutomatedSolution(courseId, body, options)(fetch, basePath);
8616+
},
84858617
/**
84868618
*
84878619
* @param {number} taskId
@@ -8624,6 +8756,18 @@ export class SolutionsApi extends BaseAPI {
86248756
return SolutionsApiFp(this.configuration).solutionsMarkSolution(solutionId, options)(this.fetch, this.basePath);
86258757
}
86268758

8759+
/**
8760+
*
8761+
* @param {number} courseId
8762+
* @param {PostAutomatedSolutionModel} [body]
8763+
* @param {*} [options] Override http request option.
8764+
* @throws {RequiredError}
8765+
* @memberof SolutionsApi
8766+
*/
8767+
public solutionsPostAutomatedSolution(courseId: number, body?: PostAutomatedSolutionModel, options?: any) {
8768+
return SolutionsApiFp(this.configuration).solutionsPostAutomatedSolution(courseId, body, options)(this.fetch, this.basePath);
8769+
}
8770+
86278771
/**
86288772
*
86298773
* @param {number} taskId

hwproj.front/src/components/Experts/InviteModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const InviteExpertModal: FC<IInviteExpertProps> = (props) => {
7878
const courses = await ApiSingleton.coursesApi.coursesGetAllUserCourses();
7979
setState(prevState => ({
8080
...prevState,
81-
lecturerCourses: courses
81+
lecturerCourses: courses.filter(x => !x.isCompleted).reverse()
8282
}));
8383
setIsCourseListLoading(false);
8484
}

0 commit comments

Comments
 (0)