├── .gitignore ├── Assignment Submission.docx ├── _docker-compose.yml ├── back-end ├── Dockerfile ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── coderscampus │ │ │ ├── AssignmentSubmissionApp │ │ │ ├── config │ │ │ │ ├── MethodSecurityConfig.java │ │ │ │ └── SecurityConfig.java │ │ │ ├── domain │ │ │ │ ├── Assignment.java │ │ │ │ ├── Authorities.java │ │ │ │ ├── Comment.java │ │ │ │ └── User.java │ │ │ ├── dto │ │ │ │ ├── AssignmentResponseDto.java │ │ │ │ ├── AuthCredentialsRequest.java │ │ │ │ ├── BootcampAssignmentResponseDto.java │ │ │ │ ├── CommentDto.java │ │ │ │ ├── GhlSlackMessageCustomData.java │ │ │ │ ├── GhlWebhookRequest.java │ │ │ │ ├── JavaFoundationsAssignmentResponseDto.java │ │ │ │ ├── UserDto.java │ │ │ │ └── UserKeyDto.java │ │ │ ├── enums │ │ │ │ ├── AssignmentEnum.java │ │ │ │ ├── AssignmentStatusEnum.java │ │ │ │ ├── AuthorityEnum.java │ │ │ │ ├── BootcampAssignmentEnum.java │ │ │ │ └── JavaFoundationsAssignmentEnum.java │ │ │ ├── filter │ │ │ │ └── JwtFilter.java │ │ │ ├── repository │ │ │ │ ├── AssignmentRepository.java │ │ │ │ ├── AuthorityRepository.java │ │ │ │ ├── CommentRepository.java │ │ │ │ └── UserRepository.java │ │ │ ├── service │ │ │ │ ├── AssignmentService.java │ │ │ │ ├── CommentService.java │ │ │ │ ├── HypeUpService.java │ │ │ │ ├── NotificationService.java │ │ │ │ ├── OrderService.java │ │ │ │ ├── UserDetailsServiceImpl.java │ │ │ │ └── UserService.java │ │ │ ├── util │ │ │ │ ├── AuthorityUtil.java │ │ │ │ ├── CustomPasswordEncoder.java │ │ │ │ └── JwtUtil.java │ │ │ └── web │ │ │ │ ├── AssignmentController.java │ │ │ │ ├── AuthController.java │ │ │ │ ├── CommentController.java │ │ │ │ ├── PingController.java │ │ │ │ └── UserController.java │ │ │ ├── AssignmentSubmissionApplication.java │ │ │ ├── PrimaryDbConfig.java │ │ │ ├── PrimaryDbConfigDev.java │ │ │ ├── PrimaryDbConfigProd.java │ │ │ ├── ProffessoDbConfig.java │ │ │ ├── ProffessoDbConfigDev.java │ │ │ ├── ProffessoDbConfigProd.java │ │ │ ├── ServletInitializer.java │ │ │ └── proffesso │ │ │ ├── domain │ │ │ ├── Authority.java │ │ │ ├── Offer.java │ │ │ ├── Order.java │ │ │ └── ProffessoUser.java │ │ │ └── repository │ │ │ ├── OfferRepository.java │ │ │ ├── OrderRepository.java │ │ │ └── ProffessoUserRepo.java │ └── resources │ │ ├── application-dev.properties │ │ ├── application-local.properties │ │ ├── application-prod.properties │ │ └── logback-spring.xml │ └── test │ └── java │ ├── AssignmentSubmissionConverterTest.java │ └── com │ └── coderscampus │ └── AssignmentSubmissionApp │ └── service │ └── NotificationServiceTest.java ├── docker-compose.yml ├── front-end ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── nginx.conf ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── AssignmentView │ └── index.js │ ├── CodeReviewAssignmentView │ └── index.js │ ├── CodeReviewerDashboard │ └── index.js │ ├── Comment │ └── index.js │ ├── CommentContainer │ └── index.js │ ├── Dashboard │ └── index.js │ ├── Homepage │ ├── homepage.css │ └── index.js │ ├── Images │ ├── coders-campus-assignment-example.png │ ├── coders-campus-logo-dark.png │ └── coders-campus-logo.png │ ├── InstructorDashboard │ ├── index.js │ └── instructor-dashboard.css │ ├── InstructorStudentEditModal │ └── index.js │ ├── Login │ └── index.js │ ├── MultiColorProgressBar │ ├── index.js │ └── multicolor-progress-bar.css │ ├── NavBar │ └── index.js │ ├── PrivateRoute │ └── index.js │ ├── Register │ └── index.js │ ├── Services │ ├── assignmentDueDatesService.js │ ├── fetchService.js │ ├── statusService.js │ └── validate.js │ ├── StatusBadge │ └── index.js │ ├── UserProvider │ └── index.js │ ├── custom.scss │ ├── index.css │ ├── index.js │ ├── reportWebVitals.js │ ├── setupTests.js │ └── util │ ├── useInterval.js │ └── useLocalStorage.js └── package-lock.json /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | .mvn 8 | .settings 9 | target 10 | 11 | ### STS ### 12 | .apt_generated 13 | .classpath 14 | .factorypath 15 | .project 16 | .settings 17 | .springBeans 18 | .sts4-cache 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | 26 | ### NetBeans ### 27 | /nbproject/private/ 28 | /nbbuild/ 29 | /dist/ 30 | /nbdist/ 31 | /.nb-gradle/ 32 | build/ 33 | !**/src/main/**/build/ 34 | !**/src/test/**/build/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | mvnw.cmd 39 | mvnw 40 | front-end/Dockerfile.old 41 | back-end/src/main/resources/application.properties 42 | docker-compose.yml 43 | docker-compose-local.yml 44 | back-end/logs/spring-boot-logger.log 45 | _docker-compose.yml 46 | _docker-compose.yml 47 | *.log 48 | ecs-push-assignment-app.sh 49 | -------------------------------------------------------------------------------- /Assignment Submission.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tp02ga/AssignmentSubmissionApp/d76607ed82e5a43672c9ddb8af8947a4b21fa1bb/Assignment Submission.docx -------------------------------------------------------------------------------- /_docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | back-end: 3 | build: 4 | context: back-end 5 | dockerfile: Dockerfile 6 | network: host 7 | ports: 8 | - 8080:8080 9 | environment: 10 | spring.profiles.active: prod 11 | db.url: jdbc:mysql://somehost:3306/assignment_submission_db 12 | db.username: username 13 | db.password: password 14 | networks: 15 | - submission-app 16 | 17 | front-end: 18 | build: 19 | context: front-end 20 | dockerfile: Dockerfile 21 | expose: 22 | - "3000" 23 | ports: 24 | - 3000:3000 25 | depends_on: 26 | - back-end 27 | networks: 28 | - submission-app 29 | 30 | webserver: 31 | image: nginx:latest 32 | ports: 33 | - 80:80 34 | - 443:443 35 | restart: always 36 | volumes: 37 | - ./nginx/conf/:/etc/nginx/conf.d/:ro 38 | - ./certbot/www:/var/www/certbot/:ro 39 | - ./certbot/conf:/etc/letsencrypt 40 | depends_on: 41 | - front-end 42 | networks: 43 | - submission-app 44 | 45 | certbot: 46 | image: certbot/certbot:latest 47 | volumes: 48 | - ./certbot/www/:/var/www/certbot/:rw 49 | - ./certbot/conf:/etc/letsencrypt 50 | command: 51 | - renew 52 | # - certonly 53 | # - --webroot 54 | # - -w 55 | # - /var/www/certbot/ 56 | # - --email=trevor@coderscampus.com 57 | # - --agree-tos 58 | # - --no-eff-email 59 | # - -d 60 | # - assignments.coderscampus.com 61 | 62 | volumes: 63 | cert_home: 64 | 65 | networks: 66 | submission-app: -------------------------------------------------------------------------------- /back-end/Dockerfile: -------------------------------------------------------------------------------- 1 | #### Stage 1: Build the application 2 | FROM adoptopenjdk/openjdk11:ubi as build 3 | 4 | # Set the current working directory inside the image 5 | WORKDIR /app 6 | 7 | # Copy maven executable to the image 8 | COPY mvnw . 9 | COPY .mvn .mvn 10 | 11 | # Copy the pom.xml file 12 | COPY pom.xml . 13 | 14 | # Build all the dependencies in preparation to go offline. 15 | # This is a separate step so the dependencies will be cached unless 16 | # the pom.xml file has changed. 17 | RUN ./mvnw dependency:go-offline -B 18 | 19 | # Copy the project source 20 | COPY src src 21 | 22 | # Package the application 23 | RUN ./mvnw package 24 | RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) 25 | 26 | 27 | #### Stage 2: A minimal docker image with command to run the app 28 | FROM adoptopenjdk/openjdk11:ubi 29 | 30 | ARG DEPENDENCY=/app/target/dependency 31 | 32 | # Copy project dependencies from the build stage 33 | COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib 34 | COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF 35 | COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app 36 | 37 | ENTRYPOINT ["java","-cp","app:app/lib/*","com.coderscampus.AssignmentSubmissionApplication"] -------------------------------------------------------------------------------- /back-end/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.6.2 10 | 11 | 12 | com.coderscampus 13 | AssignmentSubmissionApp 14 | 0.0.1-SNAPSHOT 15 | jar 16 | AssignmentSubmissionApp 17 | Demo project for Spring Boot 18 | 19 | 11 20 | 21 | 22 | 23 | 24 | com.fasterxml.jackson.datatype 25 | jackson-datatype-jsr310 26 | 27 | 28 | org.json 29 | json 30 | 20220320 31 | 32 | 33 | io.jsonwebtoken 34 | jjwt 35 | 0.9.1 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-data-jpa 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-security 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-web 48 | 49 | 50 | 51 | mysql 52 | mysql-connector-java 53 | runtime 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-tomcat 58 | 59 | 60 | 61 | org.junit.jupiter 62 | junit-jupiter-engine 63 | test 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-starter-test 68 | test 69 | 70 | 71 | org.springframework.security 72 | spring-security-test 73 | test 74 | 75 | 76 | com.sun.mail 77 | javax.mail 78 | 1.6.2 79 | 80 | 81 | 82 | 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-maven-plugin 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-surefire-plugin 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/config/MethodSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 5 | import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; 6 | 7 | @Configuration 8 | @EnableGlobalMethodSecurity( 9 | prePostEnabled = true, 10 | securedEnabled = true, 11 | jsr250Enabled = true) 12 | public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.config; 2 | 3 | import javax.servlet.http.HttpServletResponse; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 15 | 16 | import com.coderscampus.AssignmentSubmissionApp.filter.JwtFilter; 17 | import com.coderscampus.AssignmentSubmissionApp.util.CustomPasswordEncoder; 18 | 19 | 20 | @EnableWebSecurity 21 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 22 | 23 | @Autowired 24 | private UserDetailsService userDetailsService; 25 | @Autowired 26 | private CustomPasswordEncoder customPasswordEncoder; 27 | @Autowired 28 | private JwtFilter jwtFilter; 29 | 30 | @Override @Bean 31 | public AuthenticationManager authenticationManagerBean() throws Exception { 32 | return super.authenticationManagerBean(); 33 | } 34 | 35 | @Override 36 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 37 | auth.userDetailsService(userDetailsService) 38 | .passwordEncoder(customPasswordEncoder.getPasswordEncoder()); 39 | } 40 | 41 | @Override 42 | protected void configure(HttpSecurity http) throws Exception { 43 | http = http.csrf().disable().cors().disable(); 44 | 45 | http = http.sessionManagement() 46 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 47 | .and(); 48 | 49 | http = http.exceptionHandling() 50 | .authenticationEntryPoint((request, response, ex) -> { 51 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage()); 52 | }).and(); 53 | 54 | http.authorizeRequests() 55 | .antMatchers("/api/auth/**").permitAll() 56 | .antMatchers("/api").permitAll() 57 | .anyRequest().authenticated(); 58 | 59 | http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); 60 | 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/domain/Assignment.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.domain; 2 | 3 | import javax.persistence.*; 4 | import java.time.LocalDateTime; 5 | import java.util.Objects; 6 | 7 | @Entity // will create table called assignment (based on Class name) 8 | public class Assignment { 9 | 10 | @Id 11 | @GeneratedValue(strategy = GenerationType.IDENTITY) 12 | private Long id; 13 | private Integer number; 14 | private String name; 15 | private String status; 16 | private String githubUrl; 17 | private String branch; 18 | private String codeReviewVideoUrl; 19 | @ManyToOne(optional = false) 20 | private User user; 21 | @ManyToOne 22 | private User codeReviewer; 23 | private LocalDateTime submittedDate; 24 | private LocalDateTime resubmittedDate; 25 | private LocalDateTime createdDate; 26 | private LocalDateTime lastModified; 27 | 28 | public Long getId() { 29 | return id; 30 | } 31 | 32 | public void setId(Long id) { 33 | this.id = id; 34 | } 35 | 36 | public String getStatus() { 37 | return status; 38 | } 39 | 40 | public void setStatus(String status) { 41 | this.status = status; 42 | } 43 | 44 | public String getGithubUrl() { 45 | return githubUrl; 46 | } 47 | 48 | public void setGithubUrl(String githubUrl) { 49 | this.githubUrl = githubUrl; 50 | } 51 | 52 | public String getBranch() { 53 | return branch; 54 | } 55 | 56 | public void setBranch(String branch) { 57 | this.branch = branch; 58 | } 59 | 60 | public String getCodeReviewVideoUrl() { 61 | return codeReviewVideoUrl; 62 | } 63 | 64 | public void setCodeReviewVideoUrl(String codeReviewVideoUrl) { 65 | this.codeReviewVideoUrl = codeReviewVideoUrl; 66 | } 67 | 68 | public User getUser() { 69 | return user; 70 | } 71 | 72 | public void setUser(User user) { 73 | this.user = user; 74 | } 75 | 76 | public Integer getNumber() { 77 | return number; 78 | } 79 | 80 | public void setNumber(Integer number) { 81 | this.number = number; 82 | } 83 | 84 | public User getCodeReviewer() { 85 | return codeReviewer; 86 | } 87 | 88 | public void setCodeReviewer(User codeReviewer) { 89 | this.codeReviewer = codeReviewer; 90 | } 91 | 92 | public LocalDateTime getSubmittedDate() { 93 | return submittedDate; 94 | } 95 | 96 | public LocalDateTime getCreatedDate() { 97 | return createdDate; 98 | } 99 | 100 | public void setCreatedDate(LocalDateTime createdDate) { 101 | this.createdDate = createdDate; 102 | } 103 | 104 | public LocalDateTime getLastModified() { 105 | return lastModified; 106 | } 107 | 108 | public void setLastModified(LocalDateTime lastModified) { 109 | this.lastModified = lastModified; 110 | } 111 | 112 | public String getName() { 113 | return name; 114 | } 115 | 116 | public void setName(String name) { 117 | this.name = name; 118 | } 119 | 120 | public LocalDateTime getResubmittedDate() { 121 | return resubmittedDate; 122 | } 123 | 124 | public void setResubmittedDate(LocalDateTime resubmittedDate) { 125 | this.resubmittedDate = resubmittedDate; 126 | } 127 | 128 | @Override 129 | public boolean equals(Object o) { 130 | if (this == o) return true; 131 | if (o == null || getClass() != o.getClass()) return false; 132 | Assignment that = (Assignment) o; 133 | return number.equals(that.number) && user.equals(that.user); 134 | } 135 | 136 | @Override 137 | public int hashCode() { 138 | return Objects.hash(number, user); 139 | } 140 | 141 | public void setSubmittedDate(LocalDateTime submittedDate) { 142 | this.submittedDate = submittedDate; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/domain/Authorities.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.domain; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | import javax.persistence.ManyToOne; 8 | import javax.persistence.Table; 9 | 10 | import org.springframework.security.core.GrantedAuthority; 11 | 12 | import com.fasterxml.jackson.annotation.JsonIgnore; 13 | 14 | @Entity 15 | @Table(name = "authorities") 16 | public class Authorities implements GrantedAuthority { 17 | private static final long serialVersionUID = 4065375140379002510L; 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Long id; 21 | @ManyToOne 22 | @JsonIgnore 23 | private User user; 24 | private String authority; 25 | 26 | public Long getId() { 27 | return id; 28 | } 29 | 30 | public void setId(Long id) { 31 | this.id = id; 32 | } 33 | 34 | public User getUser() { 35 | return user; 36 | } 37 | 38 | public void setUser(User user) { 39 | this.user = user; 40 | } 41 | 42 | @Override 43 | public String getAuthority() { 44 | return authority; 45 | } 46 | 47 | public void setAuthority(String authority) { 48 | this.authority = authority; 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | final int prime = 31; 54 | int result = 1; 55 | result = prime * result + ((authority == null) ? 0 : authority.hashCode()); 56 | return result; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object obj) { 61 | if (this == obj) 62 | return true; 63 | if (obj == null) 64 | return false; 65 | if (getClass() != obj.getClass()) 66 | return false; 67 | Authorities other = (Authorities) obj; 68 | if (authority == null) { 69 | if (other.authority != null) 70 | return false; 71 | } else if (!authority.equals(other.authority)) 72 | return false; 73 | return true; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/domain/Comment.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.domain; 2 | 3 | import java.time.ZonedDateTime; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.GenerationType; 9 | import javax.persistence.Id; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.ManyToOne; 12 | import javax.persistence.Table; 13 | 14 | import com.fasterxml.jackson.annotation.JsonIgnore; 15 | 16 | @Entity 17 | @Table(name = "comments") 18 | public class Comment { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | @JsonIgnore 23 | @ManyToOne 24 | private Assignment assignment; 25 | @ManyToOne 26 | @JoinColumn(name = "user_id") 27 | private User createdBy; 28 | private ZonedDateTime createdDate; 29 | @Column(columnDefinition = "TEXT") 30 | private String text; 31 | 32 | public Long getId() { 33 | return id; 34 | } 35 | 36 | public void setId(Long id) { 37 | this.id = id; 38 | } 39 | 40 | public ZonedDateTime getCreatedDate() { 41 | return createdDate; 42 | } 43 | 44 | public void setCreatedDate(ZonedDateTime createdDate) { 45 | this.createdDate = createdDate; 46 | } 47 | 48 | public User getCreatedBy() { 49 | return createdBy; 50 | } 51 | 52 | public void setCreatedBy(User createdBy) { 53 | this.createdBy = createdBy; 54 | } 55 | 56 | public String getText() { 57 | return text; 58 | } 59 | 60 | public void setText(String text) { 61 | this.text = text; 62 | } 63 | 64 | public Assignment getAssignment() { 65 | return assignment; 66 | } 67 | 68 | public void setAssignment(Assignment assignment) { 69 | this.assignment = assignment; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.domain; 2 | 3 | import com.coderscampus.proffesso.domain.ProffessoUser; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | 7 | import javax.persistence.*; 8 | import java.time.LocalDate; 9 | import java.util.HashSet; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | import java.util.Set; 13 | 14 | @Entity 15 | @Table(name = "users") 16 | public class User implements UserDetails { 17 | private static final long serialVersionUID = 1840361243951715062L; 18 | @Id 19 | private Long id; 20 | private String username; 21 | @JsonIgnore 22 | private String password; 23 | private String name; 24 | @OneToMany(fetch = FetchType.EAGER, mappedBy = "user") 25 | private Set authorities = new HashSet<>(); 26 | private LocalDate cohortStartDate; 27 | private Integer bootcampDurationInWeeks; 28 | 29 | public User() { 30 | } 31 | 32 | public User(ProffessoUser proffessoUser, Optional appUserOpt) { 33 | appUserOpt.ifPresent(appUser -> { 34 | appUser.getAuthorities() 35 | .stream() 36 | .forEach(auth -> { 37 | Authorities newAuth = new Authorities(); 38 | newAuth.setAuthority(auth.getAuthority()); 39 | newAuth.setUser(this); 40 | newAuth.setId(auth.getId()); 41 | this.getAuthorities().add(newAuth); 42 | }); 43 | this.bootcampDurationInWeeks = appUser.getBootcampDurationInWeeks(); 44 | this.cohortStartDate = appUser.getCohortStartDate(); 45 | }); 46 | this.username = proffessoUser.getEmail(); 47 | this.id = proffessoUser.getId(); 48 | this.name = proffessoUser.getName(); 49 | this.password = proffessoUser.getPassword(); 50 | 51 | } 52 | 53 | public Integer getBootcampDurationInWeeks() { 54 | return bootcampDurationInWeeks; 55 | } 56 | 57 | public void setBootcampDurationInWeeks(Integer bootcampDurationInWeeks) { 58 | this.bootcampDurationInWeeks = bootcampDurationInWeeks; 59 | } 60 | 61 | public LocalDate getCohortStartDate() { 62 | return cohortStartDate; 63 | } 64 | 65 | public void setCohortStartDate(LocalDate cohortStartDate) { 66 | this.cohortStartDate = cohortStartDate; 67 | } 68 | 69 | public Long getId() { 70 | return id; 71 | } 72 | 73 | public void setId(Long id) { 74 | this.id = id; 75 | } 76 | 77 | @Override 78 | public String getPassword() { 79 | return password; 80 | } 81 | 82 | public void setPassword(String password) { 83 | this.password = password; 84 | } 85 | 86 | public String getName() { 87 | return name; 88 | } 89 | 90 | public void setName(String name) { 91 | this.name = name; 92 | } 93 | 94 | @Override 95 | public Set getAuthorities() { 96 | return authorities; 97 | } 98 | 99 | public void setAuthorities(Set authorities) { 100 | this.authorities = authorities; 101 | } 102 | 103 | @Override 104 | public String getUsername() { 105 | return username; 106 | } 107 | 108 | public void setUsername(String username) { 109 | this.username = username; 110 | } 111 | 112 | @Override 113 | public boolean isAccountNonExpired() { 114 | return true; 115 | } 116 | 117 | @Override 118 | public boolean isAccountNonLocked() { 119 | return true; 120 | } 121 | 122 | @Override 123 | public boolean isCredentialsNonExpired() { 124 | return true; 125 | } 126 | 127 | @Override 128 | public boolean isEnabled() { 129 | return true; 130 | } 131 | 132 | @Override 133 | public String toString() { 134 | return "User{" + 135 | "id=" + id + 136 | ", username='" + username + '\'' + 137 | ", name='" + name + '\'' + 138 | ", cohortStartDate=" + cohortStartDate + 139 | ", bootcampDuationInWeeks=" + bootcampDurationInWeeks + 140 | '}'; 141 | } 142 | 143 | @Override 144 | public boolean equals(Object o) { 145 | if (this == o) return true; 146 | if (o == null || getClass() != o.getClass()) return false; 147 | User user = (User) o; 148 | return id.equals(user.id); 149 | } 150 | 151 | @Override 152 | public int hashCode() { 153 | return Objects.hash(id); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/AssignmentResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.enums.AssignmentEnum; 4 | 5 | public interface AssignmentResponseDto { 6 | AssignmentEnum[] getAssignmentEnums(); 7 | } 8 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/AuthCredentialsRequest.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | public class AuthCredentialsRequest { 4 | 5 | private String username; 6 | private String password; 7 | 8 | public String getUsername() { 9 | return username; 10 | } 11 | 12 | public void setUsername(String username) { 13 | this.username = username; 14 | } 15 | 16 | public String getPassword() { 17 | return password; 18 | } 19 | 20 | public void setPassword(String password) { 21 | this.password = password; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/BootcampAssignmentResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.Assignment; 4 | import com.coderscampus.AssignmentSubmissionApp.enums.AssignmentEnum; 5 | import com.coderscampus.AssignmentSubmissionApp.enums.AssignmentStatusEnum; 6 | import com.coderscampus.AssignmentSubmissionApp.enums.BootcampAssignmentEnum; 7 | 8 | public class BootcampAssignmentResponseDto implements AssignmentResponseDto { 9 | 10 | private Assignment assignment; 11 | private BootcampAssignmentEnum[] bootcampAssignmentEnums = BootcampAssignmentEnum.values(); 12 | private AssignmentStatusEnum[] statusEnums = AssignmentStatusEnum.values(); 13 | 14 | public BootcampAssignmentResponseDto() { 15 | } 16 | 17 | public BootcampAssignmentResponseDto(Assignment assignment) { 18 | super(); 19 | this.assignment = assignment; 20 | } 21 | 22 | public Assignment getAssignment() { 23 | return assignment; 24 | } 25 | 26 | public void setAssignment(Assignment assignment) { 27 | this.assignment = assignment; 28 | } 29 | 30 | @Override 31 | public AssignmentEnum[] getAssignmentEnums() { 32 | return bootcampAssignmentEnums; 33 | } 34 | 35 | public AssignmentStatusEnum[] getStatusEnums() { 36 | return statusEnums; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/CommentDto.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | import java.time.ZonedDateTime; 4 | 5 | public class CommentDto { 6 | private Long id; 7 | private Long assignmentId; 8 | private String text; 9 | private String user; 10 | private ZonedDateTime createdDate; 11 | 12 | public Long getAssignmentId() { 13 | return assignmentId; 14 | } 15 | 16 | public void setAssignmentId(Long assignmentId) { 17 | this.assignmentId = assignmentId; 18 | } 19 | 20 | public String getText() { 21 | return text; 22 | } 23 | 24 | public void setText(String text) { 25 | this.text = text; 26 | } 27 | 28 | public String getUser() { 29 | return user; 30 | } 31 | 32 | public void setUser(String user) { 33 | this.user = user; 34 | } 35 | 36 | public Long getId() { 37 | return id; 38 | } 39 | 40 | public void setId(Long id) { 41 | this.id = id; 42 | } 43 | 44 | public ZonedDateTime getCreatedDate() { 45 | return createdDate; 46 | } 47 | 48 | public void setCreatedDate(ZonedDateTime createdDate) { 49 | this.createdDate = createdDate; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "CommentDto [id=" + id + ", assignmentId=" + assignmentId + ", text=" + text + ", user=" + user 55 | + ", createdDate=" + createdDate + "]"; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/GhlSlackMessageCustomData.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class GhlSlackMessageCustomData { 7 | private String channel; 8 | private String message; 9 | 10 | public String getChannel() { 11 | return channel; 12 | } 13 | 14 | public void setChannel(String channel) { 15 | this.channel = channel; 16 | } 17 | 18 | public String getMessage() { 19 | return message; 20 | } 21 | 22 | public void setMessage(String message) { 23 | this.message = message; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/GhlWebhookRequest.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class GhlWebhookRequest { 7 | private GhlSlackMessageCustomData customData; 8 | 9 | public GhlSlackMessageCustomData getCustomData() { 10 | return customData; 11 | } 12 | 13 | public void setCustomData(GhlSlackMessageCustomData customData) { 14 | this.customData = customData; 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/JavaFoundationsAssignmentResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.Assignment; 4 | import com.coderscampus.AssignmentSubmissionApp.enums.AssignmentEnum; 5 | import com.coderscampus.AssignmentSubmissionApp.enums.AssignmentStatusEnum; 6 | import com.coderscampus.AssignmentSubmissionApp.enums.JavaFoundationsAssignmentEnum; 7 | 8 | public class JavaFoundationsAssignmentResponseDto implements AssignmentResponseDto { 9 | private Assignment assignment; 10 | private AssignmentStatusEnum[] statusEnums = AssignmentStatusEnum.values(); 11 | private JavaFoundationsAssignmentEnum[] javaFoundationsAssignmentEnums = JavaFoundationsAssignmentEnum.values(); 12 | 13 | public JavaFoundationsAssignmentResponseDto(Assignment assignment) { 14 | super(); 15 | this.assignment = assignment; 16 | } 17 | 18 | public Assignment getAssignment() { 19 | return assignment; 20 | } 21 | 22 | public void setAssignment(Assignment assignment) { 23 | this.assignment = assignment; 24 | } 25 | 26 | @Override 27 | public AssignmentEnum[] getAssignmentEnums() { 28 | return javaFoundationsAssignmentEnums; 29 | } 30 | 31 | public AssignmentStatusEnum[] getStatusEnums() { 32 | return statusEnums; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | public class UserDto { 6 | @JsonProperty("name") 7 | private String name; 8 | @JsonProperty("username") 9 | private String username; 10 | @JsonProperty("password") 11 | private String password; 12 | public String getName() { 13 | return name; 14 | } 15 | public void setName(String name) { 16 | this.name = name; 17 | } 18 | public String getUsername() { 19 | return username; 20 | } 21 | public void setUsername(String username) { 22 | this.username = username; 23 | } 24 | public String getPassword() { 25 | return password; 26 | } 27 | public void setPassword(String password) { 28 | this.password = password; 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/dto/UserKeyDto.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.dto; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 6 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; 7 | 8 | import java.time.LocalDate; 9 | import java.util.Objects; 10 | 11 | public class UserKeyDto implements Comparable { 12 | private String email; 13 | private String name; 14 | private LocalDate startDate; 15 | private LocalDate cohortStartDate; 16 | private Integer bootcampDurationInWeeks; 17 | 18 | public UserKeyDto(String email, 19 | String name, 20 | LocalDate startDate, 21 | Integer bootcampDurationInWeeks) { 22 | this.email = email; 23 | this.name = name; 24 | this.startDate = startDate; 25 | this.bootcampDurationInWeeks = bootcampDurationInWeeks; 26 | } 27 | 28 | public LocalDate getStartDate() { 29 | return startDate; 30 | } 31 | 32 | public Integer getBootcampDurationInWeeks() { 33 | return bootcampDurationInWeeks; 34 | } 35 | 36 | public String getEmail() { 37 | return email; 38 | } 39 | 40 | public String getName() { 41 | return name; 42 | } 43 | 44 | public void setEmail(String email) { 45 | this.email = email; 46 | } 47 | 48 | public void setName(String name) { 49 | this.name = name; 50 | } 51 | 52 | public void setStartDate(LocalDate startDate) { 53 | this.startDate = startDate; 54 | } 55 | 56 | public void setBootcampDurationInWeeks(Integer bootcampDurationInWeeks) { 57 | this.bootcampDurationInWeeks = bootcampDurationInWeeks; 58 | } 59 | 60 | public LocalDate getCohortStartDate() { 61 | return cohortStartDate; 62 | } 63 | 64 | public void setCohortStartDate(LocalDate cohortStartDate) { 65 | this.cohortStartDate = cohortStartDate; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | ObjectMapper mapper = new ObjectMapper(); 71 | JavaTimeModule module = new JavaTimeModule(); 72 | 73 | module.addSerializer(LocalDateSerializer.INSTANCE); 74 | mapper.registerModule(module); 75 | try { 76 | String jsonString = mapper.writeValueAsString(this); 77 | return jsonString; 78 | } catch (JsonProcessingException e) { 79 | throw new RuntimeException(e); 80 | } 81 | } 82 | 83 | @Override 84 | public boolean equals(Object o) { 85 | if (this == o) return true; 86 | if (o == null || getClass() != o.getClass()) return false; 87 | UserKeyDto that = (UserKeyDto) o; 88 | return email.equals(that.email); 89 | } 90 | 91 | @Override 92 | public int hashCode() { 93 | return Objects.hash(email); 94 | } 95 | 96 | @Override 97 | public int compareTo(UserKeyDto that) { 98 | if (this.getStartDate() == null) 99 | throw new IllegalStateException("User " + this.getEmail() + " has no cohort start date."); 100 | if (that.getStartDate() == null) 101 | throw new IllegalStateException("User " + that.getEmail() + " has no cohort start date."); 102 | if (this.getStartDate().compareTo(that.getStartDate()) == 0) { 103 | return this.getEmail().compareTo(that.getEmail()); 104 | } else { 105 | return that.getStartDate().compareTo(this.getStartDate()); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/enums/AssignmentEnum.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.enums; 2 | 3 | public interface AssignmentEnum { 4 | int getAssignmentNum(); 5 | 6 | String getAssignmentName(); 7 | } 8 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/enums/AssignmentStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.enums; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | 5 | @JsonFormat(shape = JsonFormat.Shape.OBJECT) 6 | public enum AssignmentStatusEnum { 7 | PENDING_SUBMISSION("Pending Submission", 1), 8 | SUBMITTED("Submitted", 2), 9 | IN_REVIEW("In Review", 3), 10 | NEEDS_UPDATE("Needs Update", 4), 11 | COMPLETED("Completed", 5), 12 | RESUBMITTED("Resubmitted", 6); 13 | 14 | private String status; 15 | private Integer step; 16 | 17 | AssignmentStatusEnum(String status, Integer step) { 18 | this.status = status; 19 | this.step = step; 20 | } 21 | 22 | public String getStatus() { 23 | return status; 24 | } 25 | 26 | public Integer getStep() { 27 | return step; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/enums/AuthorityEnum.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.enums; 2 | 3 | public enum AuthorityEnum { 4 | 5 | ROLE_STUDENT, 6 | ROLE_CODE_REVIEWER, 7 | ROLE_ADMIN; 8 | } 9 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/enums/BootcampAssignmentEnum.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.enums; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | 5 | @JsonFormat(shape = JsonFormat.Shape.OBJECT) 6 | public enum BootcampAssignmentEnum implements AssignmentEnum { 7 | ASSIGNMENT_1(1, "HTML Assignment"), 8 | ASSIGNMENT_2(2, "Guessing Game"), 9 | ASSIGNMENT_3(3, "User Login"), 10 | ASSIGNMENT_4(4, "Student Course List"), 11 | ASSIGNMENT_5(5, "Custom Array List"), 12 | ASSIGNMENT_6(6, "Reports with Streams"), 13 | ASSIGNMENT_7(7, "Unit Testing"), 14 | ASSIGNMENT_8(8, "Multi-threading"), 15 | ASSIGNMENT_9(9, "Spring MVC"), 16 | ASSIGNMENT_10(10, "RESTful Service"), 17 | ASSIGNMENT_11(11, "Full-Stack with Thymeleaf"), 18 | ASSIGNMENT_12(12, "Reports with SQL"), 19 | ASSIGNMENT_13(13, "Online Bank"), 20 | ASSIGNMENT_14(14, "Chatting with JS"), 21 | FINAL_PROJECT(15, "Final Project"); 22 | 23 | private int assignmentNum; 24 | private String assignmentName; 25 | 26 | BootcampAssignmentEnum(int assignmentNum, String assignmentName) { 27 | this.assignmentNum = assignmentNum; 28 | this.assignmentName = assignmentName; 29 | } 30 | 31 | @Override 32 | public int getAssignmentNum() { 33 | return assignmentNum; 34 | } 35 | 36 | @Override 37 | public String getAssignmentName() { 38 | return assignmentName; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/enums/JavaFoundationsAssignmentEnum.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.enums; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | 5 | @JsonFormat(shape = JsonFormat.Shape.OBJECT) 6 | public enum JavaFoundationsAssignmentEnum implements AssignmentEnum { 7 | ASSIGNMENT_1(1, "Compounding Interest Calculator"), 8 | ASSIGNMENT_2(2, "Job Data Parsing & Filtering"); 9 | private int assignmentNum; 10 | private String assignmentName; 11 | 12 | JavaFoundationsAssignmentEnum(int assignmentNum, String assignmentName) { 13 | this.assignmentNum = assignmentNum; 14 | this.assignmentName = assignmentName; 15 | } 16 | 17 | @Override 18 | public int getAssignmentNum() { 19 | return assignmentNum; 20 | } 21 | 22 | @Override 23 | public String getAssignmentName() { 24 | return assignmentName; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/filter/JwtFilter.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.filter; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Optional; 7 | 8 | import javax.servlet.FilterChain; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.Cookie; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 16 | import org.springframework.security.core.context.SecurityContextHolder; 17 | import org.springframework.security.core.userdetails.UserDetails; 18 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 19 | import org.springframework.stereotype.Component; 20 | import org.springframework.web.filter.OncePerRequestFilter; 21 | 22 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 23 | import com.coderscampus.AssignmentSubmissionApp.repository.UserRepository; 24 | import com.coderscampus.AssignmentSubmissionApp.util.JwtUtil; 25 | import com.coderscampus.proffesso.repository.ProffessoUserRepo; 26 | 27 | import io.jsonwebtoken.ExpiredJwtException; 28 | import io.jsonwebtoken.SignatureException; 29 | 30 | @Component 31 | public class JwtFilter extends OncePerRequestFilter { 32 | 33 | 34 | @Autowired 35 | private ProffessoUserRepo proffessoUserRepo; 36 | @Autowired 37 | private UserRepository userRepo; 38 | @Autowired 39 | private JwtUtil jwtUtil; 40 | 41 | @Override 42 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) 43 | throws ServletException, IOException { 44 | if (request.getCookies() == null) { 45 | chain.doFilter(request, response); 46 | return; 47 | } 48 | // Get authorization header and validate 49 | Optional jwtOpt = Arrays.stream(request.getCookies()) 50 | .filter(cookie -> "jwt".equals(cookie.getName())) 51 | .findAny(); 52 | 53 | if (jwtOpt.isEmpty()) { 54 | chain.doFilter(request, response); 55 | return; 56 | } 57 | 58 | String token = jwtOpt.get().getValue(); 59 | UserDetails userDetails = null; 60 | try { 61 | Optional appUserOpt = userRepo.findByUsername(jwtUtil.getUsernameFromToken(token)); 62 | userDetails = proffessoUserRepo 63 | .findByEmail(jwtUtil.getUsernameFromToken(token)) 64 | .map(proffessoUser -> new User(proffessoUser, appUserOpt)) 65 | .orElse(null); 66 | } catch (ExpiredJwtException | SignatureException e) { 67 | chain.doFilter(request, response); 68 | return; 69 | } 70 | 71 | // Get jwt token and validate 72 | if (!jwtUtil.validateToken(token, userDetails)) { 73 | chain.doFilter(request, response); 74 | return; 75 | } 76 | 77 | UsernamePasswordAuthenticationToken 78 | authentication = new UsernamePasswordAuthenticationToken( 79 | userDetails, null, 80 | userDetails == null ? 81 | List.of() : userDetails.getAuthorities() 82 | ); 83 | 84 | authentication.setDetails( 85 | new WebAuthenticationDetailsSource().buildDetails(request) 86 | ); 87 | 88 | // this is where the authentication magic happens and the user is now valid! 89 | SecurityContextHolder.getContext().setAuthentication(authentication); 90 | 91 | chain.doFilter(request, response); 92 | 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/repository/AssignmentRepository.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.repository; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.Assignment; 4 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public interface AssignmentRepository extends JpaRepository { 12 | 13 | Set findByUser(User user); 14 | 15 | @Query("select a from Assignment a " 16 | + "where ((a.status = 'submitted' or a.status = 'resubmitted') and (a.codeReviewer is null or a.codeReviewer = :codeReviewer))" 17 | + "or a.codeReviewer = :codeReviewer") 18 | Set findByCodeReviewer(User codeReviewer); 19 | 20 | @Query("select a from Assignment a " + 21 | "join fetch a.user u " + 22 | "where u.cohortStartDate is not null and u.bootcampDurationInWeeks is not null") 23 | List findAllActiveBootcampStudents(); 24 | } 25 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/repository/AuthorityRepository.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.coderscampus.AssignmentSubmissionApp.domain.Authorities; 6 | 7 | public interface AuthorityRepository extends JpaRepository{ 8 | 9 | } 10 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/repository/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.repository; 2 | 3 | import java.util.Set; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import com.coderscampus.AssignmentSubmissionApp.domain.Comment; 9 | 10 | public interface CommentRepository extends JpaRepository{ 11 | 12 | @Query("select c from Comment c " 13 | + " where c.assignment.id = :assignmentId") 14 | Set findByAssignmentId(Long assignmentId); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.repository; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public interface UserRepository extends JpaRepository { 11 | Optional findByUsername(String username); 12 | 13 | @Query("select u from User u " + 14 | "join u.authorities auth " + 15 | "where u.cohortStartDate is null or u.bootcampDurationInWeeks is null ") 16 | List findAllInactiveBootcampStudents(); 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.service; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.Assignment; 4 | import com.coderscampus.AssignmentSubmissionApp.domain.Comment; 5 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 6 | import com.coderscampus.AssignmentSubmissionApp.dto.CommentDto; 7 | import com.coderscampus.AssignmentSubmissionApp.repository.AssignmentRepository; 8 | import com.coderscampus.AssignmentSubmissionApp.repository.CommentRepository; 9 | import org.json.JSONException; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.time.ZonedDateTime; 16 | import java.util.Set; 17 | 18 | @Service 19 | public class CommentService { 20 | private Logger log = LoggerFactory.getLogger(CommentService.class); 21 | 22 | private CommentRepository commentRepo; 23 | private AssignmentRepository assignmentRepo; 24 | private NotificationService notificationService; 25 | 26 | @Value("${domainRoot}") 27 | private String domainRoot; 28 | 29 | public CommentService(CommentRepository commentRepo, AssignmentRepository assignmentRepo, NotificationService notificationService) { 30 | this.commentRepo = commentRepo; 31 | this.assignmentRepo = assignmentRepo; 32 | this.notificationService = notificationService; 33 | } 34 | 35 | public Comment save(CommentDto commentDto, User user) { 36 | Comment comment = new Comment(); 37 | Assignment assignment = assignmentRepo.getById(commentDto.getAssignmentId()); 38 | 39 | comment.setId(commentDto.getId()); 40 | comment.setAssignment(assignment); 41 | comment.setText(commentDto.getText()); 42 | comment.setCreatedBy(user); 43 | if (comment.getId() == null) 44 | comment.setCreatedDate(ZonedDateTime.now()); 45 | else 46 | comment.setCreatedDate(commentDto.getCreatedDate()); 47 | 48 | Comment savedComment = commentRepo.save(comment); 49 | User recipient = getCommentRecipient(savedComment); 50 | if (recipient != null) { 51 | String message = "There's a new comment on Assignment #" + assignment.getNumber() + "\n\n" + savedComment.getCreatedBy().getName() + ": " + savedComment.getText(); 52 | String htmlMessage = message + "\n\nClick Here to View the Assignment"; 53 | try { 54 | notificationService.sendEmail(recipient.getUsername(), htmlMessage, message, "New Comment on Assignment"); 55 | } catch (JSONException e) { 56 | log.error("Failed to send email notification for new comment", e); 57 | } 58 | } 59 | 60 | return savedComment; 61 | } 62 | 63 | private User getCommentRecipient(Comment comment) { 64 | User createdBy = comment.getCreatedBy(); 65 | User assignmentOwner = comment.getAssignment().getUser(); 66 | 67 | if (createdBy.equals(assignmentOwner)) { 68 | return comment.getAssignment().getCodeReviewer(); 69 | } else { 70 | return assignmentOwner; 71 | } 72 | } 73 | 74 | public Set getCommentsByAssignmentId(Long assignmentId) { 75 | Set comments = commentRepo.findByAssignmentId(assignmentId); 76 | 77 | return comments; 78 | } 79 | 80 | public void delete(Long commentId) { 81 | commentRepo.deleteById(commentId); 82 | 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/service/HypeUpService.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class HypeUpService { 11 | 12 | private static List messages = new ArrayList<>(); 13 | 14 | public HypeUpService() { 15 | messages.add("Fantastic effort on getting that done!"); 16 | messages.add("Consistency is key, and you're nailing it!"); 17 | messages.add("Another milestone achieved. Keep up the momentum!"); 18 | messages.add("Every submission takes you one step closer to mastery. Well done!"); 19 | messages.add("Your dedication is evident with each task you complete."); 20 | messages.add("Setting the bar high with every task! Impressive work."); 21 | messages.add("Tackling assignments like a pro! Keep it up."); 22 | messages.add("Consistent effort leads to success. Great job!"); 23 | messages.add("Your hard work and commitment never go unnoticed."); 24 | messages.add("Pushing through and getting things done – that's the spirit!"); 25 | messages.add("Achievement unlocked! Keep going."); 26 | messages.add("Every task completed is a step towards excellence."); 27 | messages.add("You're on a roll! Keep the streak going."); 28 | messages.add("Champion effort right there!"); 29 | messages.add("Progress is made one assignment at a time. Well done!"); 30 | messages.add("You're making waves with each submission!"); 31 | messages.add("Keep setting the standard high!"); 32 | messages.add("Your dedication is inspiring to us all."); 33 | messages.add("Another task conquered. You're unstoppable!"); 34 | messages.add("Success is built on persistence. Keep it up!"); 35 | messages.add("You're turning challenges into achievements!"); 36 | messages.add("One more feather in your cap. Great job!"); 37 | messages.add("Your journey of hard work is truly commendable."); 38 | messages.add("Excellence is a habit, and you're proving it!"); 39 | messages.add("Keep up the fantastic work. The sky's the limit!"); 40 | messages.add("You're setting a great example with every submission!"); 41 | messages.add("Your efforts today shape your successes tomorrow."); 42 | messages.add("Every assignment is a testament to your dedication."); 43 | messages.add("You're making progress with every task. Keep going!"); 44 | messages.add("Your commitment to growth is evident. Kudos!"); 45 | messages.add("Another step closer to your goals. Well done!"); 46 | messages.add("You're on the path to greatness. Keep moving forward!"); 47 | messages.add("Every task completed is another victory. Cheers!"); 48 | messages.add("You're building a legacy of hard work and determination."); 49 | messages.add("Your consistency is paving the way for success."); 50 | messages.add("Keep shining with every assignment you conquer!"); 51 | messages.add("Your dedication today will lead to your achievements tomorrow."); 52 | messages.add("You're turning every challenge into an opportunity. Impressive!"); 53 | messages.add("Keep pushing boundaries. Your efforts are commendable!"); 54 | messages.add("Every submission is a reflection of your commitment. Great job!"); 55 | messages.add("You're making every task count. Keep it up!"); 56 | messages.add("Your journey is inspiring. Keep setting the pace!"); 57 | messages.add("With every assignment, you're proving your mettle. Kudos!"); 58 | messages.add("You're on a trajectory to success. Keep soaring!"); 59 | messages.add("Every task you complete adds to your story of success."); 60 | messages.add("Your hard work today will shape your victories tomorrow."); 61 | messages.add("Keep up the momentum. You're doing great!"); 62 | messages.add("Your dedication is shaping your path to success."); 63 | messages.add("With every submission, you're raising the bar. Impressive!"); 64 | messages.add("You're turning dreams into realities with your hard work."); 65 | messages.add("Every assignment is a step closer to your aspirations. Well done!"); 66 | messages.add("Your efforts are building a foundation for success."); 67 | messages.add("Keep making strides with every task. You're on the right track!"); 68 | messages.add("Your commitment to excellence is truly inspiring."); 69 | messages.add("Every task you tackle is a testament to your determination."); 70 | messages.add("You're making a mark with every submission. Keep it up!"); 71 | messages.add("Your journey of perseverance is commendable. Keep going!"); 72 | messages.add("With every task, you're closer to your goals. Kudos!"); 73 | messages.add("You're setting new standards with every assignment. Impressive!"); 74 | messages.add("Your dedication and hard work are paving the way for success."); 75 | messages.add("Keep pushing forward. Every task is a step towards greatness!"); 76 | messages.add("Your commitment to growth is evident in every submission."); 77 | messages.add("You're making progress with every assignment. Keep shining!"); 78 | messages.add("Every task you complete is a victory in itself. Well done!"); 79 | messages.add("Your journey is filled with achievements. Keep adding to it!"); 80 | messages.add("You're setting a benchmark with every task. Keep it up!"); 81 | messages.add("Your hard work and dedication are truly commendable."); 82 | messages.add("Keep making waves with every assignment. You're on a roll!"); 83 | messages.add("Every submission is a testament to your commitment. Kudos!"); 84 | messages.add("You're on the path to success. Keep making strides!"); 85 | messages.add("Your dedication to every task is truly inspiring."); 86 | messages.add("Keep up the fantastic work. Your journey is commendable!"); 87 | messages.add("Every assignment you complete adds to your legacy of hard work."); 88 | messages.add("You're making a difference with every task. Keep going!"); 89 | messages.add("Your commitment to excellence is evident in every submission."); 90 | messages.add("Keep setting the pace. Your efforts are truly commendable!"); 91 | messages.add("Every task you tackle is a step closer to your dreams."); 92 | messages.add("You're making a mark with every assignment. Keep shining!"); 93 | messages.add("Your journey of hard work and dedication is truly inspiring."); 94 | messages.add("Keep up the momentum. Every submission counts!"); 95 | messages.add("Your dedication to every task is setting a benchmark. Well done!"); 96 | messages.add("You're on a trajectory to success. Keep making strides!"); 97 | messages.add("Every assignment you complete is a testament to your hard work."); 98 | messages.add("Keep pushing boundaries. Your journey is commendable!"); 99 | messages.add("You're turning challenges into achievements with every task."); 100 | messages.add("Your commitment to growth is evident in every assignment."); 101 | messages.add("Keep up the fantastic work. Every task is a step towards success!"); 102 | messages.add("You're setting new standards with every submission. Impressive!"); 103 | messages.add("Your hard work and dedication are shaping your path to greatness."); 104 | messages.add("Keep making waves with every task. You're on the right track!"); 105 | messages.add("Every submission is a reflection of your commitment to excellence."); 106 | messages.add("You're making a mark with every assignment. Keep it up!"); 107 | messages.add("Your journey of perseverance and hard work is truly commendable."); 108 | messages.add("Keep pushing forward. Every task is a victory in itself!"); 109 | messages.add("Your dedication to every assignment is truly inspiring."); 110 | messages.add("Keep setting the pace. Your efforts are making a difference!"); 111 | messages.add("Every task you complete is a testament to your determination."); 112 | messages.add("You're on the path to greatness. Keep making strides!"); 113 | messages.add("Your hard work and commitment are evident in every submission."); 114 | messages.add("Keep up the momentum. Your journey is truly inspiring."); 115 | } 116 | public static String getHypeUpMessage () { 117 | Random rnd = new Random(); 118 | 119 | int index = rnd.nextInt(100); 120 | return messages.get(index); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/service/NotificationService.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.service; 2 | 3 | import java.nio.charset.Charset; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | import org.apache.tomcat.util.codec.binary.Base64; 10 | import org.json.JSONException; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.core.env.Environment; 16 | import org.springframework.http.HttpEntity; 17 | import org.springframework.http.HttpHeaders; 18 | import org.springframework.http.HttpMethod; 19 | import org.springframework.http.MediaType; 20 | import org.springframework.http.ResponseEntity; 21 | import org.springframework.stereotype.Service; 22 | import org.springframework.util.LinkedMultiValueMap; 23 | import org.springframework.util.MultiValueMap; 24 | import org.springframework.util.StringUtils; 25 | import org.springframework.web.client.RestTemplate; 26 | 27 | import com.coderscampus.AssignmentSubmissionApp.domain.Assignment; 28 | import com.coderscampus.AssignmentSubmissionApp.dto.GhlSlackMessageCustomData; 29 | import com.coderscampus.AssignmentSubmissionApp.dto.GhlWebhookRequest; 30 | 31 | @Service 32 | public class NotificationService { 33 | private static final String BROADCAST_MESSAGE = "This is a broadcast message to all Coders Campus Code Reviewers."; 34 | @Value("${mail.username}") 35 | private String username; 36 | @Value("${mail.password}") 37 | private String password; 38 | @Value("#{'${emails.codeReviewers}'.split(',')}") 39 | private List codeReviewers; 40 | 41 | @Autowired 42 | private Environment environment; 43 | @Value("${domainRoot}") 44 | private String domainRoot; 45 | 46 | private final Logger log = LoggerFactory.getLogger(NotificationService.class); 47 | 48 | public ResponseEntity sendEmail(String toEmail, String message, String textMessage, String subject) throws JSONException { 49 | if (toEmail == null || message == null) 50 | return null; 51 | if (message != null) { 52 | message = message.replace("\n", "
"); 53 | } 54 | 55 | RestTemplate rt = new RestTemplate(); 56 | MultiValueMap data = new LinkedMultiValueMap<>(); 57 | data.add("from", "Trevor Page "); 58 | data.add("subject", subject); 59 | data.add("to", toEmail); 60 | if (!message.contains(BROADCAST_MESSAGE)) { 61 | data.add("cc", "trevor@coderscampus.com"); 62 | } 63 | data.add("html", message); 64 | data.add("text", textMessage); 65 | 66 | HttpEntity> entity = new HttpEntity<>(data, createHeaders(username, password)); 67 | 68 | var activeProfiles = this.environment != null ? this.environment.getActiveProfiles() : new String[]{"local"}; 69 | if (activeProfiles != null && Arrays.stream(activeProfiles).anyMatch(profile -> "local".equalsIgnoreCase(profile))) { 70 | log.warn("Local profile detected, suppressing the sending of email."); 71 | return ResponseEntity.ok().build(); 72 | } else { 73 | ResponseEntity response = rt.exchange("https://api.mailgun.net/v3/coderscampus.com/messages", HttpMethod.POST, 74 | entity, String.class); 75 | log.info("Email sent to {}", toEmail); 76 | return response; 77 | } 78 | 79 | 80 | } 81 | 82 | HttpHeaders createHeaders(String username, String password) { 83 | return new HttpHeaders() {{ 84 | String auth = username + ":" + password; 85 | byte[] encodedAuth = Base64.encodeBase64( 86 | auth.getBytes(Charset.forName("US-ASCII"))); 87 | String authHeader = "Basic " + new String(encodedAuth); 88 | set("Authorization", authHeader); 89 | }}; 90 | } 91 | 92 | public void sendAssignmentStatusUpdateStudent(String oldStatus, Assignment assignment) { 93 | 94 | String emailBody = "Hello " + assignment.getUser().getName() + ", " + "\n\n Your assignment " 95 | + assignment.getNumber() + " has gone from [" + oldStatus + "] to [" + assignment.getStatus() + "]"; 96 | String htmlEmailBody = emailBody; 97 | String link = assignment.getCodeReviewVideoUrl(); 98 | if (StringUtils.hasText(link)) { 99 | htmlEmailBody = htmlEmailBody + "\n\nHere's your code review video link: " + link + ""; 100 | } 101 | 102 | try { 103 | 104 | sendEmail(assignment.getUser().getUsername(), htmlEmailBody, emailBody, "Assignment Status Update"); 105 | } catch (JSONException e) { 106 | log.error("Error sending email", e); 107 | } 108 | } 109 | 110 | public void sendAssignmentStatusUpdateCodeReviewer(String oldStatus, Assignment assignment) { 111 | String message = "Hello, " + assignment.getUser().getName() + "'s assignment " 112 | + assignment.getNumber() + " has gone from [" + oldStatus + "] to [" + assignment.getStatus() + "]." + 113 | "\n\n<" + domainRoot + "/dashboard|Click Here to Visit Assignment Submission Dashboard>"; 114 | 115 | sendSlackMessage(message, "CODE_REVIEWERS"); 116 | } 117 | 118 | public void sendCongratsOnAssignmentSubmissionSlackMessage(Assignment assignment, String channelId) { 119 | sendSlackMessage("Congrats to " + assignment.getUser().getName() + " for submitting assignment #" + assignment.getNumber() + ". " + HypeUpService.getHypeUpMessage(), channelId); 120 | } 121 | 122 | public void sendSlackMessage(String message, String channelId) { 123 | RestTemplate rt = new RestTemplate(); 124 | 125 | GhlWebhookRequest request = new GhlWebhookRequest(); 126 | 127 | GhlSlackMessageCustomData customData = new GhlSlackMessageCustomData(); 128 | customData.setChannel(channelId); 129 | customData.setMessage(message); 130 | request.setCustomData(customData); 131 | 132 | HttpHeaders headers = new HttpHeaders(); 133 | headers.setContentType(MediaType.APPLICATION_JSON); 134 | 135 | HttpEntity entity = new HttpEntity<>(request, headers); 136 | 137 | rt.exchange("https://courses.coderscampus.com/ghl/slack-message", HttpMethod.POST, entity, String.class); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.service; 2 | 3 | import com.coderscampus.proffesso.domain.Offer; 4 | import com.coderscampus.proffesso.domain.Order; 5 | import com.coderscampus.proffesso.domain.ProffessoUser; 6 | import com.coderscampus.proffesso.repository.OrderRepository; 7 | import com.coderscampus.proffesso.repository.ProffessoUserRepo; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.Set; 14 | import java.util.stream.Collectors; 15 | 16 | @Service 17 | public class OrderService { 18 | public static final List BOOTCAMP_OFFER_IDS = List.of(225L, 221L, 218L, 212L); 19 | public static final Long JAVA_FOUNDATIONS_OFFER_ID = 227L; 20 | 21 | private OrderRepository orderRepo; 22 | private ProffessoUserRepo proffessoUserRepo; 23 | 24 | public OrderService(OrderRepository orderRepo, ProffessoUserRepo proffessoUserRepo) { 25 | this.orderRepo = orderRepo; 26 | this.proffessoUserRepo = proffessoUserRepo; 27 | } 28 | 29 | public Set findStudentOrdersByUserId(Long userId) { 30 | Optional proffessoUserOpt = proffessoUserRepo.findById(userId); 31 | 32 | if (proffessoUserOpt.isPresent()) { 33 | Set orders = orderRepo.findByUser(proffessoUserOpt.get()); 34 | 35 | Set offers = orders.stream() 36 | .map(order -> order.getOffers()) 37 | .flatMap(Collection::stream) 38 | .collect(Collectors.toSet()); 39 | 40 | return offers; 41 | } 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/service/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.service; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | import org.springframework.stereotype.Service; 10 | 11 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 12 | import com.coderscampus.AssignmentSubmissionApp.repository.UserRepository; 13 | import com.coderscampus.proffesso.domain.ProffessoUser; 14 | import com.coderscampus.proffesso.repository.ProffessoUserRepo; 15 | 16 | @Service 17 | public class UserDetailsServiceImpl implements UserDetailsService { 18 | 19 | @Autowired 20 | private UserRepository userRepo; 21 | @Autowired 22 | private ProffessoUserRepo proffessoUserRepo; 23 | 24 | @Override 25 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 26 | 27 | Optional proffessoUserOpt = proffessoUserRepo.findByEmail(username); 28 | if (proffessoUserOpt.isEmpty()) { 29 | throw new UsernameNotFoundException("Invalid username or password"); 30 | } 31 | 32 | Optional userOpt = userRepo.findByUsername(username); 33 | if (userOpt.isEmpty()) { 34 | // user exists in Proffesso but not here, this means we'll need to create an account 35 | // for this user in this app 36 | User user = new User(proffessoUserOpt.get(), Optional.empty()); 37 | user = userRepo.save(user); 38 | return user; 39 | } else { 40 | // TODO: Check that proffesso user and this app's user are in sync. 41 | User user = new User(proffessoUserOpt.get(), userOpt); 42 | user = userRepo.save(user); 43 | return user; 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.service; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.Authorities; 4 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 5 | import com.coderscampus.AssignmentSubmissionApp.dto.UserDto; 6 | import com.coderscampus.AssignmentSubmissionApp.dto.UserKeyDto; 7 | import com.coderscampus.AssignmentSubmissionApp.repository.AuthorityRepository; 8 | import com.coderscampus.AssignmentSubmissionApp.repository.UserRepository; 9 | import com.coderscampus.AssignmentSubmissionApp.util.CustomPasswordEncoder; 10 | import com.coderscampus.proffesso.domain.ProffessoUser; 11 | import com.coderscampus.proffesso.repository.ProffessoUserRepo; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.security.access.annotation.Secured; 14 | import org.springframework.stereotype.Service; 15 | 16 | import javax.persistence.Tuple; 17 | import javax.transaction.Transactional; 18 | import java.util.List; 19 | import java.util.Optional; 20 | import java.util.stream.Collectors; 21 | 22 | @Service 23 | public class UserService { 24 | 25 | @Autowired 26 | private UserRepository userRepo; 27 | @Autowired 28 | private ProffessoUserRepo proffessoUserRepo; 29 | @Autowired 30 | private AuthorityRepository authorityRepo; 31 | @Autowired 32 | private CustomPasswordEncoder customPasswordEncoder; 33 | 34 | public Optional findUserByUsername(String username) { 35 | return userRepo.findByUsername(username); 36 | } 37 | 38 | public void createUser(UserDto userDto) { 39 | User newUser = new User(); 40 | newUser.setUsername(userDto.getUsername()); 41 | newUser.setName(userDto.getName()); 42 | String encodedPassword = customPasswordEncoder.getPasswordEncoder().encode(userDto.getPassword()); 43 | newUser.setPassword(encodedPassword); 44 | userRepo.save(newUser); 45 | Authorities authority = new Authorities(); 46 | authority.setAuthority("ROLE_STUDENT"); 47 | authority.setUser(newUser); 48 | authorityRepo.save(authority); 49 | 50 | } 51 | 52 | public User duplicateProffessoUser(ProffessoUser proffessoUser) { 53 | User user = new User(proffessoUser, Optional.empty()); 54 | user = userRepo.save(user); 55 | return user; 56 | } 57 | 58 | @Secured({"ROLE_INSTRUCTOR"}) 59 | @Transactional 60 | public List findBootcampStudents() { 61 | List proffessoUsersRawData = proffessoUserRepo.findBootcampStudents(); 62 | var droppedStudents = proffessoUserRepo.findDroppedBootcampStudents(); 63 | 64 | List users = userRepo.findAll(); 65 | List proffessoUsers = proffessoUsersRawData.stream() 66 | .map(data -> { 67 | Optional match = users.stream().filter(user -> user.getUsername().equals(data.get(0))).findFirst(); 68 | UserKeyDto userKeyDto = new UserKeyDto((String) data.get(0), (String) data.get(1), null, null); 69 | match.ifPresent(user -> { 70 | userKeyDto.setBootcampDurationInWeeks(user.getBootcampDurationInWeeks()); 71 | userKeyDto.setStartDate(user.getCohortStartDate()); 72 | }); 73 | return userKeyDto; 74 | }) 75 | .collect(Collectors.toList()); 76 | 77 | return proffessoUsers.stream() 78 | .filter(proffessoUser -> !droppedStudents.stream() 79 | .filter(droppedStudent -> droppedStudent.getEmail().equalsIgnoreCase(proffessoUser.getEmail())) 80 | .findAny() 81 | .isPresent()) 82 | .collect(Collectors.toList()); 83 | } 84 | 85 | @Secured({"ROLE_INSTRUCTOR"}) 86 | public List findNonConfiguredStudents() { 87 | return userRepo.findAllInactiveBootcampStudents(); 88 | } 89 | 90 | public void updateUser(UserKeyDto user) { 91 | Optional userOpt = userRepo.findByUsername(user.getEmail()); 92 | userOpt.ifPresentOrElse(u -> { 93 | u.setCohortStartDate(user.getStartDate() != null ? user.getStartDate() : user.getCohortStartDate()); 94 | u.setBootcampDurationInWeeks(user.getBootcampDurationInWeeks()); 95 | u.setName(user.getName()); 96 | userRepo.save(u); 97 | }, () -> { 98 | Optional proffessoUserOpt = proffessoUserRepo.findByEmail(user.getEmail()); 99 | proffessoUserOpt.ifPresent(proffessoUser -> duplicateProffessoUser(proffessoUser)); 100 | }); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/util/AuthorityUtil.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.util; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 4 | 5 | public class AuthorityUtil { 6 | 7 | public static Boolean hasRole(String role, User user) { 8 | return user.getAuthorities() 9 | .stream() 10 | .filter(auth -> auth.getAuthority().equals(role)) 11 | .count() > 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/util/CustomPasswordEncoder.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.util; 2 | 3 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 4 | import org.springframework.security.crypto.password.PasswordEncoder; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class CustomPasswordEncoder { 9 | private PasswordEncoder passwordEncoder; 10 | 11 | public CustomPasswordEncoder () { 12 | this.passwordEncoder = new BCryptPasswordEncoder(); 13 | } 14 | 15 | public PasswordEncoder getPasswordEncoder() { 16 | return passwordEncoder; 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/util/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.util; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.StringUtils; 14 | 15 | import io.jsonwebtoken.Claims; 16 | import io.jsonwebtoken.Jwts; 17 | import io.jsonwebtoken.SignatureAlgorithm; 18 | 19 | @Component 20 | public class JwtUtil implements Serializable { 21 | 22 | private static final long serialVersionUID = -2550185165626007488L; 23 | 24 | public static final long JWT_TOKEN_VALIDITY = 12 * 30 * 24 * 60 * 60; 25 | 26 | @Value("${jwt.secret}") 27 | private String secret; 28 | 29 | public String getUsernameFromToken(String token) { 30 | return getClaimFromToken(token, Claims::getSubject); 31 | } 32 | 33 | public Date getIssuedAtDateFromToken(String token) { 34 | return getClaimFromToken(token, Claims::getIssuedAt); 35 | } 36 | 37 | public Date getExpirationDateFromToken(String token) { 38 | return getClaimFromToken(token, Claims::getExpiration); 39 | } 40 | 41 | public T getClaimFromToken(String token, Function claimsResolver) { 42 | final Claims claims = getAllClaimsFromToken(token); 43 | return claimsResolver.apply(claims); 44 | } 45 | 46 | private Claims getAllClaimsFromToken(String token) { 47 | return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); 48 | } 49 | 50 | private Boolean isTokenExpired(String token) { 51 | final Date expiration = getExpirationDateFromToken(token); 52 | return expiration.before(new Date()); 53 | } 54 | 55 | private Boolean ignoreTokenExpiration(String token) { 56 | // here you specify tokens, for that the expiration is ignored 57 | return false; 58 | } 59 | 60 | public String generateToken(UserDetails userDetails) { 61 | Map claims = new HashMap<>(); 62 | claims.put("authorities", userDetails.getAuthorities() 63 | .stream() 64 | .map(auth -> auth.getAuthority()) 65 | .collect(Collectors.toList())); 66 | return doGenerateToken(claims, userDetails.getUsername()); 67 | } 68 | 69 | private String doGenerateToken(Map claims, String subject) { 70 | 71 | return Jwts.builder() 72 | .setClaims(claims) 73 | .setSubject(subject) 74 | .setIssuedAt(new Date(System.currentTimeMillis())) 75 | .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) 76 | .signWith(SignatureAlgorithm.HS512, secret).compact(); 77 | } 78 | 79 | public Boolean canTokenBeRefreshed(String token) { 80 | return (!isTokenExpired(token) || ignoreTokenExpiration(token)); 81 | } 82 | 83 | public Boolean validateToken(String token, UserDetails userDetails) { 84 | if (!StringUtils.hasText(token)) 85 | return false; 86 | 87 | final String username = getUsernameFromToken(token); 88 | return (userDetails != null && username.equals(userDetails.getUsername()) && !isTokenExpired(token)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/web/AssignmentController.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.web; 2 | 3 | import static com.coderscampus.AssignmentSubmissionApp.service.OrderService.BOOTCAMP_OFFER_IDS; 4 | import static com.coderscampus.AssignmentSubmissionApp.service.OrderService.JAVA_FOUNDATIONS_OFFER_ID; 5 | 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.Set; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 13 | import org.springframework.web.bind.annotation.CrossOrigin; 14 | import org.springframework.web.bind.annotation.GetMapping; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.PostMapping; 17 | import org.springframework.web.bind.annotation.PutMapping; 18 | import org.springframework.web.bind.annotation.RequestBody; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | import com.coderscampus.AssignmentSubmissionApp.domain.Assignment; 23 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 24 | import com.coderscampus.AssignmentSubmissionApp.dto.AssignmentResponseDto; 25 | import com.coderscampus.AssignmentSubmissionApp.dto.BootcampAssignmentResponseDto; 26 | import com.coderscampus.AssignmentSubmissionApp.dto.JavaFoundationsAssignmentResponseDto; 27 | import com.coderscampus.AssignmentSubmissionApp.dto.UserKeyDto; 28 | import com.coderscampus.AssignmentSubmissionApp.enums.AuthorityEnum; 29 | import com.coderscampus.AssignmentSubmissionApp.service.AssignmentService; 30 | import com.coderscampus.AssignmentSubmissionApp.service.OrderService; 31 | import com.coderscampus.AssignmentSubmissionApp.service.UserService; 32 | import com.coderscampus.AssignmentSubmissionApp.util.AuthorityUtil; 33 | import com.coderscampus.proffesso.domain.Offer; 34 | 35 | @RestController 36 | @RequestMapping("/api/assignments") 37 | @CrossOrigin(origins = {"http://localhost:3000", "http://localhost:8080", "https://assignments.coderscampus.com"}, allowCredentials = "true") 38 | public class AssignmentController { 39 | 40 | @Autowired 41 | private AssignmentService assignmentService; 42 | @Autowired 43 | private UserService userService; 44 | 45 | @Autowired 46 | private OrderService orderService; 47 | 48 | @PostMapping("") 49 | public ResponseEntity createAssignment(@AuthenticationPrincipal User user) { 50 | Assignment newAssignment = assignmentService.save(user); 51 | 52 | return ResponseEntity.ok(newAssignment); 53 | } 54 | 55 | @GetMapping("") 56 | public ResponseEntity getAssignments(@AuthenticationPrincipal User user) { 57 | Set assignmentsByUser = assignmentService.findByUser(user); 58 | return ResponseEntity.ok(assignmentsByUser); 59 | } 60 | 61 | @GetMapping("{assignmentId}") 62 | public ResponseEntity getAssignment(@PathVariable Long assignmentId, @AuthenticationPrincipal User user) { 63 | Optional assignmentOpt = assignmentService.findById(assignmentId); 64 | 65 | Set offers = orderService.findStudentOrdersByUserId(assignmentOpt.get().getUser().getId()); 66 | boolean isBootcampStudent = offers.stream() 67 | .anyMatch(offer -> BOOTCAMP_OFFER_IDS.contains(offer.getId())); 68 | boolean isJavaFoundationsStudent = offers.stream() 69 | .anyMatch(offer -> offer.getId().equals(JAVA_FOUNDATIONS_OFFER_ID)); 70 | 71 | if (isBootcampStudent) { 72 | 73 | AssignmentResponseDto response = new BootcampAssignmentResponseDto(assignmentOpt.orElse(new Assignment())); 74 | return ResponseEntity.ok(response); 75 | } else if (isJavaFoundationsStudent) { 76 | AssignmentResponseDto response = new JavaFoundationsAssignmentResponseDto(assignmentOpt.orElse(new Assignment())); 77 | return ResponseEntity.ok(response); 78 | } 79 | return ResponseEntity.ok(new BootcampAssignmentResponseDto()); 80 | } 81 | 82 | @PutMapping("{assignmentId}") 83 | public ResponseEntity updateAssignment(@PathVariable Long assignmentId, 84 | @RequestBody Assignment assignment, 85 | @AuthenticationPrincipal User user) { 86 | // add the code reviewer to this assignment if it was claimed 87 | if (assignment.getCodeReviewer() != null) { 88 | User codeReviewer = assignment.getCodeReviewer(); 89 | codeReviewer = userService.findUserByUsername(codeReviewer.getUsername()).orElseThrow(); 90 | 91 | if (AuthorityUtil.hasRole(AuthorityEnum.ROLE_CODE_REVIEWER.name(), codeReviewer)) { 92 | assignment.setCodeReviewer(codeReviewer); 93 | } 94 | } 95 | Assignment updatedAssignment = assignmentService.save(assignment); 96 | return ResponseEntity.ok(updatedAssignment); 97 | } 98 | 99 | @GetMapping("/all") 100 | public ResponseEntity allAssignments() { 101 | Map> allAssignments = assignmentService.findAll(); 102 | return ResponseEntity.ok(allAssignments); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/web/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.web; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseCookie; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.authentication.BadCredentialsException; 13 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 14 | import org.springframework.security.core.Authentication; 15 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 16 | import org.springframework.web.bind.annotation.CookieValue; 17 | import org.springframework.web.bind.annotation.CrossOrigin; 18 | import org.springframework.web.bind.annotation.GetMapping; 19 | import org.springframework.web.bind.annotation.PostMapping; 20 | import org.springframework.web.bind.annotation.RequestBody; 21 | import org.springframework.web.bind.annotation.RequestMapping; 22 | import org.springframework.web.bind.annotation.RestController; 23 | 24 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 25 | import com.coderscampus.AssignmentSubmissionApp.dto.AuthCredentialsRequest; 26 | import com.coderscampus.AssignmentSubmissionApp.util.JwtUtil; 27 | 28 | import ch.qos.logback.core.util.Duration; 29 | import io.jsonwebtoken.ExpiredJwtException; 30 | 31 | @RestController 32 | @RequestMapping("/api/auth") 33 | @CrossOrigin(origins = {"http://localhost:3000", "http://localhost:8080", "https://assignments.coderscampus.com"}, allowCredentials = "true") 34 | public class AuthController { 35 | 36 | private Logger log = LoggerFactory.getLogger(AuthController.class); 37 | @Autowired 38 | private AuthenticationManager authenticationManager; 39 | @Autowired 40 | private JwtUtil jwtUtil; 41 | @Value("${cookies.domain}") 42 | private String domain; 43 | 44 | @PostMapping("login") 45 | public ResponseEntity login(@RequestBody AuthCredentialsRequest request) { 46 | try { 47 | Authentication authenticate = authenticationManager 48 | .authenticate( 49 | new UsernamePasswordAuthenticationToken( 50 | request.getUsername(), request.getPassword())); 51 | 52 | User user = (User) authenticate.getPrincipal(); 53 | user.setPassword(null); 54 | 55 | String token = jwtUtil.generateToken(user); 56 | ResponseCookie cookie = ResponseCookie.from("jwt", token) 57 | .domain(domain) 58 | .path("/") 59 | .maxAge(Duration.buildByDays(365).getMilliseconds()) 60 | .build(); 61 | return ResponseEntity.ok() 62 | .header(HttpHeaders.SET_COOKIE, cookie.toString()) 63 | .body(token); 64 | } catch (BadCredentialsException ex) { 65 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 66 | } 67 | } 68 | 69 | @GetMapping("/validate") 70 | public ResponseEntity validateToken(@CookieValue(name = "jwt") String token, 71 | @AuthenticationPrincipal User user) { 72 | try { 73 | Boolean isValidToken = jwtUtil.validateToken(token, user); 74 | return ResponseEntity.ok(isValidToken); 75 | } catch (ExpiredJwtException e) { 76 | return ResponseEntity.ok(false); 77 | } 78 | } 79 | 80 | @GetMapping("/logout") 81 | public ResponseEntity logout () { 82 | ResponseCookie cookie = ResponseCookie.from("jwt", "") 83 | .domain(domain) 84 | .path("/") 85 | .maxAge(0) 86 | .build(); 87 | return ResponseEntity.ok() 88 | .header(HttpHeaders.SET_COOKIE, cookie.toString()).body("ok"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/web/CommentController.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.web; 2 | 3 | import java.util.Set; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 9 | import org.springframework.web.bind.annotation.CrossOrigin; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.PutMapping; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestParam; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import com.coderscampus.AssignmentSubmissionApp.domain.Comment; 21 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 22 | import com.coderscampus.AssignmentSubmissionApp.dto.CommentDto; 23 | import com.coderscampus.AssignmentSubmissionApp.service.CommentService; 24 | 25 | @RestController 26 | @RequestMapping("/api/comments") 27 | @CrossOrigin(origins = {"http://localhost:3000", "http://localhost:8080", "https://assignments.coderscampus.com"}, allowCredentials = "true") 28 | public class CommentController { 29 | 30 | @Autowired 31 | private CommentService commentService; 32 | 33 | @PostMapping("") 34 | public ResponseEntity createComment (@RequestBody CommentDto commentDto, @AuthenticationPrincipal User user) { 35 | Comment comment = commentService.save(commentDto, user); 36 | 37 | return ResponseEntity.ok(comment); 38 | } 39 | 40 | @PutMapping("{commentId}") 41 | public ResponseEntity updateComment (@RequestBody CommentDto commentDto, @AuthenticationPrincipal User user) { 42 | Comment comment = commentService.save(commentDto, user); 43 | 44 | return ResponseEntity.ok(comment); 45 | } 46 | 47 | @GetMapping("") 48 | public ResponseEntity> getCommentsByAssignment(@RequestParam Long assignmentId) { 49 | Set comments = commentService.getCommentsByAssignmentId(assignmentId); 50 | 51 | return ResponseEntity.ok(comments); 52 | } 53 | 54 | @DeleteMapping("{commentId}") 55 | public ResponseEntity deleteComment (@PathVariable Long commentId) { 56 | try { 57 | commentService.delete(commentId); 58 | return ResponseEntity.ok("Comment deleted"); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); 62 | } 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/web/PingController.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.web; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class PingController { 9 | 10 | @GetMapping("/api") 11 | public ResponseEntity ping () { 12 | return ResponseEntity.ok("OK"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApp/web/UserController.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.web; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 4 | import com.coderscampus.AssignmentSubmissionApp.dto.UserDto; 5 | import com.coderscampus.AssignmentSubmissionApp.dto.UserKeyDto; 6 | import com.coderscampus.AssignmentSubmissionApp.service.UserService; 7 | import com.coderscampus.AssignmentSubmissionApp.util.JwtUtil; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.security.access.annotation.Secured; 13 | import org.springframework.security.authentication.AuthenticationManager; 14 | import org.springframework.security.authentication.BadCredentialsException; 15 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 16 | import org.springframework.security.core.Authentication; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import java.util.List; 20 | import java.util.Optional; 21 | import java.util.stream.Collectors; 22 | 23 | @RestController 24 | @RequestMapping("/api/users") 25 | @CrossOrigin(origins = {"http://localhost:3000", "http://localhost:8080", "https://assignments.coderscampus.com"}, allowCredentials = "true") 26 | public class UserController { 27 | @Autowired 28 | private UserService userService; 29 | @Autowired 30 | private AuthenticationManager authenticationManager; 31 | @Autowired 32 | private JwtUtil jwtUtil; 33 | 34 | @GetMapping("{email}") 35 | public ResponseEntity getUser(@PathVariable String email) { 36 | Optional userByUsername = userService.findUserByUsername(email); 37 | 38 | return ResponseEntity.ok(userByUsername); 39 | } 40 | 41 | @PutMapping("{email}") 42 | @Secured({"ROLE_INSTRUCTOR"}) 43 | public ResponseEntity saveUser(@RequestBody UserKeyDto user) { 44 | System.out.println("Got user: " + user); 45 | userService.updateUser(user); 46 | return ResponseEntity.ok().build(); 47 | } 48 | 49 | @PostMapping("/register") 50 | private ResponseEntity createUser(@RequestBody UserDto userDto) { 51 | userService.createUser(userDto); 52 | 53 | try { 54 | Authentication authenticate = authenticationManager 55 | .authenticate( 56 | new UsernamePasswordAuthenticationToken( 57 | userDto.getUsername(), userDto.getPassword() 58 | ) 59 | ); 60 | 61 | User user = (User) authenticate.getPrincipal(); 62 | user.setPassword(null); 63 | return ResponseEntity.ok() 64 | .header( 65 | HttpHeaders.AUTHORIZATION, 66 | jwtUtil.generateToken(user) 67 | ) 68 | .body(user); 69 | } catch (BadCredentialsException ex) { 70 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 71 | } 72 | } 73 | 74 | @GetMapping("/non-configured") 75 | public ResponseEntity nonConfiguredStudents() { 76 | List nonConfiguredStudents = userService.findNonConfiguredStudents(); 77 | List result = nonConfiguredStudents.stream() 78 | .map(u -> new UserKeyDto(u.getUsername(), u.getName(), u.getCohortStartDate(), u.getBootcampDurationInWeeks())) 79 | .collect(Collectors.toList()); 80 | return ResponseEntity.ok(result); 81 | } 82 | 83 | @GetMapping("/bootcamp-students") 84 | public ResponseEntity getBootcampStudents() { 85 | List bootcampStudents = userService.findBootcampStudents(); 86 | 87 | return ResponseEntity.ok(bootcampStudents); 88 | } 89 | } 90 | 91 | 92 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/AssignmentSubmissionApplication.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class AssignmentSubmissionApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(AssignmentSubmissionApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/PrimaryDbConfig.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import javax.persistence.EntityManagerFactory; 7 | import javax.sql.DataSource; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.jdbc.DataSourceBuilder; 14 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.annotation.Primary; 18 | import org.springframework.context.annotation.Profile; 19 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 20 | import org.springframework.orm.jpa.JpaTransactionManager; 21 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 22 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 23 | import org.springframework.transaction.PlatformTransactionManager; 24 | import org.springframework.transaction.annotation.EnableTransactionManagement; 25 | 26 | @Configuration 27 | @EnableTransactionManagement 28 | @EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory", basePackages = { 29 | "com.coderscampus.AssignmentSubmissionApp" }) 30 | @Profile("local") 31 | public class PrimaryDbConfig { 32 | 33 | Logger log = LoggerFactory.getLogger(PrimaryDbConfig.class); 34 | 35 | @Value("${DB_USERNAME}") 36 | private String dbUsername; 37 | @Value("${DB_PASSWORD}") 38 | private String dbPassword; 39 | @Value("${DB_URL}") 40 | private String dbUrl; 41 | 42 | @Primary 43 | @Bean(name = "dataSource") 44 | public DataSource getDataSource() { 45 | 46 | return DataSourceBuilder.create() 47 | .driverClassName("com.mysql.cj.jdbc.Driver") 48 | .url(dbUrl) 49 | .username(dbUsername) 50 | .password(dbPassword) 51 | .build(); 52 | } 53 | 54 | @Primary 55 | @Bean(name = "entityManagerFactory") 56 | public LocalContainerEntityManagerFactoryBean entityManagerFactory( 57 | EntityManagerFactoryBuilder builder, 58 | @Qualifier("dataSource") DataSource dataSource) { 59 | LocalContainerEntityManagerFactoryBean em = builder 60 | .dataSource(dataSource) 61 | .packages("com.coderscampus.AssignmentSubmissionApp") 62 | .persistenceUnit("AssignmentSubmissionApp") 63 | .build(); 64 | HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); 65 | 66 | Map properties = new HashMap<>(); 67 | properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); 68 | properties.put("hibernate.hbm2ddl.auto", "update"); 69 | properties.put("hibernate.implicit_naming_strategy", 70 | "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy"); 71 | properties.put("hibernate.physical_naming_strategy", 72 | "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"); 73 | em.setJpaVendorAdapter(adapter); 74 | em.getJpaPropertyMap().putAll(properties); 75 | 76 | return em; 77 | } 78 | 79 | @Primary 80 | @Bean(name = "transactionManager") 81 | public PlatformTransactionManager transactionManager( 82 | @Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) { 83 | return new JpaTransactionManager(entityManagerFactory); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/PrimaryDbConfigDev.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import javax.persistence.EntityManagerFactory; 7 | import javax.sql.DataSource; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.jdbc.DataSourceBuilder; 14 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.annotation.Primary; 18 | import org.springframework.context.annotation.Profile; 19 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 20 | import org.springframework.orm.jpa.JpaTransactionManager; 21 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 22 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 23 | import org.springframework.transaction.PlatformTransactionManager; 24 | import org.springframework.transaction.annotation.EnableTransactionManagement; 25 | 26 | @Configuration 27 | @EnableTransactionManagement 28 | @EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory", basePackages = { 29 | "com.coderscampus.AssignmentSubmissionApp" }) 30 | @Profile("dev") 31 | public class PrimaryDbConfigDev { 32 | 33 | Logger log = LoggerFactory.getLogger(PrimaryDbConfigDev.class); 34 | 35 | @Value("${DB_USERNAME}") 36 | private String dbUsername; 37 | @Value("${DB_PASSWORD}") 38 | private String dbPassword; 39 | @Value("${DB_URL}") 40 | private String dbUrl; 41 | 42 | @Primary 43 | @Bean(name = "dataSource") 44 | public DataSource getDataSource() { 45 | 46 | return DataSourceBuilder.create() 47 | .driverClassName("com.mysql.cj.jdbc.Driver") 48 | .url(dbUrl) 49 | .username(dbUsername) 50 | .password(dbPassword) 51 | .build(); 52 | } 53 | 54 | @Primary 55 | @Bean(name = "entityManagerFactory") 56 | public LocalContainerEntityManagerFactoryBean entityManagerFactory( 57 | EntityManagerFactoryBuilder builder, 58 | @Qualifier("dataSource") DataSource dataSource) { 59 | LocalContainerEntityManagerFactoryBean em = builder 60 | .dataSource(dataSource) 61 | .packages("com.coderscampus.AssignmentSubmissionApp") 62 | .persistenceUnit("AssignmentSubmissionApp") 63 | .build(); 64 | HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); 65 | 66 | Map properties = new HashMap<>(); 67 | properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); 68 | properties.put("hibernate.hbm2ddl.auto", "update"); 69 | properties.put("hibernate.show-sql", "true"); 70 | properties.put("hibernate.implicit_naming_strategy", 71 | "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy"); 72 | properties.put("hibernate.physical_naming_strategy", 73 | "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"); 74 | em.setJpaVendorAdapter(adapter); 75 | em.getJpaPropertyMap().putAll(properties); 76 | 77 | return em; 78 | } 79 | 80 | @Primary 81 | @Bean(name = "transactionManager") 82 | public PlatformTransactionManager transactionManager( 83 | @Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) { 84 | return new JpaTransactionManager(entityManagerFactory); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/PrimaryDbConfigProd.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import javax.persistence.EntityManagerFactory; 7 | import javax.sql.DataSource; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.jdbc.DataSourceBuilder; 14 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.annotation.Primary; 18 | import org.springframework.context.annotation.Profile; 19 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 20 | import org.springframework.orm.jpa.JpaTransactionManager; 21 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 22 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 23 | import org.springframework.transaction.PlatformTransactionManager; 24 | import org.springframework.transaction.annotation.EnableTransactionManagement; 25 | 26 | @Configuration 27 | @EnableTransactionManagement 28 | @EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory", basePackages = { 29 | "com.coderscampus.AssignmentSubmissionApp" }) 30 | @Profile("prod") 31 | public class PrimaryDbConfigProd { 32 | 33 | Logger log = LoggerFactory.getLogger(PrimaryDbConfigProd.class); 34 | 35 | @Value("${DB_USERNAME}") 36 | private String dbUsername; 37 | @Value("${DB_PASSWORD}") 38 | private String dbPassword; 39 | @Value("${DB_URL}") 40 | private String dbUrl; 41 | 42 | @Primary 43 | @Bean(name = "dataSource") 44 | public DataSource getDataSource() { 45 | 46 | return DataSourceBuilder.create() 47 | .driverClassName("com.mysql.cj.jdbc.Driver") 48 | .url(dbUrl) 49 | .username(dbUsername) 50 | .password(dbPassword) 51 | .build(); 52 | } 53 | 54 | @Primary 55 | @Bean(name = "entityManagerFactory") 56 | public LocalContainerEntityManagerFactoryBean entityManagerFactory( 57 | EntityManagerFactoryBuilder builder, 58 | @Qualifier("dataSource") DataSource dataSource) { 59 | LocalContainerEntityManagerFactoryBean em = builder 60 | .dataSource(dataSource) 61 | .packages("com.coderscampus.AssignmentSubmissionApp") 62 | .persistenceUnit("AssignmentSubmissionApp") 63 | .build(); 64 | HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); 65 | 66 | Map properties = new HashMap<>(); 67 | properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); 68 | properties.put("hibernate.hbm2ddl.auto", "update"); 69 | properties.put("hibernate.show-sql", "true"); 70 | properties.put("hibernate.implicit_naming_strategy", 71 | "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy"); 72 | properties.put("hibernate.physical_naming_strategy", 73 | "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"); 74 | em.setJpaVendorAdapter(adapter); 75 | em.getJpaPropertyMap().putAll(properties); 76 | 77 | return em; 78 | } 79 | 80 | @Primary 81 | @Bean(name = "transactionManager") 82 | public PlatformTransactionManager transactionManager( 83 | @Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) { 84 | return new JpaTransactionManager(entityManagerFactory); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/ProffessoDbConfig.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.jdbc.DataSourceBuilder; 8 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Profile; 12 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 13 | import org.springframework.orm.jpa.JpaTransactionManager; 14 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 15 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 16 | import org.springframework.transaction.PlatformTransactionManager; 17 | import org.springframework.transaction.annotation.EnableTransactionManagement; 18 | 19 | import javax.persistence.EntityManagerFactory; 20 | import javax.sql.DataSource; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | @Configuration 25 | @EnableTransactionManagement 26 | @EnableJpaRepositories(entityManagerFactoryRef = "proffessoEntityManagerFactory", transactionManagerRef = "proffessoTransactionManager", basePackages = { 27 | "com.coderscampus.proffesso"}) 28 | @Profile("local") 29 | public class ProffessoDbConfig { 30 | Logger log = LoggerFactory.getLogger(PrimaryDbConfig.class); 31 | 32 | @Value("${PROFFESSO_DB_USERNAME}") 33 | private String dbUsername; 34 | @Value("${PROFFESSO_DB_PASSWORD}") 35 | private String dbPassword; 36 | 37 | @Bean(name = "proffessoDataSource") 38 | public DataSource getDataSource() { 39 | 40 | return DataSourceBuilder.create() 41 | .driverClassName("com.mysql.cj.jdbc.Driver") 42 | .url("jdbc:mysql://localhost:3306/proffessoproddb") 43 | .username(dbUsername) 44 | .password(dbPassword) 45 | .build(); 46 | } 47 | 48 | @Bean(name = "proffessoEntityManagerFactory") 49 | public LocalContainerEntityManagerFactoryBean proffessoEntityManagerFactory( 50 | EntityManagerFactoryBuilder builder, 51 | @Qualifier("proffessoDataSource") DataSource dataSource) { 52 | LocalContainerEntityManagerFactoryBean em = builder 53 | .dataSource(dataSource) 54 | .packages("com.coderscampus.proffesso") 55 | .persistenceUnit("proffesso") 56 | .build(); 57 | HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); 58 | 59 | Map properties = new HashMap<>(); 60 | properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); 61 | properties.put("hibernate.hbm2ddl.auto", "update"); 62 | properties.put("hibernate.show-sql", "true"); 63 | properties.put("hibernate.implicit_naming_strategy", 64 | "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy"); 65 | properties.put("hibernate.physical_naming_strategy", 66 | "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"); 67 | em.setJpaVendorAdapter(adapter); 68 | em.getJpaPropertyMap().putAll(properties); 69 | return em; 70 | } 71 | 72 | @Bean(name = "proffessoTransactionManager") 73 | public PlatformTransactionManager proffessoTransactionManager( 74 | @Qualifier("proffessoEntityManagerFactory") EntityManagerFactory proffessoEntityManagerFactory) { 75 | return new JpaTransactionManager(proffessoEntityManagerFactory); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/ProffessoDbConfigDev.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import javax.persistence.EntityManagerFactory; 7 | import javax.sql.DataSource; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.jdbc.DataSourceBuilder; 14 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.annotation.Profile; 18 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 19 | import org.springframework.orm.jpa.JpaTransactionManager; 20 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 21 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 22 | import org.springframework.transaction.PlatformTransactionManager; 23 | import org.springframework.transaction.annotation.EnableTransactionManagement; 24 | 25 | @Configuration 26 | @EnableTransactionManagement 27 | @EnableJpaRepositories(entityManagerFactoryRef = "proffessoEntityManagerFactory", transactionManagerRef = "proffessoTransactionManager", basePackages = { 28 | "com.coderscampus.proffesso" }) 29 | @Profile("dev") 30 | public class ProffessoDbConfigDev { 31 | Logger log = LoggerFactory.getLogger(PrimaryDbConfig.class); 32 | 33 | @Value("${PROFFESSO_DB_USERNAME}") 34 | private String dbUsername; 35 | @Value("${PROFFESSO_DB_PASSWORD}") 36 | private String dbPassword; 37 | 38 | @Bean(name = "proffessoDataSource") 39 | public DataSource getDataSource() { 40 | 41 | return DataSourceBuilder.create() 42 | .driverClassName("com.mysql.cj.jdbc.Driver") 43 | .url("jdbc:mysql://mysql.cod3rscampus.com/proffessoproddb") 44 | .username(dbUsername) 45 | .password(dbPassword) 46 | .build(); 47 | 48 | } 49 | 50 | @Bean(name = "proffessoEntityManagerFactory") 51 | public LocalContainerEntityManagerFactoryBean proffessoEntityManagerFactory( 52 | EntityManagerFactoryBuilder builder, 53 | @Qualifier("proffessoDataSource") DataSource dataSource) { 54 | LocalContainerEntityManagerFactoryBean em = builder 55 | .dataSource(dataSource) 56 | .packages("com.coderscampus.proffesso") 57 | .persistenceUnit("proffesso") 58 | .build(); 59 | HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); 60 | 61 | Map properties = new HashMap<>(); 62 | properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); 63 | properties.put("hibernate.hbm2ddl.auto", "update"); 64 | properties.put("hibernate.show-sql", "true"); 65 | properties.put("hibernate.implicit_naming_strategy", 66 | "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy"); 67 | properties.put("hibernate.physical_naming_strategy", 68 | "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"); 69 | em.setJpaVendorAdapter(adapter); 70 | em.getJpaPropertyMap().putAll(properties); 71 | return em; 72 | } 73 | 74 | @Bean(name = "proffessoTransactionManager") 75 | public PlatformTransactionManager proffessoTransactionManager( 76 | @Qualifier("proffessoEntityManagerFactory") EntityManagerFactory proffessoEntityManagerFactory) { 77 | return new JpaTransactionManager(proffessoEntityManagerFactory); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/ProffessoDbConfigProd.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import javax.persistence.EntityManagerFactory; 7 | import javax.sql.DataSource; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Qualifier; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.jdbc.DataSourceBuilder; 14 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.annotation.Profile; 18 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 19 | import org.springframework.orm.jpa.JpaTransactionManager; 20 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 21 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 22 | import org.springframework.transaction.PlatformTransactionManager; 23 | import org.springframework.transaction.annotation.EnableTransactionManagement; 24 | 25 | @Configuration 26 | @EnableTransactionManagement 27 | @EnableJpaRepositories(entityManagerFactoryRef = "proffessoEntityManagerFactory", transactionManagerRef = "proffessoTransactionManager", basePackages = { 28 | "com.coderscampus.proffesso" }) 29 | @Profile("prod") 30 | public class ProffessoDbConfigProd { 31 | Logger log = LoggerFactory.getLogger(PrimaryDbConfig.class); 32 | 33 | @Value("${PROFFESSO_DB_USERNAME}") 34 | private String dbUsername; 35 | @Value("${PROFFESSO_DB_PASSWORD}") 36 | private String dbPassword; 37 | 38 | @Bean(name = "proffessoDataSource") 39 | public DataSource getDataSource() { 40 | 41 | return DataSourceBuilder.create() 42 | .driverClassName("com.mysql.cj.jdbc.Driver") 43 | .url("jdbc:mysql://proffessoprod.cq548thrnnwh.us-east-1.rds.amazonaws.com:3306/proffessoproddb") 44 | .username(dbUsername) 45 | .password(dbPassword) 46 | .build(); 47 | 48 | } 49 | 50 | @Bean(name = "proffessoEntityManagerFactory") 51 | public LocalContainerEntityManagerFactoryBean proffessoEntityManagerFactory( 52 | EntityManagerFactoryBuilder builder, 53 | @Qualifier("proffessoDataSource") DataSource dataSource) { 54 | LocalContainerEntityManagerFactoryBean em = builder 55 | .dataSource(dataSource) 56 | .packages("com.coderscampus.proffesso") 57 | .persistenceUnit("proffesso") 58 | .build(); 59 | HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); 60 | 61 | Map properties = new HashMap<>(); 62 | properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect"); 63 | properties.put("hibernate.hbm2ddl.auto", "update"); 64 | properties.put("hibernate.show-sql", "true"); 65 | properties.put("hibernate.implicit_naming_strategy", 66 | "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy"); 67 | properties.put("hibernate.physical_naming_strategy", 68 | "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"); 69 | em.setJpaVendorAdapter(adapter); 70 | em.getJpaPropertyMap().putAll(properties); 71 | return em; 72 | } 73 | 74 | @Bean(name = "proffessoTransactionManager") 75 | public PlatformTransactionManager proffessoTransactionManager( 76 | @Qualifier("proffessoEntityManagerFactory") EntityManagerFactory proffessoEntityManagerFactory) { 77 | return new JpaTransactionManager(proffessoEntityManagerFactory); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(AssignmentSubmissionApplication.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/proffesso/domain/Authority.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.proffesso.domain; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | import javax.persistence.ManyToOne; 8 | 9 | import org.springframework.security.core.GrantedAuthority; 10 | 11 | import com.fasterxml.jackson.annotation.JsonIgnore; 12 | 13 | @Entity 14 | public class Authority implements GrantedAuthority { 15 | private static final long serialVersionUID = -6520888182797362903L; 16 | 17 | @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | private String authority; 20 | @ManyToOne(optional = false) 21 | @JsonIgnore 22 | private ProffessoUser user; 23 | 24 | public Authority () {} 25 | 26 | public Authority (String authority) { 27 | this.authority = authority; 28 | } 29 | 30 | public Long getId() { 31 | return id; 32 | } 33 | public void setId(Long id) { 34 | this.id = id; 35 | } 36 | @Override 37 | public String getAuthority() { 38 | return authority; 39 | } 40 | public void setAuthority(String authority) { 41 | this.authority = authority; 42 | } 43 | 44 | public ProffessoUser getUser() { 45 | return user; 46 | } 47 | public void setUser(ProffessoUser user) { 48 | this.user = user; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/proffesso/domain/Order.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.proffesso.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 5 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 6 | 7 | import javax.persistence.*; 8 | import java.io.Serializable; 9 | import java.util.Date; 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | @Entity 14 | @Table(name = "orders") 15 | @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id") 16 | @EntityListeners(AuditingEntityListener.class) 17 | public class Order implements Serializable { 18 | private static final long serialVersionUID = 8997396493278439691L; 19 | private Long id; 20 | private Date orderDate; 21 | private Set offers = new HashSet<>(); 22 | private ProffessoUser user; 23 | private Boolean delinquent = false; 24 | private Date suspendOn; 25 | private String purchaseUrl; 26 | private Date cancellationRequestedOn; 27 | private Date refundRequestedOn; 28 | 29 | @Id 30 | @GeneratedValue(strategy = GenerationType.IDENTITY) 31 | public Long getId() { 32 | return id; 33 | } 34 | 35 | public void setId(Long id) { 36 | this.id = id; 37 | } 38 | 39 | @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) 40 | @JoinTable(name = "orders_offers", joinColumns = @JoinColumn(name = "orders_id"), inverseJoinColumns = @JoinColumn(name = "offers_id")) 41 | public Set getOffers() { 42 | return offers; 43 | } 44 | 45 | public void setOffers(Set offers) { 46 | this.offers = offers; 47 | } 48 | 49 | @ManyToOne(cascade = CascadeType.PERSIST) 50 | public ProffessoUser getUser() { 51 | return user; 52 | } 53 | 54 | public void setUser(ProffessoUser user) { 55 | this.user = user; 56 | } 57 | 58 | public Date getOrderDate() { 59 | return orderDate; 60 | } 61 | 62 | public void setOrderDate(Date orderDate) { 63 | this.orderDate = orderDate; 64 | } 65 | 66 | public Boolean getDelinquent() { 67 | return delinquent; 68 | } 69 | 70 | public void setDelinquent(Boolean delinquent) { 71 | this.delinquent = delinquent; 72 | } 73 | 74 | public Date getSuspendOn() { 75 | return suspendOn; 76 | } 77 | 78 | public void setSuspendOn(Date suspendOn) { 79 | this.suspendOn = suspendOn; 80 | } 81 | 82 | public String getPurchaseUrl() { 83 | return purchaseUrl; 84 | } 85 | 86 | public void setPurchaseUrl(String purchaseUrl) { 87 | this.purchaseUrl = purchaseUrl; 88 | } 89 | 90 | public Date getCancellationRequestedOn() { 91 | return cancellationRequestedOn; 92 | } 93 | 94 | public void setCancellationRequestedOn(Date cancellationRequestedOn) { 95 | this.cancellationRequestedOn = cancellationRequestedOn; 96 | } 97 | 98 | public Date getRefundRequestedOn() { 99 | return refundRequestedOn; 100 | } 101 | 102 | public void setRefundRequestedOn(Date refundRequestedOn) { 103 | this.refundRequestedOn = refundRequestedOn; 104 | } 105 | 106 | @Override 107 | public int hashCode() { 108 | final int prime = 31; 109 | int result = 1; 110 | result = prime * result + ((id == null) ? 0 : id.hashCode()); 111 | return result; 112 | } 113 | 114 | @Override 115 | public boolean equals(Object obj) { 116 | if (this == obj) 117 | return true; 118 | if (obj == null) 119 | return false; 120 | if (getClass() != obj.getClass()) 121 | return false; 122 | Order other = (Order) obj; 123 | if (id == null) { 124 | if (other.id != null) 125 | return false; 126 | } else if (!id.equals(other.id)) 127 | return false; 128 | return true; 129 | } 130 | 131 | @Override 132 | public String toString() { 133 | return "Order [id=" + id + ", orderDate=" + orderDate + ", suspendOn=" + suspendOn + "]"; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/proffesso/domain/ProffessoUser.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.proffesso.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | 5 | import javax.persistence.*; 6 | import java.util.HashSet; 7 | import java.util.Objects; 8 | import java.util.Set; 9 | 10 | @Entity 11 | @Table(name = "users") 12 | public class ProffessoUser { 13 | private static final long serialVersionUID = -1605751799209099860L; 14 | 15 | private Long id; 16 | private String email; 17 | @JsonIgnore 18 | private String password; 19 | private Set authorities = new HashSet<>(); 20 | private Set orders = new HashSet<>(); 21 | private String name; 22 | 23 | @Id 24 | @GeneratedValue(strategy = GenerationType.IDENTITY) 25 | public Long getId() { 26 | return id; 27 | } 28 | 29 | public void setId(Long id) { 30 | this.id = id; 31 | } 32 | 33 | 34 | public String getEmail() { 35 | return email; 36 | } 37 | 38 | public void setEmail(String email) { 39 | this.email = email; 40 | } 41 | 42 | public String getPassword() { 43 | return password; 44 | } 45 | 46 | public void setPassword(String password) { 47 | this.password = password; 48 | } 49 | 50 | @OneToMany(fetch = FetchType.EAGER, mappedBy = "user") 51 | public Set getAuthorities() { 52 | return authorities; 53 | } 54 | 55 | public void setAuthorities(Set authorities) { 56 | this.authorities = authorities; 57 | } 58 | 59 | public String getName() { 60 | return name; 61 | } 62 | 63 | public void setName(String name) { 64 | this.name = name; 65 | } 66 | 67 | @OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.LAZY) 68 | public Set getOrders() { 69 | return orders; 70 | } 71 | 72 | public void setOrders(Set orders) { 73 | this.orders = orders; 74 | } 75 | 76 | @Override 77 | public boolean equals(Object o) { 78 | if (this == o) return true; 79 | if (o == null || getClass() != o.getClass()) return false; 80 | ProffessoUser that = (ProffessoUser) o; 81 | return id.equals(that.id); 82 | } 83 | 84 | @Override 85 | public int hashCode() { 86 | return Objects.hash(id); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/proffesso/repository/OfferRepository.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.proffesso.repository; 2 | 3 | import com.coderscampus.proffesso.domain.Offer; 4 | import org.springframework.data.repository.PagingAndSortingRepository; 5 | 6 | public interface OfferRepository extends PagingAndSortingRepository { 7 | // @Query(value = "select distinct o from Offer o " 8 | // + " left join o.courses c " 9 | // + " left join c.reviews r " 10 | // + " left join r.user u " 11 | // + " left join u.authorities a " 12 | // + " where active = true") 13 | // public Page findAllActiveOffers(Pageable pageable); 14 | // 15 | // public Offer findByName(String offerName); 16 | // 17 | // public Set findByNameIn(List names); 18 | // 19 | // public Offer findByVideoId(String uuid); 20 | // 21 | // // select * from offer where url = :offerName 22 | // @Query("select o from Offer o " 23 | // + " left join fetch o.courses c " 24 | // + " left join fetch c.reviews r " 25 | // + " left join fetch r.user u " 26 | // + " left join fetch u.authorities a " 27 | // + " left join fetch c.sections s" 28 | // + " left join fetch s.lessons l " 29 | // + " where o.url = :offerName") 30 | // public Offer findByUrl(@Param("offerName") String offerName); 31 | // 32 | // public List findByIdIn(List offerIds); 33 | // 34 | // @Query("select distinct o from Offer o " 35 | // + "join o.courses as course " 36 | // + "join course.user as u " 37 | // + "where u = :user " 38 | // + "and o.type = :type " 39 | // + "order by o.name") 40 | // public List findByUserAndType(@Param("user") ProffessoUser user, @Param("type") String type); 41 | // 42 | // @Query(value = "select o from Offer o " 43 | // + " left join fetch o.courses c " 44 | // + " left join fetch c.reviews r " 45 | // + " left join fetch r.user u " 46 | // + " left join fetch u.authorities a " 47 | // + " where o.id in :ids") 48 | // public Set findOffersWithCoursesAndReviews(@Param("ids") List ids); 49 | } 50 | -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/proffesso/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.proffesso.repository; 2 | 3 | import com.coderscampus.proffesso.domain.Order; 4 | import com.coderscampus.proffesso.domain.ProffessoUser; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | import org.springframework.data.repository.query.Param; 8 | 9 | import java.util.Set; 10 | 11 | public interface OrderRepository extends JpaRepository { 12 | // Page findByOffersIn(@Param(value = "offers") Set offers, Pageable page); 13 | 14 | @Query("select o from Order o " + 15 | "join fetch o.offers " + 16 | "where o.user = :user") 17 | Set findByUser(@Param(value = "user") ProffessoUser user); 18 | 19 | } -------------------------------------------------------------------------------- /back-end/src/main/java/com/coderscampus/proffesso/repository/ProffessoUserRepo.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.proffesso.repository; 2 | 3 | import com.coderscampus.proffesso.domain.ProffessoUser; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import javax.persistence.Tuple; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public interface ProffessoUserRepo extends JpaRepository { 12 | @Query("select u from ProffessoUser u " 13 | + "left join fetch u.authorities " 14 | + "where u.email = :username ") 15 | Optional findByEmail(String username); 16 | 17 | @Query(value = "CALL find_bootcamp_students();", nativeQuery = true) 18 | List findBootcampStudents(); 19 | 20 | @Query("select u from ProffessoUser u " + 21 | "join u.orders o " + 22 | "join o.offers off " + 23 | "where off.id = 225 " + 24 | "and (o.suspendOn is not null and o.suspendOn < now())") 25 | List findDroppedBootcampStudents(); 26 | } 27 | -------------------------------------------------------------------------------- /back-end/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | #spring.datasource.url=jdbc:mysql://localhost:3306/assignment_submission_db 2 | #spring.datasource.username=example_user 3 | #spring.datasource.password=password123 4 | 5 | #spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect 6 | #spring.jpa.show-sql=true 7 | #spring.jpa.hibernate.ddl-auto=update 8 | 9 | # coderscampus.com/bootcamp 10 | 11 | cookies.domain=localhost 12 | domainRoot=http://localhost:3000 13 | 14 | spring.security.filter.order=10 15 | 16 | server.port=8081 -------------------------------------------------------------------------------- /back-end/src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | #spring.datasource.url=jdbc:mysql://localhost:3306/assignment_submission_db 2 | #spring.datasource.username=example_user 3 | #spring.datasource.password=password123 4 | 5 | #spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect 6 | #spring.jpa.show-sql=true 7 | #spring.jpa.properties.hibernate.format_sql=true 8 | #spring.jpa.hibernate.ddl-auto=update 9 | 10 | # coderscampus.com/bootcamp 11 | 12 | cookies.domain=localhost 13 | domainRoot=http://localhost:3000 14 | 15 | spring.security.filter.order=10 16 | 17 | server.port=8081 -------------------------------------------------------------------------------- /back-end/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | #spring.datasource.url=jdbc:mysql://localhost:3306/assignment_submission_db 2 | #spring.datasource.username=example_user 3 | #spring.datasource.password=password123 4 | 5 | #spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect 6 | #spring.jpa.show-sql=true 7 | #spring.jpa.hibernate.ddl-auto=update 8 | 9 | # coderscampus.com/bootcamp 10 | 11 | cookies.domain=coderscampus.com 12 | domainRoot=https://assignments.coderscampus.com 13 | 14 | spring.security.filter.order=10 15 | 16 | server.port=8081 17 | -------------------------------------------------------------------------------- /back-end/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | %white(%d{ISO8601}) %highlight(%-5level) [%white(%t)] %yellow(%C{1.}): %msg%n%throwable 11 | 12 | 13 | 14 | 15 | 17 | ${LOGS}/spring-boot-logger.log 18 | 20 | %d %p %C{1.} [%t] %m%n 21 | 22 | 23 | 25 | 26 | ${LOGS}/archived/spring-boot-logger-%d{yyyy-MM-dd}.%i.log 27 | 28 | 30 | 10MB 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /back-end/src/test/java/com/coderscampus/AssignmentSubmissionApp/service/NotificationServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.coderscampus.AssignmentSubmissionApp.service; 2 | 3 | import com.coderscampus.AssignmentSubmissionApp.domain.Assignment; 4 | import com.coderscampus.AssignmentSubmissionApp.domain.User; 5 | import org.junit.jupiter.api.Test; 6 | 7 | 8 | class NotificationServiceTest { 9 | 10 | NotificationService sut = new NotificationService(); 11 | 12 | void test() { 13 | Assignment testAssignment = new Assignment(); 14 | 15 | User user = new User(); 16 | user.setName("Trevor Page"); 17 | 18 | testAssignment.setUser(user); 19 | testAssignment.setNumber(4); 20 | sut.sendCongratsOnAssignmentSubmissionSlackMessage(testAssignment, "C05UGL8F42H"); 21 | 22 | } 23 | 24 | // @Test 25 | void code_reviewer_channel_slack_test () { 26 | User user = new User(); 27 | user.setName("Trevor Page"); 28 | Assignment testAssignment = new Assignment(); 29 | 30 | testAssignment.setUser(user); 31 | testAssignment.setNumber(4); 32 | testAssignment.setStatus("SUBMITTED"); 33 | sut.sendAssignmentStatusUpdateCodeReviewer("PENDING_SUBMISSION", testAssignment); 34 | } 35 | 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | back-end: 3 | image: 851518566978.dkr.ecr.us-east-1.amazonaws.com/assignment-submission-backend-dev 4 | ports: 5 | - 8081:8081 6 | environment: 7 | DB_URL: jdbc:mysql://18.209.158.33:3306/assignment_submission_db 8 | DB_USERNAME: example_user 9 | DB_PASSWORD: password123 10 | front-end: 11 | image: 851518566978.dkr.ecr.us-east-1.amazonaws.com/assignment-submission-frontend-dev 12 | ports: 13 | - 3000:3000 14 | -------------------------------------------------------------------------------- /front-end/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .dockerignore 4 | Dockerfile 5 | Dockerfile.prod -------------------------------------------------------------------------------- /front-end/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /front-end/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:17-alpine3.15 AS builder 2 | # Set working directory 3 | WORKDIR /app 4 | # Copy all files from current directory to working dir in image 5 | COPY . . 6 | # install node modules and build assets 7 | RUN npm install && npm run build 8 | 9 | # nginx state for serving content 10 | FROM nginx:alpine 11 | 12 | RUN rm /etc/nginx/conf.d/default.conf 13 | 14 | COPY nginx.conf /etc/nginx/conf.d 15 | 16 | # Set working directory to nginx asset directory 17 | WORKDIR /usr/share/nginx/html 18 | # Remove default nginx static assets 19 | RUN rm -rf ./* 20 | # Copy static assets from builder stage 21 | COPY --from=builder /app/build . 22 | # Containers run nginx with global directives and daemon off 23 | ENTRYPOINT ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /front-end/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /front-end/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | include /etc/nginx/mime.types; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri $uri/ /index.html; 9 | } 10 | 11 | location /api { 12 | try_files $uri @proxy_api; 13 | } 14 | 15 | location @proxy_api { 16 | proxy_set_header X-Forwarded-Proto https; 17 | proxy_set_header X-Url-Scheme $scheme; 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | proxy_set_header Host $http_host; 20 | proxy_redirect off; 21 | #proxy_pass http://host.docker.internal:8081; # for local 22 | proxy_pass http://localhost:8081; # for dev 23 | } 24 | } -------------------------------------------------------------------------------- /front-end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://localhost:8081/", 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.16.1", 8 | "@testing-library/react": "^12.1.2", 9 | "@testing-library/user-event": "^13.5.0", 10 | "bootstrap": "^5.1.3", 11 | "custom-react-step-progress-bar": "^1.0.8", 12 | "dayjs": "^1.10.7", 13 | "http-proxy-middleware": "^2.0.2", 14 | "js-cookie": "^3.0.1", 15 | "jwt-check-expiration": "^1.0.5", 16 | "moment": "^2.29.4", 17 | "react": "^17.0.2", 18 | "react-bootstrap": "^2.1.2", 19 | "react-dom": "^17.0.2", 20 | "react-router-dom": "^6.2.1", 21 | "react-scripts": "5.0.0", 22 | "react-step-progress-bar": "^1.0.3", 23 | "sass": "^1.49.8", 24 | "styled-components": "^5.3.3", 25 | "web-vitals": "^2.1.2" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "deploy": "aws s3 sync build/ s3://assignments.coderscampus.com --acl public-read", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject" 33 | }, 34 | "eslintConfig": { 35 | "extends": [ 36 | "react-app", 37 | "react-app/jest" 38 | ] 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /front-end/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tp02ga/AssignmentSubmissionApp/d76607ed82e5a43672c9ddb8af8947a4b21fa1bb/front-end/public/favicon.ico -------------------------------------------------------------------------------- /front-end/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Assignment Submission 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /front-end/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tp02ga/AssignmentSubmissionApp/d76607ed82e5a43672c9ddb8af8947a4b21fa1bb/front-end/public/logo192.png -------------------------------------------------------------------------------- /front-end/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tp02ga/AssignmentSubmissionApp/d76607ed82e5a43672c9ddb8af8947a4b21fa1bb/front-end/public/logo512.png -------------------------------------------------------------------------------- /front-end/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /front-end/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /front-end/src/App.css: -------------------------------------------------------------------------------- 1 | .assignment-wrapper { 2 | border: 1px dashed lightgray; 3 | padding: 2em; 4 | border-radius: 1em; 5 | margin-top: 2em; 6 | } 7 | 8 | .assignment-wrapper-title { 9 | width: min-content !important; 10 | margin-top: -2em !important; 11 | margin-bottom: 1em !important; 12 | background-color: white !important; 13 | white-space: nowrap !important; 14 | } 15 | 16 | .error { 17 | color: red; 18 | } 19 | 20 | .link { 21 | text-decoration: none; 22 | cursor: pointer; 23 | color: black; 24 | } 25 | .blue-link { 26 | text-decoration: none; 27 | cursor: pointer; 28 | color: blue; 29 | } 30 | .comment-bubble { 31 | background: #eeeeee; 32 | border-radius: 1em; 33 | padding: 1em; 34 | margin: 1em 0em; 35 | } 36 | 37 | .logo { 38 | padding: 0.5em 0em; 39 | max-width: 10em; 40 | } 41 | 42 | .nav { 43 | min-height: 100px; 44 | border-bottom: 1px solid grey; 45 | 46 | width: 100%; 47 | } 48 | 49 | .NavBar { 50 | padding-top: 25px; 51 | } 52 | -------------------------------------------------------------------------------- /front-end/src/App.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import jwt_decode from "jwt-decode"; 3 | import { Route, Routes } from "react-router-dom"; 4 | import "./App.css"; 5 | import AssignmentView from "./AssignmentView"; 6 | import Dashboard from "./Dashboard"; 7 | import CodeReviewerDashboard from "./CodeReviewerDashboard"; 8 | import Homepage from "./Homepage"; 9 | import Login from "./Login"; 10 | import PrivateRoute from "./PrivateRoute"; 11 | import "./custom.scss"; 12 | import CodeReviewerAssignmentView from "./CodeReviewAssignmentView"; 13 | import { useUser } from "./UserProvider"; 14 | import InstructorDashboard from "./InstructorDashboard"; 15 | 16 | function App() { 17 | const [roles, setRoles] = useState([]); 18 | const user = useUser(); 19 | 20 | useEffect(() => { 21 | setRoles(getRolesFromJWT()); 22 | }, [user.jwt]); 23 | 24 | function getRolesFromJWT() { 25 | if (user.jwt) { 26 | const decodedJwt = jwt_decode(user.jwt); 27 | return decodedJwt.authorities; 28 | } 29 | return []; 30 | } 31 | return ( 32 | 33 | role === "ROLE_CODE_REVIEWER") ? ( 37 | 38 | 39 | 40 | ) : ( 41 | 42 | 43 | 44 | ) 45 | } 46 | /> 47 | role === "ROLE_INSTRUCTOR") ? ( 51 | 52 | 53 | 54 | ) : ( 55 |
You don't have the appropriate role. Talk to Trevor.
56 | ) 57 | } 58 | /> 59 | role === "ROLE_CODE_REVIEWER") ? ( 63 | 64 | 65 | 66 | ) : ( 67 | 68 | 69 | 70 | ) 71 | } 72 | /> 73 | } /> 74 | } /> 75 |
76 | ); 77 | } 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /front-end/src/AssignmentView/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import { 3 | Button, 4 | ButtonGroup, 5 | Col, 6 | Container, 7 | Dropdown, 8 | DropdownButton, 9 | Form, 10 | Row, 11 | } from "react-bootstrap"; 12 | import ajax from "../Services/fetchService"; 13 | import StatusBadge from "../StatusBadge"; 14 | import { useNavigate, useParams } from "react-router-dom"; 15 | import { useUser } from "../UserProvider"; 16 | import CommentContainer from "../CommentContainer"; 17 | import NavBar from "../NavBar"; 18 | import { getButtonsByStatusAndRole } from "../Services/statusService"; 19 | 20 | const AssignmentView = () => { 21 | let navigate = useNavigate(); 22 | const user = useUser(); 23 | const { assignmentId } = useParams(); 24 | 25 | // const assignmentId = window.location.href.split("/assignments/")[1]; 26 | const [assignment, setAssignment] = useState({ 27 | branch: "", 28 | githubUrl: "", 29 | number: null, 30 | status: null, 31 | }); 32 | 33 | const [assignmentEnums, setAssignmentEnums] = useState([]); 34 | const [assignmentStatuses, setAssignmentStatuses] = useState([]); 35 | 36 | const prevAssignmentValue = useRef(assignment); 37 | 38 | function updateAssignment(prop, value) { 39 | const newAssignment = { ...assignment }; 40 | newAssignment[prop] = value; 41 | setAssignment(newAssignment); 42 | } 43 | 44 | function save(status) { 45 | // this implies that the student is submitting the assignment for the first time 46 | 47 | if (status && assignment.status !== status) { 48 | updateAssignment("status", status); 49 | } else { 50 | persist(); 51 | } 52 | } 53 | 54 | function persist() { 55 | ajax(`/api/assignments/${assignmentId}`, "PUT", user.jwt, assignment).then( 56 | (assignmentData) => { 57 | setAssignment(assignmentData); 58 | } 59 | ); 60 | } 61 | useEffect(() => { 62 | if (prevAssignmentValue.current.status !== assignment.status) { 63 | persist(); 64 | } 65 | prevAssignmentValue.current = assignment; 66 | }, [assignment]); 67 | 68 | useEffect(() => { 69 | ajax(`/api/assignments/${assignmentId}`, "GET", user.jwt).then( 70 | (assignmentResponse) => { 71 | let assignmentData = assignmentResponse.assignment; 72 | if (assignmentData.branch === null) assignmentData.branch = ""; 73 | if (assignmentData.githubUrl === null) assignmentData.githubUrl = ""; 74 | setAssignment(assignmentData); 75 | setAssignmentEnums(assignmentResponse.assignmentEnums); 76 | setAssignmentStatuses(assignmentResponse.statusEnums); 77 | } 78 | ); 79 | }, []); 80 | 81 | return ( 82 | <> 83 | 84 | 85 | 86 | 87 | {assignment && assignment.number && assignmentEnums.length > 0 ? ( 88 | <> 89 |

Assignment {assignment.number}

90 |

{assignmentEnums[assignment.number - 1].assignmentName}

91 | 92 | ) : ( 93 | <> 94 | )} 95 | 96 | 97 | {assignment ? : <>} 98 | 99 |
100 | {assignment ? ( 101 | <> 102 | 103 | 104 | Assignment Number: 105 | 106 | 107 | { 116 | updateAssignment("number", selectedElement); 117 | }} 118 | > 119 | {assignmentEnums.map((assignmentEnum) => ( 120 | 124 | {assignmentEnum.assignmentNum} 125 | 126 | ))} 127 | 128 | 129 | 130 | 131 | 132 | GitHub URL: 133 | 134 | 135 | 137 | updateAssignment("githubUrl", e.target.value) 138 | } 139 | type="url" 140 | value={assignment.githubUrl} 141 | placeholder="https://github.com/username/repo-name" 142 | /> 143 | 144 | 145 | 146 | 147 | 148 | Branch: 149 | 150 | 151 | updateAssignment("branch", e.target.value)} 155 | value={assignment.branch} 156 | /> 157 | 158 | 159 | 160 |
161 | {getButtonsByStatusAndRole(assignment.status, "student").map( 162 | (btn) => ( 163 | 174 | ) 175 | )} 176 |
177 | 178 | 179 | ) : ( 180 | <> 181 | )} 182 |
183 | 184 | ); 185 | }; 186 | 187 | export default AssignmentView; 188 | -------------------------------------------------------------------------------- /front-end/src/CodeReviewAssignmentView/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import { Button, Col, Container, Form, Row } from "react-bootstrap"; 3 | import CommentContainer from "../CommentContainer"; 4 | import NavBar from "../NavBar"; 5 | import ajax from "../Services/fetchService"; 6 | import { getButtonsByStatusAndRole } from "../Services/statusService"; 7 | import StatusBadge from "../StatusBadge"; 8 | import { useUser } from "../UserProvider"; 9 | 10 | const CodeReviewerAssignmentView = () => { 11 | const user = useUser(); 12 | const assignmentId = window.location.href.split("/assignments/")[1]; 13 | const [assignment, setAssignment] = useState({ 14 | branch: "", 15 | githubUrl: "", 16 | number: null, 17 | status: null, 18 | codeReviewVideoUrl: null, 19 | }); 20 | const [assignmentEnums, setAssignmentEnums] = useState([]); 21 | const [assignmentStatuses, setAssignmentStatuses] = useState([]); 22 | const [errorMsg, setErrorMsg] = useState(""); 23 | const prevAssignmentValue = useRef(assignment); 24 | 25 | function updateAssignment(prop, value) { 26 | const newAssignment = { ...assignment }; 27 | newAssignment[prop] = value; 28 | setAssignment(newAssignment); 29 | } 30 | 31 | function save(status) { 32 | setErrorMsg(""); 33 | if ( 34 | status === "Completed" && 35 | (assignment.codeReviewVideoUrl === null || 36 | assignment.codeReviewVideoUrl === "") 37 | ) { 38 | setErrorMsg( 39 | "Please insert the URL to the video review for the student to watch." 40 | ); 41 | return; 42 | } 43 | if (status && assignment.status !== status) { 44 | updateAssignment("status", status); 45 | } else { 46 | persist(); 47 | } 48 | } 49 | 50 | function persist() { 51 | ajax(`/api/assignments/${assignmentId}`, "PUT", user.jwt, assignment).then( 52 | (assignmentData) => { 53 | setAssignment(assignmentData); 54 | } 55 | ); 56 | } 57 | useEffect(() => { 58 | if ( 59 | assignment && 60 | prevAssignmentValue.current.status !== assignment.status 61 | ) { 62 | persist(); 63 | } 64 | prevAssignmentValue.current = assignment; 65 | }, [assignment]); 66 | 67 | useEffect(() => { 68 | ajax(`/api/assignments/${assignmentId}`, "GET", user.jwt).then( 69 | (assignmentResponse) => { 70 | let assignmentData = assignmentResponse.assignment; 71 | if (assignmentData.branch === null) assignmentData.branch = ""; 72 | if (assignmentData.githubUrl === null) assignmentData.githubUrl = ""; 73 | 74 | setAssignment(assignmentData); 75 | setAssignmentEnums(assignmentResponse.assignmentEnums); 76 | setAssignmentStatuses(assignmentResponse.statusEnums); 77 | } 78 | ); 79 | }, []); 80 | 81 | return ( 82 | <> 83 | 84 | 85 | 86 | 87 | {assignment && assignment.number ? ( 88 |

Assignment {assignment.number}

89 | ) : ( 90 | <> 91 | )} 92 | 93 | 94 | {assignment ? : <>} 95 | 96 |
97 | 98 | {errorMsg} 99 | 100 | 101 | {assignment ? ( 102 | <> 103 | {assignment.user ? ( 104 | 105 | 106 | Student Name: 107 | 108 | 109 | 114 | 115 | 116 | ) : ( 117 | <> 118 | )} 119 | 120 | 121 | GitHub URL: 122 | 123 | 124 | 126 | updateAssignment("githubUrl", e.target.value) 127 | } 128 | type="url" 129 | readOnly 130 | value={assignment.githubUrl} 131 | placeholder="https://github.com/username/repo-name" 132 | /> 133 | 134 | 135 | 136 | 137 | 138 | Branch: 139 | 140 | 141 | updateAssignment("branch", e.target.value)} 146 | value={assignment.branch} 147 | /> 148 | 149 | 150 | 151 | 152 | 153 | Video Review URL: 154 | 155 | 156 | 158 | updateAssignment("codeReviewVideoUrl", e.target.value) 159 | } 160 | type="url" 161 | value={assignment.codeReviewVideoUrl} 162 | placeholder="https://screencast-o-matic.com/something" 163 | /> 164 | 165 | 166 | 167 |
168 | {getButtonsByStatusAndRole( 169 | assignment.status, 170 | "code_reviewer" 171 | ).map((btn) => ( 172 | 179 | ))} 180 |
181 | 182 | 183 | 184 | ) : ( 185 | <> 186 | )} 187 |
188 | 189 | ); 190 | }; 191 | 192 | export default CodeReviewerAssignmentView; 193 | -------------------------------------------------------------------------------- /front-end/src/Comment/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useUser } from "../UserProvider"; 3 | import jwt_decode from "jwt-decode"; 4 | import dayjs from "dayjs"; 5 | import relativeTime from "dayjs/plugin/relativeTime"; 6 | 7 | const Comment = (props) => { 8 | const user = useUser(); 9 | const decodedJwt = jwt_decode(user.jwt); 10 | const { id, createdDate, createdBy, text } = props.commentData; 11 | const { emitEditComment, emitDeleteComment } = props; 12 | const [commentRelativeTime, setCommentRelativeTime] = useState(""); 13 | 14 | useEffect(() => { 15 | updateCommentRelativeTime(); 16 | }, [createdDate]); 17 | 18 | function updateCommentRelativeTime() { 19 | if (createdDate) { 20 | dayjs.extend(relativeTime); 21 | 22 | if (typeof createdDate === "string") 23 | setCommentRelativeTime(dayjs(createdDate).fromNow()); 24 | else { 25 | setCommentRelativeTime(createdDate.fromNow()); 26 | } 27 | } 28 | } 29 | 30 | return ( 31 | <> 32 |
33 |
34 |
{`${createdBy.name}`}
35 | {decodedJwt.sub === createdBy.username ? ( 36 | <> 37 |
emitEditComment(id)} 39 | style={{ cursor: "pointer", color: "blue" }} 40 | > 41 | edit 42 |
43 |
emitDeleteComment(id)} 45 | style={{ cursor: "pointer", color: "red" }} 46 | > 47 | delete 48 |
49 | 50 | ) : ( 51 | <> 52 | )} 53 |
54 |
{text}
55 |
56 | 57 |
60 | {commentRelativeTime ? `Posted ${commentRelativeTime}` : ""} 61 |
62 | 63 | ); 64 | }; 65 | 66 | export default Comment; 67 | -------------------------------------------------------------------------------- /front-end/src/CommentContainer/index.js: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import React, { useEffect, useState } from "react"; 3 | import { Button, Col, Row } from "react-bootstrap"; 4 | import Comment from "../Comment"; 5 | import ajax from "../Services/fetchService"; 6 | import { useUser } from "../UserProvider"; 7 | import { useInterval } from "../util/useInterval"; 8 | 9 | const CommentContainer = (props) => { 10 | const { assignmentId } = props; 11 | const user = useUser(); 12 | 13 | const emptyComment = { 14 | id: null, 15 | text: "", 16 | assignmentId: assignmentId != null ? parseInt(assignmentId) : null, 17 | user: user.jwt, 18 | createdDate: null, 19 | }; 20 | 21 | const [comment, setComment] = useState(emptyComment); 22 | const [comments, setComments] = useState([]); 23 | 24 | useInterval(() => { 25 | updateCommentTimeDisplay(); 26 | }, 1000 * 5); 27 | function updateCommentTimeDisplay() { 28 | const commentsCopy = [...comments]; 29 | commentsCopy.forEach( 30 | (comment) => (comment.createdDate = dayjs(comment.createdDate)) 31 | ); 32 | formatComments(commentsCopy); 33 | } 34 | 35 | function handleEditComment(commentId) { 36 | const i = comments.findIndex((comment) => comment.id === commentId); 37 | const commentCopy = { 38 | id: comments[i].id, 39 | text: comments[i].text, 40 | assignmentId: assignmentId != null ? parseInt(assignmentId) : null, 41 | user: user.jwt, 42 | createdDate: comments[i].createdDate, 43 | }; 44 | setComment(commentCopy); 45 | } 46 | 47 | function handleDeleteComment(commentId) { 48 | // TODO: send DELETE request to server 49 | ajax(`/api/comments/${commentId}`, "delete", user.jwt).then((msg) => { 50 | const commentsCopy = [...comments]; 51 | const i = commentsCopy.findIndex((comment) => comment.id === commentId); 52 | commentsCopy.splice(i, 1); 53 | formatComments(commentsCopy); 54 | }); 55 | } 56 | function formatComments(commentsCopy) { 57 | commentsCopy.forEach((comment) => { 58 | if (typeof comment.createDate === "string") { 59 | comment.createDate = dayjs(comment.createDate); 60 | } 61 | }); 62 | setComments(commentsCopy); 63 | } 64 | 65 | useEffect(() => { 66 | ajax( 67 | `/api/comments?assignmentId=${assignmentId}`, 68 | "get", 69 | user.jwt, 70 | null 71 | ).then((commentsData) => { 72 | formatComments(commentsData); 73 | }); 74 | }, []); 75 | 76 | function updateComment(value) { 77 | const commentCopy = { ...comment }; 78 | commentCopy.text = value; 79 | setComment(commentCopy); 80 | } 81 | function submitComment() { 82 | // if ( 83 | // typeof comment.createdDate === "object" && 84 | // comment.createdDate != null 85 | // ) { 86 | // comment.createdDate = comment.createdDate.toDate(); 87 | // } 88 | if (comment.id) { 89 | ajax(`/api/comments/${comment.id}`, "put", user.jwt, comment).then( 90 | (d) => { 91 | const commentsCopy = [...comments]; 92 | const i = commentsCopy.findIndex((comment) => comment.id === d.id); 93 | commentsCopy[i] = d; 94 | formatComments(commentsCopy); 95 | 96 | setComment(emptyComment); 97 | } 98 | ); 99 | } else { 100 | ajax("/api/comments", "post", user.jwt, comment).then((d) => { 101 | const commentsCopy = [...comments]; 102 | commentsCopy.push(d); 103 | formatComments(commentsCopy); 104 | setComment(emptyComment); 105 | }); 106 | } 107 | } 108 | return ( 109 | <> 110 |
111 |

Comments

112 |
113 | 114 | 115 | 120 | 121 | 122 | 123 |
124 | {comments.map((comment) => ( 125 | 131 | ))} 132 |
133 | 134 | ); 135 | }; 136 | 137 | export default CommentContainer; 138 | -------------------------------------------------------------------------------- /front-end/src/Dashboard/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Button, Card, Col, Row } from "react-bootstrap"; 3 | import { useNavigate } from "react-router-dom"; 4 | import NavBar from "../NavBar"; 5 | import ajax from "../Services/fetchService"; 6 | import StatusBadge from "../StatusBadge"; 7 | import { useUser } from "../UserProvider"; 8 | import MultiColorProgressBar from "../MultiColorProgressBar"; 9 | import jwt_decode from "jwt-decode"; 10 | import { getDueDates } from "../Services/assignmentDueDatesService"; 11 | 12 | const Dashboard = () => { 13 | const navigate = useNavigate(); 14 | const user = useUser(); 15 | const [assignments, setAssignments] = useState(null); 16 | const [userData, setUserData] = useState(null); 17 | const [asignmentDueDates, setAssignmentDueDates] = useState(null); 18 | 19 | useEffect(() => { 20 | const decodedJwt = jwt_decode(user.jwt); 21 | if (!userData && assignments) { 22 | ajax("api/users/" + decodedJwt.sub, "GET", user.jwt).then((data) => { 23 | setUserData(data); 24 | let dueDates = getDueDates( 25 | data.cohortStartDate, 26 | data.bootcampDurationInWeeks, 27 | assignments 28 | ); 29 | 30 | setAssignmentDueDates(dueDates); 31 | }); 32 | } 33 | }, [user, userData, assignments]); 34 | 35 | useEffect(() => { 36 | ajax("api/assignments", "GET", user.jwt).then((assignmentsData) => { 37 | setAssignments(assignmentsData); 38 | }); 39 | if (!user.jwt) { 40 | console.warn("No valid jwt found, redirecting to login page"); 41 | navigate("/login"); 42 | } 43 | }, [user.jwt]); 44 | 45 | function createAssignment() { 46 | ajax("api/assignments", "POST", user.jwt).then((assignment) => { 47 | navigate(`/assignments/${assignment.id}`); 48 | // window.location.href = `/assignments/${assignment.id}`; 49 | }); 50 | } 51 | return ( 52 | <> 53 | 54 |
55 | {asignmentDueDates ? ( 56 | 57 | ) : ( 58 | <> 59 | )} 60 | 61 |
62 |
66 | {assignments && 67 | assignments.map((assignment) => ( 68 | // 69 | 73 | 74 | Assignment #{assignment.number} 75 |
76 | 77 |
78 | 79 | 80 | GitHub URL: {assignment.githubUrl} 81 |
82 | Branch: {assignment.branch} 83 |
84 | 85 | {assignment && assignment.status === "Completed" ? ( 86 | <> 87 | 95 | 103 | 104 | ) : ( 105 | 113 | )} 114 |
115 |
116 | // 117 | ))} 118 | 119 | 120 | 123 | 124 | 125 |
126 |
127 | 128 | ); 129 | }; 130 | 131 | export default Dashboard; 132 | -------------------------------------------------------------------------------- /front-end/src/Homepage/homepage.css: -------------------------------------------------------------------------------- 1 | .main-container{ 2 | height: 100vh; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | .wrapper{ 7 | display:flex; 8 | flex-direction: column; 9 | flex-grow: 1; 10 | background: linear-gradient(to bottom, #8ad2d4 50% , #243f93 50%); 11 | } 12 | .top-half{ 13 | padding-top: 3rem; 14 | display:inline-block; 15 | height:50%; 16 | } 17 | .welcome { 18 | color: #243f93; 19 | } 20 | .bottom-half{ 21 | display:inline-block; 22 | height: 50%; 23 | margin-top: 3rem; 24 | } 25 | .bottom-container{ 26 | display: flex; 27 | justify-content: space-between; 28 | } 29 | .welcome-text{ 30 | color: white; 31 | max-width: 500px; 32 | } 33 | .assignment{ 34 | max-width: 20em; 35 | margin-right: 20rem; 36 | } 37 | -------------------------------------------------------------------------------- /front-end/src/Homepage/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Container } from "react-bootstrap"; 3 | import NavBar from "../NavBar"; 4 | import assignment from "../Images/coders-campus-assignment-example.png"; 5 | 6 | import './homepage.css'; 7 | 8 | const Homepage = () => { 9 | 10 | return ( 11 |
12 | 13 |
14 |
15 | 16 |

Welcome Fellow Coders

17 |

18 | Coders Campus Assignment Submission Form is a tool to make turning in assignments more convenient. 19 | Students can insert Github links of their code for each individual assignment so a reviewer can clone code. 20 |

21 |
22 |
23 |
24 | 25 |

With these tools students can get personalized video feedback from coding experts.

26 | assignment-example 27 |
28 |
29 |
30 |
31 | ); 32 | }; 33 | 34 | export default Homepage; 35 | -------------------------------------------------------------------------------- /front-end/src/Images/coders-campus-assignment-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tp02ga/AssignmentSubmissionApp/d76607ed82e5a43672c9ddb8af8947a4b21fa1bb/front-end/src/Images/coders-campus-assignment-example.png -------------------------------------------------------------------------------- /front-end/src/Images/coders-campus-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tp02ga/AssignmentSubmissionApp/d76607ed82e5a43672c9ddb8af8947a4b21fa1bb/front-end/src/Images/coders-campus-logo-dark.png -------------------------------------------------------------------------------- /front-end/src/Images/coders-campus-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tp02ga/AssignmentSubmissionApp/d76607ed82e5a43672c9ddb8af8947a4b21fa1bb/front-end/src/Images/coders-campus-logo.png -------------------------------------------------------------------------------- /front-end/src/InstructorDashboard/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import NavBar from "../NavBar"; 3 | import { Col, Container, Row } from "react-bootstrap"; 4 | import ajax from "../Services/fetchService"; 5 | import { useUser } from "../UserProvider"; 6 | import { numAssignmentsThatShouldBeCompleted } from "../Services/assignmentDueDatesService"; 7 | import dayjs from "dayjs"; 8 | import "./instructor-dashboard.css"; 9 | import InstructorStudentEditModal from "../InstructorStudentEditModal"; 10 | 11 | const InstructorDashboard = () => { 12 | const user = useUser(); 13 | const [userAssignments, setUserAssignments] = useState(null); 14 | const [nonConfiguredUsers, setNonConfiguredUsers] = useState(null); 15 | const [bootcampStudents, setBootcampStudents] = useState(null); 16 | const [studentEditModal, setStudentEditModal] = useState(<>); 17 | 18 | const handleCloseStudentEdit = () => { 19 | setStudentEditModal(<>); 20 | }; 21 | 22 | const handleSaveStudent = (data) => { 23 | ajax(`/api/users/${data.email}`, "put", user.jwt, data).then(() => { 24 | handleCloseStudentEdit(); 25 | }); 26 | }; 27 | 28 | useEffect(() => { 29 | ajax("/api/assignments/all", "get", user.jwt).then((data) => { 30 | setUserAssignments(data); 31 | }); 32 | 33 | ajax("/api/users/bootcamp-students", "get", user.jwt).then((data) => { 34 | setBootcampStudents(data); 35 | }); 36 | }, []); 37 | 38 | useEffect(() => { 39 | if (userAssignments && bootcampStudents) { 40 | const nonConfigured = bootcampStudents.filter((student) => { 41 | let found = false; 42 | Object.entries(userAssignments).forEach((entry) => { 43 | const key = JSON.parse(entry[0]); 44 | if (key.email === student.email) { 45 | found = true; 46 | } 47 | }); 48 | return !found; 49 | }); 50 | setNonConfiguredUsers(nonConfigured); 51 | } 52 | }, [userAssignments, bootcampStudents]); 53 | 54 | const getColor = (delta) => { 55 | if (delta <= -1) { 56 | return "#b2e0b2"; // green 57 | } else if (delta === 1) { 58 | return "yellow"; // yellow 59 | } else if (delta === 2) { 60 | return "orange"; // orange 61 | } else if (delta === 3) { 62 | return "#f65555"; // red 63 | } else if (delta >= 4) { 64 | return "#c70000"; 65 | } 66 | }; 67 | return ( 68 | <> 69 | 70 | 71 | {studentEditModal} 72 | 73 | 74 |
Instructor Dashboard
75 | 76 |
77 | {userAssignments && nonConfiguredUsers ? ( 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | {nonConfiguredUsers 90 | .filter((student) => { 91 | return student.bootcampDurationInWeeks && student.startDate; 92 | }) 93 | .map((student) => { 94 | let numAssignmentsToBeDone = 95 | numAssignmentsThatShouldBeCompleted( 96 | student.startDate, 97 | student.bootcampDurationInWeeks 98 | ); 99 | let numAssignmentsDone = 0; 100 | return ( 101 | 102 | 107 | 116 | 117 | 118 | 119 | 120 | ); 121 | })} 122 | {Object.entries(userAssignments).map((entry) => { 123 | const [keyData, value] = entry; 124 | let key = JSON.parse(keyData); 125 | let numAssignmentsToBeDone = 126 | numAssignmentsThatShouldBeCompleted( 127 | key.startDate, 128 | key.bootcampDurationInWeeks 129 | ); 130 | let numAssignmentsDone = value.length; 131 | return ( 132 | 133 | 136 | 145 | 146 | 147 | 148 | 149 | ); 150 | })} 151 | 152 |
CohortNameEmailAssignments SubmittedAssignments Expected
103 | {dayjs(student.startDate, "YYYY-M-D").format( 104 | "MMM YYYY" 105 | )} 106 | 114 | {student.name} 115 | {student.email}{numAssignmentsDone}{numAssignmentsToBeDone}
134 | {dayjs(key.startDate, "YYYY-M-D").format("MMM YYYY")} 135 | 143 | {key.name} 144 | {key.email}{numAssignmentsDone}{numAssignmentsToBeDone}
153 | ) : ( 154 | <> 155 | )} 156 | 157 | {nonConfiguredUsers ? ( 158 | <> 159 |

Non Configured Users

160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | {nonConfiguredUsers 170 | .filter((u) => !u.bootcampDurationInWeeks || !u.startDate) 171 | .map((u) => ( 172 | 173 | 174 | 175 | 191 | 192 | ))} 193 | 194 |
NameEmail
{u.name}{u.email} 176 |
{ 179 | setStudentEditModal( 180 | 185 | ); 186 | }} 187 | > 188 | configure 189 |
190 |
195 | 196 | ) : ( 197 | <> 198 | )} 199 |
200 | 201 | ); 202 | }; 203 | 204 | export default InstructorDashboard; 205 | -------------------------------------------------------------------------------- /front-end/src/InstructorDashboard/instructor-dashboard.css: -------------------------------------------------------------------------------- 1 | td, 2 | th { 3 | padding: 3px 15px; 4 | } 5 | tr:nth-child(even) { 6 | background: #eee; 7 | } 8 | tr:nth-child(odd) { 9 | background: #fff; 10 | } 11 | -------------------------------------------------------------------------------- /front-end/src/InstructorStudentEditModal/index.js: -------------------------------------------------------------------------------- 1 | import { Form, Button, Modal } from "react-bootstrap"; 2 | import React, { useEffect, useState } from "react"; 3 | import { useUser } from "../UserProvider"; 4 | import ajax from "../Services/fetchService"; 5 | 6 | const InstructorStudentEditModal = (props) => { 7 | const { emitClose, emitSave, studentEmail } = props; 8 | const user = useUser(); 9 | const [student, setStudent] = useState(null); 10 | useEffect(() => { 11 | ajax(`/api/users/${studentEmail}`, "get", user.jwt).then((data) => { 12 | if (!data) { 13 | let yesNo = window.confirm( 14 | "User doesn't exist in the Assignment Submission app, do you want to create the user?" 15 | ); 16 | if (yesNo) { 17 | ajax(`/api/users/${studentEmail}`, "put", user.jwt, { 18 | email: studentEmail, 19 | }).then((data) => { 20 | emitClose(); 21 | }); 22 | } else { 23 | emitClose(); 24 | } 25 | } 26 | data.email = data.username; 27 | setStudent(data); 28 | }); 29 | }, []); 30 | 31 | const updateStudentData = (field, value) => { 32 | const studentCopy = { ...student }; 33 | studentCopy[field] = value; 34 | setStudent(studentCopy); 35 | }; 36 | return ( 37 | <> 38 | 39 | 40 | Edit Student Info 41 | 42 | {student ? ( 43 | <> 44 | 45 |
46 | 47 | Name 48 | updateStudentData("name", e.target.value)} 52 | > 53 | 54 | 55 | Email 56 | 61 | 62 | 63 | Start Date 64 | 68 | updateStudentData("startDate", e.target.value) 69 | } 70 | > 71 | 72 | 73 | Bootcamp Duration (Weeks) 74 | 78 | updateStudentData( 79 | "bootcampDurationInWeeks", 80 | e.target.value 81 | ) 82 | } 83 | > 84 | 85 |
86 |
87 | 88 | 91 | 106 | 107 | 108 | ) : ( 109 |
Loading...
110 | )} 111 |
112 | 113 | ); 114 | }; 115 | 116 | export default InstructorStudentEditModal; 117 | -------------------------------------------------------------------------------- /front-end/src/Login/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button, Col, Container, Row, Form } from "react-bootstrap"; 3 | import { useNavigate } from "react-router-dom"; 4 | import NavBar from "../NavBar"; 5 | import { useUser } from "../UserProvider"; 6 | 7 | const Login = () => { 8 | const user = useUser(); 9 | const navigate = useNavigate(); 10 | const [username, setUsername] = useState(""); 11 | const [password, setPassword] = useState(""); 12 | const [errorMsg, setErrorMsg] = useState(null); 13 | 14 | // useEffect(() => { 15 | // if (user.jwt) navigate("/dashboard"); 16 | // }, [user]); 17 | 18 | function sendLoginRequest() { 19 | setErrorMsg(""); 20 | const reqBody = { 21 | username: username, 22 | password: password, 23 | }; 24 | 25 | fetch("api/auth/login", { 26 | headers: { 27 | "Content-Type": "application/json", 28 | }, 29 | method: "post", 30 | body: JSON.stringify(reqBody), 31 | }) 32 | .then((response) => { 33 | if (response.status === 200) return response.text(); 34 | else if (response.status === 401 || response.status === 403) { 35 | setErrorMsg("Invalid username or password"); 36 | } else { 37 | setErrorMsg( 38 | "Something went wrong, try again later or reach out to trevor@coderscampus.com" 39 | ); 40 | } 41 | }) 42 | .then((data) => { 43 | if (data) { 44 | user.setJwt(data); 45 | navigate("/dashboard"); 46 | } 47 | }); 48 | } 49 | return ( 50 | <> 51 | 52 | 53 | 54 | 55 | 56 | Username 57 | setUsername(e.target.value)} 63 | /> 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | Password 72 | setPassword(e.target.value)} 78 | /> 79 | 80 | 81 | 82 | {errorMsg ? ( 83 | 84 | 85 |
86 | {errorMsg} 87 |
88 | 89 |
90 | ) : ( 91 | <> 92 | )} 93 | 94 | 99 | 107 | 117 | 118 | 119 |
120 | 121 | ); 122 | }; 123 | 124 | export default Login; 125 | -------------------------------------------------------------------------------- /front-end/src/MultiColorProgressBar/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./multicolor-progress-bar.css"; 3 | 4 | class MultiColorProgressBar extends React.Component { 5 | render() { 6 | const parent = this.props; 7 | 8 | let values = 9 | parent.readings && 10 | parent.readings.length && 11 | parent.readings.map(function (item, i) { 12 | if (item.value > 0) { 13 | return ( 14 |
19 |
{item.name}
20 |
{item.dueDate}
21 |
22 | ); 23 | } 24 | }, this); 25 | 26 | let calibrations = 27 | parent.readings && 28 | parent.readings.length && 29 | parent.readings.map(function (item, i) { 30 | if (item.value > 0) { 31 | return ( 32 |
37 | | 38 |
39 | ); 40 | } 41 | }, this); 42 | 43 | let bars = 44 | parent.readings && 45 | parent.readings.length && 46 | parent.readings.map(function (item, i) { 47 | if (item.value > 0) { 48 | return ( 49 |
54 | ); 55 | } 56 | }, this); 57 | 58 | return ( 59 |
60 |
{values == "" ? "" : values}
61 |
{calibrations == "" ? "" : calibrations}
62 |
{bars == "" ? "" : bars}
63 |
64 | ); 65 | } 66 | } 67 | 68 | export default MultiColorProgressBar; 69 | -------------------------------------------------------------------------------- /front-end/src/MultiColorProgressBar/multicolor-progress-bar.css: -------------------------------------------------------------------------------- 1 | .multicolor-bar { 2 | margin: 20px 20%; 3 | } 4 | 5 | .multicolor-bar .values .value { 6 | float: left; 7 | text-align: center; 8 | } 9 | 10 | .multicolor-bar .scale .graduation { 11 | float: left; 12 | text-align: center; 13 | } 14 | 15 | .multicolor-bar .bars .bar { 16 | float: left; 17 | height: 10px; 18 | } 19 | 20 | .multicolor-bar .bars div.bar:first-of-type { 21 | border-top-left-radius: 5px; 22 | border-bottom-left-radius: 5px; 23 | } 24 | 25 | .multicolor-bar .bars div.bar:last-of-type { 26 | border-top-right-radius: 5px; 27 | border-bottom-right-radius: 5px; 28 | } 29 | 30 | .multicolor-bar .legends { 31 | text-align: center; 32 | } 33 | 34 | .multicolor-bar .legends .legend { 35 | display: inline-block; 36 | margin: 0 5px; 37 | text-align: center; 38 | } 39 | 40 | .multicolor-bar .legends .legend .dot { 41 | font-size: 25px; 42 | vertical-align: middle; 43 | } 44 | 45 | .multicolor-bar .legends .legend .label { 46 | margin-left: 2px; 47 | vertical-align: middle; 48 | } 49 | -------------------------------------------------------------------------------- /front-end/src/NavBar/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link, useNavigate, useLocation } from "react-router-dom"; 3 | import { Button, Image } from "react-bootstrap"; 4 | import logo from "../Images/coders-campus-logo.png"; 5 | import { useUser } from "../UserProvider"; 6 | import jwt_decode from "jwt-decode"; 7 | 8 | function NavBar() { 9 | const navigate = useNavigate(); 10 | const { pathname } = useLocation(); 11 | const user = useUser(); 12 | const [authorities, setAuthorities] = useState(null); 13 | 14 | useEffect(() => { 15 | if (user && user.jwt) { 16 | const decodedJwt = jwt_decode(user.jwt); 17 | setAuthorities(decodedJwt.authorities); 18 | } 19 | }, [user, user.jwt]); 20 | 21 | return ( 22 |
23 |
24 | 25 | logo 26 | 27 |
28 |
29 | {user && user.jwt ? ( 30 | { 33 | // TODO: have this delete cookie on server side 34 | fetch("/api/auth/logout").then((response) => { 35 | if (response.status === 200) { 36 | user.setJwt(null); 37 | navigate("/"); 38 | } 39 | }); 40 | }} 41 | > 42 | Logout 43 | 44 | ) : pathname !== "/login" ? ( 45 | 54 | ) : ( 55 | <> 56 | )} 57 | 58 | {authorities && 59 | authorities.filter((auth) => auth === "ROLE_INSTRUCTOR").length > 0 ? ( 60 | 64 | Instructors 65 | 66 | ) : ( 67 | <> 68 | )} 69 | 70 | {user && user.jwt ? ( 71 | 79 | ) : ( 80 | <> 81 | )} 82 |
83 |
84 | ); 85 | } 86 | 87 | export default NavBar; 88 | -------------------------------------------------------------------------------- /front-end/src/PrivateRoute/index.js: -------------------------------------------------------------------------------- 1 | import { Navigate } from "react-router-dom"; 2 | import { useUser } from "../UserProvider"; 3 | import { useState, useEffect } from "react"; 4 | import ajax from "../Services/fetchService"; 5 | 6 | const PrivateRoute = (props) => { 7 | const user = useUser(); 8 | const [isLoading, setIsLoading] = useState(true); 9 | const [isValid, setIsValid] = useState(null); 10 | const { children } = props; 11 | 12 | if (user && user.jwt) { 13 | ajax(`/api/auth/validate`, "get", user.jwt).then((isValid) => { 14 | setIsValid(isValid); 15 | setIsLoading(false); 16 | }); 17 | } else { 18 | return ; 19 | } 20 | 21 | return isLoading ? ( 22 |
Loading...
23 | ) : isValid === true ? ( 24 | children 25 | ) : ( 26 | 27 | ); 28 | }; 29 | 30 | export default PrivateRoute; 31 | -------------------------------------------------------------------------------- /front-end/src/Register/index.js: -------------------------------------------------------------------------------- 1 | import Cookies from "js-cookie"; 2 | import React, { useState, useEffect } from "react"; 3 | import { Button, Col, Container, Row, Form } from "react-bootstrap"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | import { useUser } from "../UserProvider"; 7 | 8 | const Register = () => { 9 | const user = useUser(); 10 | const navigate = useNavigate(); 11 | const [username, setUsername] = useState(""); 12 | const [password, setPassword] = useState(""); 13 | const [name, setName] = useState(""); 14 | 15 | useEffect(() => { 16 | if (user.jwt) navigate("/dashboard"); 17 | }, [user]); 18 | 19 | function createAndLoginUser() { 20 | const reqBody = { 21 | username: username, 22 | password: password, 23 | name: name, 24 | }; 25 | 26 | fetch("api/users/register", { 27 | headers: { 28 | "Content-Type": "application/json", 29 | }, 30 | method: "post", 31 | body: JSON.stringify(reqBody), 32 | }) 33 | .then((response) => { 34 | if (response.status === 200) 35 | return Promise.all([response.json(), response.headers]); 36 | else return Promise.reject("Invalid login attempt"); 37 | }) 38 | .then(([body, headers]) => { 39 | user.setJwt(Cookies.get("jwt")); 40 | }) 41 | .catch((message) => { 42 | alert(message); 43 | }); 44 | } 45 | 46 | return ( 47 |
48 | 49 | 50 | 51 | 52 | Full Name 53 | setName(e.target.value)} 61 | /> 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Username 70 | setUsername(e.target.value)} 76 | /> 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Password 85 | setPassword(e.target.value)} 91 | /> 92 | 93 | 94 | 95 | 96 | 101 | 111 | 121 | 122 | 123 | 124 |
125 | ); 126 | }; 127 | 128 | export default Register; 129 | -------------------------------------------------------------------------------- /front-end/src/Services/assignmentDueDatesService.js: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { isValidValue } from "./validate"; 3 | 4 | const nineMonthDueDatesInWeeks = [ 5 | 2, 7, 10, 16, 18, 20, 21, 22, 23, 24, 25, 27, 29, 32, 36, 6 | ]; 7 | const sixMonthDueDatesinWeeks = [ 8 | 3, 6, 7, 10, 11, 12, 14, 15, 16, 17, 18, 20, 22, 24, 26, 9 | ]; 10 | 11 | function getDueDates(sd, courseDurationInWeeks, assignments) { 12 | isValidValue(courseDurationInWeeks, [24, 36]); 13 | const startDate = dayjs(sd); 14 | 15 | if (courseDurationInWeeks === 36) { 16 | return nineMonthDueDatesInWeeks.map((weekDue, i) => { 17 | return { 18 | name: `#${i + 1}`, 19 | dueDate: startDate.add(weekDue, "week").format("MMM-DD"), 20 | value: 6.6, 21 | color: getColor(assignments, i + 1, startDate.add(weekDue, "week")), 22 | }; 23 | }); 24 | } else { 25 | return sixMonthDueDatesinWeeks.map((weekDue, i) => { 26 | return { 27 | name: `#${i + 1}`, 28 | dueDate: startDate.add(weekDue, "week").format("MMM-DD"), 29 | value: 6.6, 30 | color: getColor(assignments, i + 1, startDate.add(weekDue, "week")), 31 | }; 32 | }); 33 | } 34 | } 35 | 36 | const getNumDaysSinceLastSubmission = (assignments) => { 37 | const latestAssignment = assignments 38 | .sort((a1, a2) => { 39 | if (a2.submittedDate && a1.submittedDate) { 40 | return dayjs(a2.submittedDate).diff(dayjs(a1.submittedDate), "second"); 41 | } else if (a2.lastModified && a1.lastModified) { 42 | return new Date(a2.lastModified) - new Date(a1.lastModified); 43 | } else if (!a2.submittedDate && a1.submittedDate) { 44 | return -1; 45 | } else if (!a1.submittedDate && a2.submittedDate) { 46 | return 1; 47 | } 48 | return 0; 49 | }) 50 | .splice(0, 1); 51 | 52 | if (latestAssignment.length > 0 && latestAssignment[0].submittedDate) { 53 | const submittedDay = dayjs(latestAssignment[0].submittedDate); 54 | console.log("We have a submitted date: ", submittedDay); 55 | const dayDiff = dayjs().diff(submittedDay, "day"); 56 | console.log("Day diff: ", dayDiff); 57 | return dayDiff; 58 | } else if (latestAssignment.lastModified) { 59 | return dayjs().diff(dayjs(latestAssignment.lastModified), "day"); 60 | } else { 61 | return -1; 62 | } 63 | }; 64 | 65 | function numAssignmentsThatShouldBeCompleted(sd, courseDurationInWeeks) { 66 | const startDate = dayjs(sd); 67 | const now = dayjs(); 68 | const weekDiff = now.diff(startDate, "week"); 69 | let assignmentNumber = 15; 70 | 71 | if (courseDurationInWeeks === 36) { 72 | for (let i = 0; i < nineMonthDueDatesInWeeks.length; i++) { 73 | if (weekDiff < nineMonthDueDatesInWeeks[i]) { 74 | assignmentNumber = i + 1; 75 | break; 76 | } 77 | } 78 | return assignmentNumber; 79 | } else { 80 | for (let i = 0; i < sixMonthDueDatesinWeeks.length; i++) { 81 | if (weekDiff < sixMonthDueDatesinWeeks[i]) { 82 | assignmentNumber = i + 1; 83 | break; 84 | } 85 | } 86 | return assignmentNumber; 87 | } 88 | } 89 | 90 | function getColor(assignments, num, dueDate) { 91 | const assignment = assignments.filter((a) => a.number === num); 92 | const now = dayjs(); 93 | if (assignment.length > 0) { 94 | if (assignment[0].status === "Completed") return "green"; 95 | 96 | if (now.isAfter(dueDate)) { 97 | if (assignment[0].status === "Submitted") { 98 | return "rgb(255, 193, 7)"; 99 | } else if (assignment[0].status === "Needs Update") { 100 | return "orange"; 101 | } else { 102 | return "rgb(220, 53, 69)"; 103 | } 104 | } else return "grey"; 105 | } else { 106 | if (now.isAfter(dueDate)) return "rgb(220, 53, 69)"; 107 | else return "grey"; 108 | } 109 | } 110 | export { 111 | getDueDates, 112 | numAssignmentsThatShouldBeCompleted, 113 | getNumDaysSinceLastSubmission, 114 | }; 115 | -------------------------------------------------------------------------------- /front-end/src/Services/fetchService.js: -------------------------------------------------------------------------------- 1 | function ajax(url, requestMethod, jwt, requestBody) { 2 | const fetchData = { 3 | headers: { 4 | "Content-Type": "application/json", 5 | }, 6 | method: requestMethod, 7 | }; 8 | 9 | if (jwt) { 10 | fetchData.headers.Authorization = `Bearer ${jwt}`; 11 | } 12 | 13 | if (requestBody) { 14 | fetchData.body = JSON.stringify(requestBody); 15 | } 16 | 17 | return fetch(url, fetchData).then((response) => { 18 | if (response.status === 200) { 19 | const contentType = response.headers.get("content-type"); 20 | if (contentType && contentType.indexOf("application/json") !== -1) { 21 | return response.json(); 22 | } else { 23 | return response.text(); 24 | } 25 | } 26 | }); 27 | } 28 | 29 | export default ajax; 30 | -------------------------------------------------------------------------------- /front-end/src/Services/statusService.js: -------------------------------------------------------------------------------- 1 | function getButtonsByStatusAndRole(currentStatus, role) { 2 | let buttons = []; 3 | if (currentStatus === "Pending Submission" && role === "student") { 4 | buttons.push(getButton("Save")); 5 | buttons.push(getButton("Submit")); 6 | } else if (currentStatus === "Submitted" && role === "student") { 7 | buttons.push(getButton("Un-submit")); 8 | } else if (currentStatus === "Needs Update" && role === "student") { 9 | buttons.push(getButton("Resubmit Assignment")); 10 | } else if (currentStatus === "In Review" && role === "code_reviewer") { 11 | buttons.push(getButton("Complete Review")); 12 | buttons.push(getButton("Reject Assignment")); 13 | } else if (currentStatus === "Completed" && role === "code_reviewer") { 14 | buttons.push(getButton("Re-claim")); 15 | } else if (currentStatus === "Needs Update" && role === "code_reviewer") { 16 | buttons.push(getButton("Re-claim")); 17 | } else if (currentStatus === "Resubmitted" && role === "student") { 18 | buttons.push(getButton("Un-submit")); 19 | } 20 | 21 | return buttons; 22 | } 23 | 24 | function getButton(type, currentStatus) { 25 | let button = {}; 26 | button.text = type; 27 | switch (type) { 28 | case "Save": 29 | button.variant = "secondary"; 30 | button.nextStatus = "Same"; 31 | break; 32 | case "Submit": 33 | button.variant = "primary"; 34 | button.nextStatus = "Submitted"; 35 | break; 36 | case "Un-submit": 37 | button.variant = "secondary"; 38 | button.nextStatus = "Pending Submission"; 39 | break; 40 | case "Resubmit Assignment": 41 | button.variant = "primary"; 42 | button.nextStatus = "Resubmitted"; 43 | break; 44 | case "Complete Review": 45 | button.variant = "primary"; 46 | button.nextStatus = "Completed"; 47 | break; 48 | case "Reject Assignment": 49 | button.variant = "danger"; 50 | button.nextStatus = "Needs Update"; 51 | break; 52 | case "Re-claim": 53 | if (currentStatus === "Submitted") { 54 | button.nextStatus = "Pending Submission"; 55 | } else if (currentStatus === "Needs Update" || "Completed") { 56 | button.nextStatus = "In Review"; 57 | } 58 | button.variant = "secondary"; 59 | break; 60 | default: 61 | button.variant = "info"; 62 | break; 63 | } 64 | return button; 65 | } 66 | 67 | export { getButtonsByStatusAndRole }; 68 | -------------------------------------------------------------------------------- /front-end/src/Services/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Is the `input` value inside the set of `validValues`? 3 | * @param {*} input the input value that we're trying to validate 4 | * @param {*} validValues an array of valid values for the input 5 | */ 6 | function isValidValue(input, validValues) { 7 | const valid = 8 | validValues.filter((validValue) => validValue == input).length > 0; 9 | if (!valid) { 10 | throw new Error( 11 | `Input value ${input} not in set of valid values ${validValues}` 12 | ); 13 | } 14 | } 15 | 16 | export { isValidValue }; 17 | -------------------------------------------------------------------------------- /front-end/src/StatusBadge/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Badge } from "react-bootstrap"; 3 | 4 | const StatusBadge = (props) => { 5 | const { text } = props; 6 | function getColorOfBadge() { 7 | if (text === "Completed") return "success"; 8 | else if (text === "Needs Update") return "danger"; 9 | else if (text === "Pending Submission") return "warning"; 10 | else if (text === "Resubmitted") return "primary"; 11 | else return "info"; 12 | } 13 | return ( 14 | 21 | {text} 22 | 23 | ); 24 | }; 25 | 26 | export default StatusBadge; 27 | -------------------------------------------------------------------------------- /front-end/src/UserProvider/index.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useState } from "react"; 2 | import Cookies from "js-cookie"; 3 | const UserContext = createContext(); 4 | 5 | const UserProvider = ({ children }) => { 6 | const [jwt, setJwt] = useState(Cookies.get("jwt")); 7 | 8 | const value = { jwt, setJwt }; 9 | return {children}; 10 | }; 11 | 12 | function useUser() { 13 | const context = useContext(UserContext); 14 | if (context === undefined) { 15 | throw new Error("useUser must be used within a UserProvider"); 16 | } 17 | 18 | return context; 19 | } 20 | 21 | export { useUser, UserProvider }; 22 | -------------------------------------------------------------------------------- /front-end/src/custom.scss: -------------------------------------------------------------------------------- 1 | $theme-colors: ( 2 | "primary": #233f93, 3 | "secondary": #6c757d, 4 | "success": #28a745, 5 | "danger": #dc3545, 6 | "warning": #ffc107, 7 | "info": #8ad2d4, 8 | "light": #f8f9fa, 9 | "dark": #343a40, 10 | ); 11 | // Import Bootstrap and its default variables 12 | @import "~bootstrap/scss/bootstrap.scss"; 13 | 14 | a { 15 | text-decoration: none !important; 16 | } 17 | -------------------------------------------------------------------------------- /front-end/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /front-end/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | import { BrowserRouter } from "react-router-dom"; 7 | import { UserProvider } from "./UserProvider"; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | 13 | 14 | 15 | 16 | , 17 | document.getElementById("root") 18 | ); 19 | 20 | // If you want to start measuring performance in your app, pass a function 21 | // to log results (for example: reportWebVitals(console.log)) 22 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 23 | reportWebVitals(); 24 | -------------------------------------------------------------------------------- /front-end/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /front-end/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /front-end/src/util/useInterval.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | function useInterval(callback, delay) { 4 | const savedCallback = useRef(); 5 | 6 | // Remember the latest callback. 7 | useEffect(() => { 8 | savedCallback.current = callback; 9 | }, [callback]); 10 | 11 | // Set up the interval. 12 | useEffect(() => { 13 | function tick() { 14 | savedCallback.current(); 15 | } 16 | if (delay !== null) { 17 | let id = setInterval(tick, delay); 18 | return () => clearInterval(id); 19 | } 20 | }, [delay]); 21 | } 22 | 23 | export { useInterval }; 24 | -------------------------------------------------------------------------------- /front-end/src/util/useLocalStorage.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | function useLocalState(defaultValue, key) { 4 | const [value, setValue] = useState(() => { 5 | const localStorageValue = localStorage.getItem(key); 6 | 7 | return localStorageValue !== null 8 | ? JSON.parse(localStorageValue) 9 | : defaultValue; 10 | }); 11 | 12 | useEffect(() => { 13 | localStorage.setItem(key, JSON.stringify(value)); 14 | }, [key, value]); 15 | 16 | return [value, setValue]; 17 | } 18 | 19 | export { useLocalState }; 20 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AssignmentSubmissionApp", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | --------------------------------------------------------------------------------