Browse Source

Subject list

Marcin Jaborski 3 năm trước cách đây
mục cha
commit
6a0ec61abe

+ 41 - 0
project-back/src/main/java/com/example/projectback/controllers/SubjectController.java

@@ -0,0 +1,41 @@
+package com.example.projectback.controllers;
+
+import com.example.projectback.model.Changeset;
+import com.example.projectback.model.Subject;
+import com.example.projectback.repository.SubjectRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.swing.event.ChangeEvent;
+import java.util.List;
+
+@RestController
+@CrossOrigin(origins = "http://localhost:4200")
+@RequestMapping("/subjects")
+public class SubjectController {
+    private final SubjectRepository subjectRepository;
+
+    @Autowired
+    public SubjectController(SubjectRepository subjectRepository) {
+        this.subjectRepository = subjectRepository;
+    }
+
+    @GetMapping
+    public List<Subject> getAllSubjects() {return subjectRepository.findAll();}
+
+    @PostMapping
+    public ResponseEntity<Subject> addSubject(@RequestBody Subject subject) {
+        subjectRepository.save(subject);
+        return new ResponseEntity<>(subject, HttpStatus.CREATED);
+    }
+
+    @PostMapping("/delete")
+    public ResponseEntity<Changeset> deleteSubjects(@RequestBody Changeset changeset) {
+        for (Long recId : changeset.getRecords()) {
+            subjectRepository.deleteById(recId);
+        }
+        return new ResponseEntity<>(changeset, HttpStatus.OK);
+    }
+}

+ 22 - 0
project-back/src/main/java/com/example/projectback/model/Changeset.java

@@ -0,0 +1,22 @@
+package com.example.projectback.model;
+
+import java.util.ArrayList;
+
+public class Changeset {
+    private ArrayList<Long> records = new ArrayList<>();
+
+    public Changeset() {
+    }
+
+    public Changeset(ArrayList<Long> records) {
+        this.records = records;
+    }
+
+    public ArrayList<Long> getRecords() {
+        return records;
+    }
+
+    public void setRecords(ArrayList<Long> records) {
+        this.records = records;
+    }
+}

+ 17 - 5
project-back/src/main/java/com/example/projectback/model/Student.java

@@ -1,19 +1,31 @@
 package com.example.projectback.model;
 
-import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.Id;
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
 
 @Entity
 public class Student {
     @Id
-    @GeneratedValue
-    private long id;
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @OneToMany(mappedBy = "student")
+    private List<SubjectStudent> subjectAssoc = new ArrayList<>();
+
     private String firstname;
     private String lastname;
     private String email;
     private String telephone;
 
+    public List<SubjectStudent> getSubjectAssoc() {
+        return subjectAssoc;
+    }
+
+    public void setSubjectAssoc(List<SubjectStudent> subjectAssoc) {
+        this.subjectAssoc = subjectAssoc;
+    }
+
     public String getFirstname() {
         return firstname;
     }

+ 48 - 0
project-back/src/main/java/com/example/projectback/model/Subject.java

@@ -0,0 +1,48 @@
+package com.example.projectback.model;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+public class Subject {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    private String name;
+
+    @OneToMany(mappedBy = "subject")
+    private List<SubjectStudent> studentsAssoc = new ArrayList<>();
+
+    public Subject() {
+    }
+
+    public Subject(String name) {
+        this.name = name;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<SubjectStudent> getStudentsAssoc() {
+        return studentsAssoc;
+    }
+
+    public void setStudentsAssoc(List<SubjectStudent> studentsAssoc) {
+        this.studentsAssoc = studentsAssoc;
+    }
+}

+ 53 - 0
project-back/src/main/java/com/example/projectback/model/SubjectStudent.java

@@ -0,0 +1,53 @@
+package com.example.projectback.model;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name="subject_student")
+@IdClass(SubjectStudentId.class)
+public class SubjectStudent {
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "subject_id", referencedColumnName = "id")
+    private Subject subject;
+
+    @Id
+    @ManyToOne
+    @JoinColumn(name = "student_id", referencedColumnName = "id")
+    private Student student;
+
+    @Column(name = "payment_confirmed")
+    private boolean paymentConfirmed = false;
+
+    public SubjectStudent() {
+    }
+
+    public SubjectStudent(Subject subject, Student student) {
+        this.subject = subject;
+        this.student = student;
+    }
+
+    public Subject getSubject() {
+        return subject;
+    }
+
+    public void setSubject(Subject subject) {
+        this.subject = subject;
+    }
+
+    public Student getStudent() {
+        return student;
+    }
+
+    public void setStudent(Student student) {
+        this.student = student;
+    }
+
+    public boolean isPaymentConfirmed() {
+        return paymentConfirmed;
+    }
+
+    public void setPaymentConfirmed(boolean paymentConfirmed) {
+        this.paymentConfirmed = paymentConfirmed;
+    }
+}

