Skip to content

Commit

Permalink
Merge pull request #373 from fujaba/feat/course-members
Browse files Browse the repository at this point in the history
Course Members
  • Loading branch information
Clashsoft authored Oct 27, 2023
2 parents 90d4eb0 + 2ae9645 commit f7bd027
Show file tree
Hide file tree
Showing 26 changed files with 324 additions and 111 deletions.
2 changes: 2 additions & 0 deletions frontend/src/app/assignment/assignment.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {AssigneeService} from "./services/assignee.service";
import {EvaluationService} from "./services/evaluation.service";
import {EmbeddingService} from "./services/embedding.service";
import {KeycloakBearerInterceptor} from "keycloak-angular";
import {MemberService} from "./services/member.service";

@NgModule({
declarations: [
Expand Down Expand Up @@ -77,6 +78,7 @@ import {KeycloakBearerInterceptor} from "keycloak-angular";
AssigneeService,
EvaluationService,
EmbeddingService,
MemberService,
],
})
export class AssignmentModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import {SubmitModalComponent} from './submit-modal/submit-modal.component';
import {AssignmentTasksComponent} from './tasks/tasks.component';
import {StatisticsService} from "./statistics.service";
import {SubmitService} from "./submit.service";
import {MemberService} from "./member.service";
import {UserModule} from "../../../user/user.module";