+ 38 - 0
project-back/src/main/java/com/example/projectback/model/SubjectStudentId.java

@@ -0,0 +1,38 @@
+package com.example.projectback.model;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+public class SubjectStudentId implements Serializable {
+    private int subject;
+    private int student;
+
+    public int getSubject() {
+        return subject;
+    }
+
+    public void setSubject(int subject) {
+        this.subject = subject;
+    }
+
+    public int getStudent() {
+        return student;
+    }
+
+    public void setStudent(int student) {
+        this.student = student;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SubjectStudentId that = (SubjectStudentId) o;
+        return subject == that.subject && student == that.student;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(subject, student);
+    }
+}

+ 7 - 0
project-back/src/main/java/com/example/projectback/repository/SubjectRepository.java

@@ -0,0 +1,7 @@
+package com.example.projectback.repository;
+
+import com.example.projectback.model.Subject;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface SubjectRepository extends JpaRepository<Subject, Long> {
+}

+ 19 - 2
project-back/src/main/resources/data.sql

@@ -1,2 +1,19 @@
-INSERT INTO role (name) SELECT 'ROLE_ADMIN' WHERE NOT EXISTS(SELECT * FROM role WHERE role.name='ROLE_ADMIN');
-INSERT INTO role (name) SELECT 'ROLE_USER' WHERE NOT EXISTS(SELECT * FROM role WHERE role.name='ROLE_USER');
+INSERT INTO role (name)
+SELECT 'ROLE_ADMIN'
+WHERE NOT EXISTS(SELECT * FROM role WHERE role.name = 'ROLE_ADMIN');
+
+INSERT INTO role (name)
+SELECT 'ROLE_USER'
+WHERE NOT EXISTS(SELECT * FROM role WHERE role.name = 'ROLE_USER');
+
+INSERT INTO users (password, email)
+SELECT '$2a$10$JChJcsJNVtqXPi88MPNRuuBhyAUEfUt12n2Cf1weK8gvAAXfPzzZ2', 'prof.kowalski@example.com'
+WHERE NOT EXISTS(SELECT * FROM users WHERE users.email = 'prof.kowalski@example.com');
+
+INSERT INTO users_roles (user_id, roles_id)
+SELECT 1, 1
+WHERE NOT EXISTS(SELECT * FROM users_roles WHERE user_id=1 AND roles_id=1);
+
+INSERT INTO users_roles (user_id, roles_id)
+SELECT 1, 2
+WHERE NOT EXISTS(SELECT * FROM users_roles WHERE user_id=1 AND roles_id=2);

+ 1 - 0
project-front/src/app/app.component.html

@@ -3,6 +3,7 @@
   <div class="right">
     <a mat-raised-button [routerLink]="['/home']">Home</a>
     <a mat-raised-button [routerLink]="['/students']">Students</a>
+    <a mat-raised-button [routerLink]="['/subjects']">Subjects</a>
     <a *ngIf="!authority" mat-raised-button [routerLink]="['/auth/login']">Login</a>
     <a *ngIf="!authority" mat-raised-button [routerLink]="['/auth/signup']">Register</a>
     <button *ngIf="authority" mat-raised-button (click)="logout()">Logout</button>

+ 5 - 1
project-front/src/app/app.module.ts