@NgModule({
declarations: [
Expand Down Expand Up @@ -56,10 +54,8 @@ import {UserModule} from "../../../user/user.module";
NgbAccordionModule,
RouteTabsModule,
ModalModule,
UserModule,
],
providers: [
MemberService,
StatisticsService,
SubmitService,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,7 @@ <h5>
Share this link with your students. They will be able to submit solutions to this assignment.
</div>
<hr/>
<h5 class="d-flex align-items-center">
Members
<app-user-typeahead class="ms-auto" [(user)]="newMember"></app-user-typeahead>
<button class="btn btn-success bi-person-add ms-2" [disabled]="!newMember" (click)="addMember()">
Add Member
</button>
</h5>
<div class="mb-3">
<app-member-list [members]="members" [owner]="assignment?.createdBy" (deleted)="deleteMember($event)"></app-member-list>
<div class="form-text">
Add teaching assistants and other people who should have full access to this assignment.
</div>
</div>
<app-edit-member-list *ngIf="assignment" namespace="assignments" [parent]="assignment._id" [owner]="assignment.createdBy"></app-edit-member-list>
<hr/>
<div class="alert alert-warning">
The following fields contain the assignment token, a secret key that can be used to access and grade all submissions.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import {DOCUMENT} from '@angular/common';
import {Component, Inject, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {switchMap, tap} from 'rxjs/operators';
import {switchMap} from 'rxjs/operators';
import {AssignmentService} from '../../../services/assignment.service';
import Assignment, {ReadAssignmentDto} from "../../../model/assignment";
import {MemberService} from "../member.service";
import {Member} from "../../../../user/member";
import {User} from "../../../../user/user";
import {forkJoin} from "rxjs";
import {UserService} from "../../../../user/user.service";

@Component({
selector: 'app-assignment-share',
Expand All @@ -17,16 +12,11 @@ import {UserService} from "../../../../user/user.service";
})
export class ShareComponent implements OnInit {
assignment?: Assignment | ReadAssignmentDto;
members: Member[];

newMember?: User;

readonly origin: string;

constructor(
private assignmentService: AssignmentService,
private memberService: MemberService,
private userService: UserService,
private route: ActivatedRoute,
@Inject(DOCUMENT) document: Document,
) {
Expand All @@ -39,14 +29,6 @@ export class ShareComponent implements OnInit {
).subscribe(assignment => {
this.assignment = assignment;
});

this.route.params.pipe(
switchMap(({aid}) => this.memberService.findAll(aid)),
tap(members => this.members = members),
switchMap(members => forkJoin(members.map(member => this.userService.findOne(member.user).pipe(
tap(user => member._user = user),
)))),
).subscribe();
}

regenerateToken() {
Expand All @@ -59,20 +41,5 @@ export class ShareComponent implements OnInit {
});
}

addMember() {
this.memberService.update({
parent: this.assignment!._id,
user: this.newMember!.id!,
_user: this.newMember!,
}).subscribe(member => {
this.members.push(member);
this.newMember = undefined;
});
}

deleteMember(member: Member) {
this.memberService.delete(member).subscribe(() => {
this.members.splice(this.members.indexOf(member), 1);
});
}
protected readonly switchMap = switchMap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
<h5>
Student Link
</h5>
<p>
You can now give your students the following link:
</p>
<div class="input-group">
<input #linkInput type="text" class="form-control" readonly [value]="origin + '/assignments/courses/' + route.snapshot.params.cid">
<input #linkInput type="text" class="form-control" readonly [value]="origin + '/assignments/courses/' + course?._id">
<button *ngxClipboardIfSupported type="button" class="btn btn-outline-secondary bi-clipboard" ngbTooltip="Copy" [ngxClipboard]="linkInput">
</button>
</div>
<div class="form-text">
Share this link with your students. They will be able to submit solutions for this course.
</div>
<hr/>
<app-edit-member-list *ngIf="course" namespace="courses" [parent]="course._id" [owner]="course.createdBy"></app-edit-member-list>
</div>
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import {DOCUMENT} from '@angular/common';
import {Component, Inject} from '@angular/core';
import {Component, Inject, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {CourseService} from "../../../services/course.service";
import {switchMap} from "rxjs/operators";
import Course from "../../../model/course";

@Component({
selector: 'app-assignment-share',
templateUrl: './share.component.html',
styleUrls: ['./share.component.scss'],
})
export class ShareComponent {
export class ShareComponent implements OnInit {
course?: Course;

readonly origin: string;

constructor(
public readonly route: ActivatedRoute,
private courseService: CourseService,
private route: ActivatedRoute,
@Inject(DOCUMENT) document: Document,
) {
this.origin = document.location.origin;
}

ngOnInit() {
this.route.params.pipe(
switchMap(({cid}) => this.courseService.get(cid)),
).subscribe(course => this.course = course);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<h5 class="d-flex align-items-center">
Members
<app-user-typeahead class="ms-auto" [(user)]="newMember"></app-user-typeahead>
<button class="btn btn-success bi-person-add ms-2" [disabled]="!newMember" (click)="addMember()">
Add Member
</button>
</h5>
<div class="mb-3">
<app-member-list [members]="members" [owner]="owner" (deleted)="deleteMember($event)"></app-member-list>
<div class="form-text">
Add teaching assistants and other people who should have full access to this {{ namespace|slice:0:-1 }}.
</div>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {Component, Input, OnInit} from '@angular/core';
import {Member} from "../../../../user/member";
import {User} from "../../../../user/user";
import {switchMap, tap} from "rxjs/operators";
import {forkJoin} from "rxjs";
import {MemberService, Namespace} from "../../../services/member.service";
import {UserService} from "../../../../user/user.service";

@Component({
selector: 'app-edit-member-list',
templateUrl: './edit-member-list.component.html',
styleUrls: ['./edit-member-list.component.scss']
})
export class EditMemberListComponent implements OnInit {
@Input({required: true}) namespace: Namespace;
@Input({required: true}) parent: string;
@Input() owner?: string;

members: Member[];

newMember?: User;

constructor(
private memberService: MemberService,
private userService: UserService,
) {
}

ngOnInit() {
this.memberService.findAll(this.namespace, this.parent).pipe(
tap(members => this.members = members),
switchMap(members => forkJoin(members.map(member => this.userService.findOne(member.user).pipe(
tap(user => member._user = user),
)))),
).subscribe();
}

addMember() {
this.memberService.update(this.namespace, {
parent: this.parent,
user: this.newMember!.id!,
_user: this.newMember!,
}).subscribe(member => {
this.members.push(member);
this.newMember = undefined;
});
}

deleteMember(member: Member) {
this.memberService.delete(this.namespace, member.parent, member.user).subscribe(() => {
this.members.splice(this.members.indexOf(member), 1);
});
}
}
5 changes: 5 additions & 0 deletions frontend/src/app/assignment/modules/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {GithubLinkPipe} from './pipes/github-link.pipe';
import {CloneLinkPipe} from './pipes/clone-link.pipe';
import {AssignmentActionsComponent} from './assignment-actions/assignment-actions.component';
import {AssigneeInputComponent} from './assignee-input/assignee-input.component';
import {EditMemberListComponent} from './edit-member-list/edit-member-list.component';
import {UserModule} from "../../../user/user.module";

@NgModule({
imports: [
Expand All @@ -26,6 +28,7 @@ import {AssigneeInputComponent} from './assignee-input/assignee-input.component'
NgbDropdownModule,
NgbTypeaheadModule,
FormsModule,
UserModule,
],
declarations: [
AssignmentInfoComponent,
Expand All @@ -40,6 +43,7 @@ import {AssigneeInputComponent} from './assignee-input/assignee-input.component'
CloneLinkPipe,
AssignmentActionsComponent,
AssigneeInputComponent,
EditMemberListComponent,
],
exports: [
AssignmentInfoComponent,
Expand All @@ -54,6 +58,7 @@ import {AssigneeInputComponent} from './assignee-input/assignee-input.component'
CloneLinkPipe,
AssignmentActionsComponent,
AssigneeInputComponent,
EditMemberListComponent,
],
})
export class AssignmentSharedModule {
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/app/assignment/services/course.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ export class CourseService {

getOwn(): Observable<Course[]> {
return this.userService.getCurrent().pipe(
switchMap(user => user ? this.http.get<Course[]>(`${environment.assignmentsApiUrl}/courses`, {params: {createdBy: user.id!}}) : of([])),
switchMap(user => user ? this.http.get<Course[]>(`${environment.assignmentsApiUrl}/courses`, {
params: {
createdBy: user.id!,
members: [user.id!],
},
}) : of([])),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {tap} from 'rxjs/operators';
import {Member} from "../../../user/member";
import {environment} from "../../../../environments/environment";
import {Member} from "../../user/member";
import {environment} from "../../../environments/environment";

export type Namespace = 'assignments' | 'courses';

@Injectable()
export class MemberService {
Expand All @@ -12,18 +14,18 @@ export class MemberService {
) {
}

findAll(id: string): Observable<Member[]> {
return this.http.get<Member[]>(`${environment.assignmentsApiUrl}/assignments/${id}/members`);
findAll(namespace: Namespace, parent: string): Observable<Member[]> {
return this.http.get<Member[]>(`${environment.assignmentsApiUrl}/${namespace}/${parent}/members`);
}

update(member: Member): Observable<Member> {
update(namespace: Namespace, member: Member): Observable<Member> {
const {_user, parent, user, ...rest} = member;
return this.http.put<Member>(`${environment.assignmentsApiUrl}/assignments/${parent}/members/${user}`, rest).pipe(
return this.http.put<Member>(`${environment.assignmentsApiUrl}/${namespace}/${parent}/members/${user}`, rest).pipe(
tap(newMember => newMember._user = _user),
);
}

delete({parent, user}: Member): Observable<Member> {
return this.http.delete<Member>(`${environment.assignmentsApiUrl}/assignments/${parent}/members/${user}`);
delete(namespace: Namespace, parent: string, user: string): Observable<Member> {
return this.http.delete<Member>(`${environment.assignmentsApiUrl}/${namespace}/${parent}/members/${user}`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const removeOwnerResponse = 'Owner cannot remove themselves as member';

@Controller('assignments/:assignment/members')
@ApiTags('Assignment Members')
export class MemberController {
export class AssignmentMemberController {
constructor(
private readonly memberService: MemberService,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Injectable} from "@nestjs/common";
import {MemberService} from "@app/member";

@Injectable()
export class MemberHandler {
export class AssignmentMemberHandler {
constructor(
readonly memberService: MemberService,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import {forwardRef, Module} from '@nestjs/common';
import {MongooseModule} from '@nestjs/mongoose';
import {Member, MemberAuthGuard, MemberSchema, MemberService} from '@app/member';
import {MemberController} from './member.controller';
import {MemberHandler} from "./member.handler";
import {AssignmentMemberController} from './assignment-member.controller';
import {AssignmentMemberHandler} from "./assignment-member.handler";
import {AssignmentModule} from "../assignment/assignment.module";

@Module({
imports: [
MongooseModule.forFeature([{name: Member.name, schema: MemberSchema}]),
forwardRef(() => AssignmentModule),
],
controllers: [MemberController],
controllers: [AssignmentMemberController],
providers: [
MemberService,
MemberAuthGuard,
MemberHandler,
AssignmentMemberHandler,
],
exports: [
MemberService,
MemberAuthGuard,
],
})
export class MemberModule {
export class AssignmentMemberModule {
}
Loading

0 comments on commit f7bd027

Please sign in to comment.