@@ -14,10 +14,12 @@ import { httpInterceptorProviders } from './auth/auth-interceptor';
 import { RoleGuard } from "./guards/role.guard";
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { MaterialModule } from "./material/material.module";
+import { AddSubjectDialog, SubjectsComponent } from './subjects/subjects.component';
 
 const routes: Routes = [
   { path: 'home', component: HomeComponent },
   { path: 'students', component: StudentsComponent },
+  { path: 'subjects', component: SubjectsComponent },
   { path: 'user', component: UserComponent, canActivate: [RoleGuard], data: { roles: ['ROLE_USER', 'ROLE_ADMIN'] }, },
   { path: 'admin', component: AdminComponent, canActivate: [RoleGuard], data: { roles: ['ROLE_ADMIN'] }, },
   { path: 'auth/login', component: LoginComponent },
@@ -33,7 +35,9 @@ const routes: Routes = [
     LoginComponent,
     RegisterComponent,
     UserComponent,
-    AdminComponent
+    AdminComponent,
+    SubjectsComponent,
+    AddSubjectDialog
   ],
   imports: [
     BrowserModule,

+ 8 - 0
project-front/src/app/material/material.module.ts

@@ -5,6 +5,10 @@ import { MatFormFieldModule } from "@angular/material/form-field";
 import { MatInputModule } from "@angular/material/input";
 import { MatSnackBarModule } from "@angular/material/snack-bar";
 import { MatStepperModule } from "@angular/material/stepper";
+import { MatTableModule } from "@angular/material/table";
+import { MatDialogModule } from "@angular/material/dialog";
+import { MatPaginatorModule } from "@angular/material/paginator";
+import { MatCheckboxModule } from "@angular/material/checkbox";
 
 const MaterialComponents = [
   MatButtonModule,
@@ -13,6 +17,10 @@ const MaterialComponents = [
   MatInputModule,
   MatSnackBarModule,
   MatStepperModule,
+  MatTableModule,
+  MatDialogModule,
+  MatPaginatorModule,
+  MatCheckboxModule,
 ];
 
 @NgModule({

+ 1 - 1
project-front/src/app/students/student.model.spec.ts

@@ -2,6 +2,6 @@ import { Student } from './student.model';
 
 describe('Student', () => {
   it('should create an instance', () => {
-    expect(new Student()).toBeTruthy();
+    expect(new Student("John", "Doe", "john.doe@example.com", "123123123")).toBeTruthy();
   });
 });

+ 11 - 0
project-front/src/app/subjects/add-subject-dialog.component.html

@@ -0,0 +1,11 @@
+<h1 mat-dialog-title>Add new subject</h1>
+<div mat-dialog-content>
+  <mat-form-field appearance="fill">
+    <mat-label>Name</mat-label>
+    <input matInput [(ngModel)]="subject.name">
+  </mat-form-field>
+</div>
+<div mat-dialog-actions>
+  <button mat-button (click)="onNoClick()">Cancel</button>
+  <button mat-button [mat-dialog-close]="subject.name">Add</button>
+</div>

+ 7 - 0
project-front/src/app/subjects/subjects.component.css

@@ -0,0 +1,7 @@
+table {
+    width: 100%;
+}
+
+button {
+    margin: 20px;
+}

+ 44 - 0
project-front/src/app/subjects/subjects.component.html

@@ -0,0 +1,44 @@
+<button mat-raised-button color="primary" (click)="openDialog()">Add</button>
+<button mat-raised-button color="primary" (click)="delete()">Remove</button>
+
+<table *ngIf="subjectList" mat-table [dataSource]="subjectList">
+  <ng-container matColumnDef="select">
+    <th mat-header-cell *matHeaderCellDef>
+      <mat-checkbox (change)="$event ? masterToggle() : null"
+                    [checked]="selection.hasValue() && isAllSelected()"
+                    [indeterminate]="selection.hasValue() && !isAllSelected()"
+                    [aria-label]="checkboxLabel()">
+      </mat-checkbox>
+    </th>
+    <td mat-cell *matCellDef="let row">
+      <mat-checkbox (click)="$event.stopPropagation()"
+                    (change)="$event ? selection.toggle(row) : null"
+                    [checked]="selection.isSelected(row)"
+                    [aria-label]="checkboxLabel(row)">
+      </mat-checkbox>
+    </td>
+  </ng-container>
+
+  <ng-container matColumnDef="no">
+    <th mat-header-cell *matHeaderCellDef> No.</th>
+    <td mat-cell *matCellDef="let subject"> {{subject.id}} </td>
+  </ng-container>
+
+  <ng-container matColumnDef="name">
+    <th mat-header-cell *matHeaderCellDef> Name</th>
+    <td mat-cell *matCellDef="let subject"> {{subject.name}} </td>
+  </ng-container>
+
+  <ng-container matColumnDef="students">
+    <th mat-header-cell *matHeaderCellDef> Students</th>
+    <td mat-cell *matCellDef="let subject"> {{subject.studentsAssoc.length}} </td>
+  </ng-container>
+
+  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+</table>
+
+<mat-paginator [pageSizeOptions]="[5, 10, 20]"
+               showFirstLastButtons
+               aria-label="Select page">
+</mat-paginator>

+ 25 - 0
project-front/src/app/subjects/subjects.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SubjectsComponent } from './subjects.component';
+
+describe('SubjectsComponent', () => {
+  let component: SubjectsComponent;
+  let fixture: ComponentFixture<SubjectsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ SubjectsComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SubjectsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 105 - 0
project-front/src/app/subjects/subjects.component.ts

@@ -0,0 +1,105 @@
+import { AfterViewInit, Component, Inject, OnInit, ViewChild } from '@angular/core';
+import { Subject } from "./subjects.model";
+import { SubjectsService } from "./subjects.service";
+import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from "@angular/material/dialog";
+import { MatTable, MatTableDataSource } from "@angular/material/table";
+import { MatPaginator } from "@angular/material/paginator";
+import { SelectionModel } from "@angular/cdk/collections";
+
+@Component({
+  selector: 'app-subjects',
+  templateUrl: './subjects.component.html',
+  styleUrls: ['./subjects.component.css']
+})
+export class SubjectsComponent implements OnInit, AfterViewInit {
+  displayedColumns: string[] = ['select', 'no', 'name', 'students'];
+  subjectList = new MatTableDataSource<Subject>([]);
+  selection = new SelectionModel<Subject>(true, []);
+  subject?: Subject;
+  @ViewChild(MatTable) table: MatTable<Subject> | undefined;
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+
+  constructor(private subjectService: SubjectsService, public dialog: MatDialog) {
+  }
+
+  ngOnInit(): void {
+    this.getSubjects();
+  }
+
+  ngAfterViewInit() {
+    this.subjectList.paginator = this.paginator;
+  }
+
+  isAllSelected() {
+    const numSelected = this.selection.selected.length;
+    const numRows = this.subjectList.data.length;
+    return numSelected === numRows;
+  }
+
+  masterToggle() {
+    if (this.isAllSelected()) {
+      this.selection.clear();
+      return;
+    }
+
+    this.selection.select(...this.subjectList.data);
+  }
+
+  checkboxLabel(row?: Subject): string {
+    if (!row) {
+      return `${this.isAllSelected() ? 'deselect' : 'select'} all`;
+    }
+    return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${(row.id || 0) + 1}`;
+  }
+
+  getSubjects(): void {
+    this.subjectService.getSubjects().subscribe(subjectList => this.subjectList.data = subjectList);
+  }
+
+  openDialog(): void {
+    const dialogRef = this.dialog.open(AddSubjectDialog, {
+      width: '250px',
+      data: { subject: this.subject },
+    });
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        this.add(result);
+      }
+    })
+  }
+
+  add(name: string): void {
+    name = name.trim();
+    this.subjectService.addSubject({ name } as Subject).subscribe({
+        next: (subject: Subject) => {
+          this.subjectList.data = [...this.subjectList.data, subject];
+        },
+      }
+    )
+  }
+
+  delete(): void {
+    const selectedSubjects: Subject[] = this.selection.selected;
+    this.subjectService.deleteSubjects(selectedSubjects).subscribe({
+      next: ({ records }) => {
+        for (const rec of records) {
+          this.subjectList.data = this.subjectList.data.filter(subject => subject.id !== rec);
+        }
+      },
+    });
+  }
+
+}
+
+@Component({
+  selector: 'add-subject-dialog',
+  templateUrl: 'add-subject-dialog.component.html',
+})
+export class AddSubjectDialog {
+  constructor(public dialogRef: MatDialogRef<AddSubjectDialog>, @Inject(MAT_DIALOG_DATA) public subject: Subject) {
+  }
+
+  onNoClick(): void {
+    this.dialogRef.close();
+  }
+}

+ 11 - 0
project-front/src/app/subjects/subjects.model.ts

@@ -0,0 +1,11 @@
+import { Student } from "../students/student.model";
+
+export class Subject {
+  id?: number;
+  name: string;
+  studentsAssoc?: Student[];
+
+  constructor(name: string) {
+    this.name = name;
+  }
+}

+ 16 - 0
project-front/src/app/subjects/subjects.service.spec.ts

@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { SubjectsService } from './subjects.service';
+
+describe('SubjectsService', () => {
+  let service: SubjectsService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(SubjectsService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});

+ 50 - 0
project-front/src/app/subjects/subjects.service.ts

@@ -0,0 +1,50 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders } from "@angular/common/http";
+import { catchError, Observable, of, tap } from "rxjs";
+import { Subject } from "./subjects.model";
+
+const httpOptions = {
+  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
+};
+
+export interface Changeset {
+  records: [],
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class SubjectsService {
+
+  private subjectsUrl = 'http://localhost:8080/subjects';
+
+  constructor(private http: HttpClient) { }
+
+  getSubjects(): Observable<Subject[]> {
+    return this.http.get<Subject[]>(this.subjectsUrl);
+  }
+
+  addSubject(subject: Subject): Observable<Subject> {
+    return this.http.post<Subject>(this.subjectsUrl, subject, httpOptions).pipe(
+      tap((subjectAdded: Subject) => this.log(`added subject id=${subjectAdded.id}`)),
+      catchError(this.handleError<Subject>('addSubject'))
+    )
+  }
+
+  deleteSubjects(subjects: Subject[]): Observable<any> {
+    const ids = subjects.map(subject => subject.id);
+    return this.http.post(`${this.subjectsUrl}/delete`, {records: ids}, httpOptions);
+  }
+
+  private log(message: string): void {
+    console.log('SubjectService: ' + message);
+  }
+
+  private handleError<T>(operation = 'operation', result?: T) {
+    return (error: any): Observable<T> => {
+      console.log(error);
+      this.log(`${operation} failed: ${error.message}`);
+      return of(result as T);
+    }
+  }
+}