├── init.sql ├── api ├── .gitattributes ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── api │ │ │ │ ├── dtos │ │ │ │ ├── request │ │ │ │ │ ├── CriarCursoRequestDto.java │ │ │ │ │ └── InscricaoRequestDto.java │ │ │ │ ├── response │ │ │ │ │ ├── CriarCursoResponseDto.java │ │ │ │ │ ├── InscricaoResponseDto.java │ │ │ │ │ ├── CursoResponseDto.java │ │ │ │ │ └── PessoaResponseDto.java │ │ │ │ └── PessoaDto.java │ │ │ │ ├── repositories │ │ │ │ ├── CursoRepository.java │ │ │ │ ├── PessoaRepository.java │ │ │ │ └── InscricaoRepository.java │ │ │ │ ├── models │ │ │ │ ├── InscricaoId.java │ │ │ │ ├── Inscricao.java │ │ │ │ ├── Curso.java │ │ │ │ └── Pessoa.java │ │ │ │ ├── ApiApplication.java │ │ │ │ ├── exceptions │ │ │ │ └── ConsumerErrorHandler.java │ │ │ │ ├── consumers │ │ │ │ └── PessoaConsumer.java │ │ │ │ ├── configs │ │ │ │ ├── WebSocketConfig.java │ │ │ │ ├── RabbitMQConfig.java │ │ │ │ └── SecurityConfig.java │ │ │ │ ├── controllers │ │ │ │ ├── CursoController.java │ │ │ │ └── InscricaoController.java │ │ │ │ └── services │ │ │ │ ├── InscricaoService.java │ │ │ │ └── CursoService.java │ │ └── resources │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── api │ │ └── ApiApplicationTests.java ├── .gitignore ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── pom.xml ├── mvnw.cmd └── mvnw ├── backend ├── .gitattributes ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── backend │ │ │ │ ├── dtos │ │ │ │ ├── request │ │ │ │ │ ├── LoginRequestDto.java │ │ │ │ │ └── CriarPessoaRequestDto.java │ │ │ │ ├── response │ │ │ │ │ ├── CriarPessoaResponseDto.java │ │ │ │ │ └── LoginResponseDto.java │ │ │ │ └── PessoaDto.java │ │ │ │ ├── repositories │ │ │ │ └── PessoaRepository.java │ │ │ │ ├── BackendApplication.java │ │ │ │ ├── configs │ │ │ │ ├── RabbitMQConfig.java │ │ │ │ ├── AdminUserConfig.java │ │ │ │ └── SecurityConfig.java │ │ │ │ ├── models │ │ │ │ └── Pessoa.java │ │ │ │ ├── controllers │ │ │ │ └── PessoaController.java │ │ │ │ ├── producers │ │ │ │ └── PessoaProducer.java │ │ │ │ └── services │ │ │ │ └── PessoaService.java │ │ └── resources │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── backend │ │ └── BackendApplicationTests.java ├── .gitignore ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── pom.xml ├── mvnw.cmd └── mvnw ├── frontend ├── .eslintrc.json ├── postcss.config.js ├── src │ ├── app │ │ ├── page.tsx │ │ ├── cursos │ │ │ ├── components │ │ │ │ ├── Header.tsx │ │ │ │ └── CourseCard.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── globals.css │ │ ├── login │ │ │ └── page.tsx │ │ └── cadastro │ │ │ └── page.tsx │ ├── lib │ │ └── utils.ts │ ├── context │ │ └── AuthContext │ │ │ └── index.tsx │ ├── hooks │ │ └── useAuth │ │ │ └── index.tsx │ ├── components │ │ └── ui │ │ │ ├── input.tsx │ │ │ ├── button.tsx │ │ │ └── card.tsx │ ├── middleware.ts │ └── services │ │ ├── api.ts │ │ └── authService │ │ └── index.ts ├── next.config.js ├── components.json ├── .gitignore ├── tsconfig.json ├── tailwind.config.ts └── package.json ├── .DS_Store ├── docker-compose.yml └── README.md /init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE backend; 2 | CREATE DATABASE api; -------------------------------------------------------------------------------- /api/.gitattributes: -------------------------------------------------------------------------------- 1 | /mvnw text eol=lf 2 | *.cmd text eol=crlf 3 | -------------------------------------------------------------------------------- /backend/.gitattributes: -------------------------------------------------------------------------------- 1 | /mvnw text eol=lf 2 | *.cmd text eol=crlf 3 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davifariasp/plataforma-cursos/HEAD/.DS_Store -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation'; 2 | 3 | export default function Home() { 4 | redirect('/login'); 5 | } -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/dtos/request/CriarCursoRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.example.api.dtos.request; 2 | 3 | public record CriarCursoRequestDto( 4 | String nome, 5 | int numeroVagas 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/dtos/request/LoginRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.dtos.request; 2 | 3 | public record LoginRequestDto( 4 | String login, 5 | String senha 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/dtos/response/CriarCursoResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.example.api.dtos.response; 2 | 3 | import java.util.UUID; 4 | 5 | public record CriarCursoResponseDto( 6 | UUID idCurso, 7 | String mensagem){ 8 | } 9 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/dtos/request/InscricaoRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.example.api.dtos.request; 2 | 3 | import java.util.UUID; 4 | 5 | public record InscricaoRequestDto( 6 | UUID idCurso, 7 | String cpf 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: 'standalone', 4 | eslint: { 5 | ignoreDuringBuilds: true, 6 | }, 7 | images: { unoptimized: true }, 8 | }; 9 | 10 | module.exports = nextConfig; 11 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/dtos/response/CriarPessoaResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.dtos.response; 2 | 3 | import java.util.UUID; 4 | 5 | public record CriarPessoaResponseDto( 6 | UUID id, 7 | String mensagem 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/dtos/response/InscricaoResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.example.api.dtos.response; 2 | 3 | import com.example.api.models.InscricaoId; 4 | 5 | 6 | public record InscricaoResponseDto ( 7 | InscricaoId insricaoId, 8 | String mensagem 9 | ) { 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/dtos/response/LoginResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.dtos.response; 2 | 3 | import java.util.Map; 4 | 5 | public record LoginResponseDto( 6 | Map user, 7 | String accessToken, 8 | Long expiresIn 9 | ) { 10 | } 11 | -------------------------------------------------------------------------------- /api/src/test/java/com/example/api/ApiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApiApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/dtos/response/CursoResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.example.api.dtos.response; 2 | 3 | import java.util.UUID; 4 | 5 | public record CursoResponseDto( 6 | UUID idCurso, 7 | String nomeCurso, 8 | int numeroVagas, 9 | int numeroInscricoes 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/repositories/CursoRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.api.repositories; 2 | 3 | import com.example.api.models.Curso; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.UUID; 7 | 8 | public interface CursoRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/test/java/com/example/backend/BackendApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.backend; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BackendApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/dtos/response/PessoaResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.example.api.dtos.response; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | public record PessoaResponseDto( 7 | String nome, 8 | String email, 9 | String cpf, 10 | List inscricoes 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/dtos/request/CriarPessoaRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.dtos.request; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | import java.time.LocalDate; 6 | 7 | public record CriarPessoaRequestDto( 8 | @NotNull String nome, 9 | LocalDate nascimento, 10 | String cpf, 11 | @NotNull String email, 12 | @NotNull String senha 13 | ) { } 14 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/repositories/PessoaRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.api.repositories; 2 | 3 | import com.example.api.models.Pessoa; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public interface PessoaRepository extends JpaRepository { 10 | Optional findByCpf(String cpf); 11 | } 12 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/models/InscricaoId.java: -------------------------------------------------------------------------------- 1 | package com.example.api.models; 2 | 3 | 4 | import jakarta.persistence.Embeddable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import java.util.UUID; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Embeddable 14 | public class InscricaoId { 15 | private UUID idCurso; 16 | private UUID idPessoa; 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/dtos/PessoaDto.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.dtos; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.time.LocalDate; 7 | import java.util.UUID; 8 | 9 | @Data 10 | public class PessoaDto implements Serializable { 11 | private UUID id; 12 | private String nome; 13 | private LocalDate nascimento; 14 | private String cpf; 15 | private String email; 16 | } 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | container_name: pg-fiesc 4 | image: postgres 5 | restart: always 6 | environment: 7 | POSTGRES_PASSWORD: 'pgpass' 8 | volumes: 9 | - ./init.sql:/docker-entrypoint-initdb.d/init.sql 10 | ports: 11 | - 5432:5432 12 | 13 | rabbitmq: 14 | container_name: rabbit-fiesc 15 | image: rabbitmq:management 16 | restart: always 17 | ports: 18 | - 5672:5672 19 | - 15672:15672 -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /api/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=api 2 | server.port=8082 3 | 4 | jwt.public.key=classpath:app.pub 5 | jwt.private.key=classpath:app.key 6 | 7 | spring.datasource.url=jdbc:postgresql://localhost:5432/api 8 | spring.datasource.username=postgres 9 | spring.datasource.password=pgpass 10 | spring.jpa.hibernate.ddl-auto=update 11 | 12 | spring.rabbitmq.host = localhost 13 | spring.rabbitmq.port = 5672 14 | spring.rabbitmq.username = guest 15 | spring.rabbitmq.password = guest 16 | 17 | broker.queue.backend.name= default.pessoa -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/repositories/InscricaoRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.api.repositories; 2 | 3 | import com.example.api.models.Inscricao; 4 | import com.example.api.models.InscricaoId; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.UUID; 10 | 11 | public interface InscricaoRepository extends JpaRepository { 12 | 13 | List findAllByCursoId(UUID id); 14 | List findAllByPessoaCpf(String cpf); 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=backend 2 | server.port=8081 3 | 4 | jwt.public.key=classpath:app.pub 5 | jwt.private.key=classpath:app.key 6 | 7 | spring.datasource.url=jdbc:postgresql://localhost:5432/backend 8 | spring.datasource.username=postgres 9 | spring.datasource.password=pgpass 10 | spring.jpa.hibernate.ddl-auto=update 11 | 12 | spring.rabbitmq.host = localhost 13 | spring.rabbitmq.port = 5672 14 | spring.rabbitmq.username = guest 15 | spring.rabbitmq.password = guest 16 | 17 | broker.queue.backend.name= default.pessoa 18 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ## chaves jwy 8 | app.key 9 | app.pub 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 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ## chaves jwy 8 | app.key 9 | app.pub 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 | -------------------------------------------------------------------------------- /frontend/src/app/cursos/components/Header.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | 5 | interface HeaderProps { 6 | userName: string; 7 | onLogout: () => void; 8 | } 9 | 10 | export function Header({ userName, onLogout }: HeaderProps) { 11 | return ( 12 |
13 |

Cursos Disponíveis

14 |
15 | Olá, {userName} 16 | 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/repositories/PessoaRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.repositories; 2 | 3 | import com.example.backend.models.Pessoa; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | public interface PessoaRepository extends JpaRepository { 11 | 12 | @Query("SELECT p FROM Pessoa p WHERE p.email = :login OR p.cpf = :login") 13 | Pessoa findByLogin(String login); 14 | 15 | @Query("SELECT p FROM Pessoa p WHERE p.nome = 'admin' ") 16 | Optional findAdmin(); 17 | } 18 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/ApiApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 4 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SecurityScheme( 9 | name = "Authorization", 10 | type = SecuritySchemeType.HTTP, 11 | bearerFormat = "JWT", 12 | scheme = "bearer" 13 | ) 14 | @SpringBootApplication 15 | public class ApiApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(ApiApplication.class, args); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/BackendApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.backend; 2 | 3 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 4 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SecurityScheme( 9 | name = "Authorization", 10 | type = SecuritySchemeType.HTTP, 11 | bearerFormat = "JWT", 12 | scheme = "bearer" 13 | ) 14 | @SpringBootApplication 15 | public class BackendApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(BackendApplication.class, args); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/dtos/PessoaDto.java: -------------------------------------------------------------------------------- 1 | package com.example.api.dtos; 2 | 3 | import com.example.api.models.Pessoa; 4 | 5 | import java.time.LocalDate; 6 | import java.util.UUID; 7 | 8 | public record PessoaDto( 9 | UUID id, 10 | String nome, 11 | LocalDate nascimento, 12 | String cpf, 13 | String email 14 | ) { 15 | 16 | public Pessoa toEntity() { 17 | 18 | Pessoa pessoa = new Pessoa(); 19 | 20 | pessoa.setId(this.id()); 21 | pessoa.setNome(this.nome()); 22 | pessoa.setNascimento(this.nascimento()); 23 | pessoa.setCpf(this.cpf()); 24 | pessoa.setEmail(this.email()); 25 | 26 | return pessoa; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { AuthProvider } from '@/context/AuthContext'; 2 | import './globals.css'; 3 | import type { Metadata } from 'next'; 4 | import { Inter } from 'next/font/google'; 5 | import { Toaster } from 'react-hot-toast'; 6 | 7 | const inter = Inter({ subsets: ['latin'] }); 8 | 9 | export const metadata: Metadata = { 10 | title: 'FIESC', 11 | description: 'Generated by create next app', 12 | }; 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 21 | 22 | 23 | 24 | {children} 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/context/AuthContext/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { createContext, useContext, ReactNode } from 'react' 3 | import { useAuth } from '@/hooks/useAuth' 4 | 5 | interface AuthProviderProps { 6 | children: ReactNode 7 | } 8 | 9 | const AuthContext = createContext | undefined>( 10 | undefined 11 | ) 12 | 13 | export const AuthProvider = ({ children }: AuthProviderProps) => { 14 | const auth = useAuth() 15 | return {children} 16 | } 17 | 18 | export const useAuthContext = () => { 19 | const context = useContext(AuthContext) 20 | if (context === undefined) { 21 | throw new Error('useAuthContext must be used within an AuthProvider') 22 | } 23 | return context 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/hooks/useAuth/index.tsx: -------------------------------------------------------------------------------- 1 | import {login, setToken, setUser, clearToken} from "@/services/authService"; 2 | import { useRouter } from 'next/navigation' 3 | 4 | export function useAuth() { 5 | const router = useRouter() 6 | 7 | const handleLogin = async (username: string, senha: string) => { 8 | try { 9 | const response = await login({login: username, senha}); 10 | setToken(response.token); 11 | setUser(response.user); 12 | } catch (error: any) { 13 | throw new Error("Erro ao fazer login"); 14 | } 15 | } 16 | 17 | const handleLogout = () => { 18 | clearToken() 19 | router.push('/') 20 | } 21 | 22 | return { 23 | handleLogin, 24 | handleLogout 25 | } 26 | } -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/configs/RabbitMQConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.configs; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.amqp.core.Queue; 5 | import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class RabbitMQConfig { 12 | @Bean 13 | public Jackson2JsonMessageConverter messageConverter(){ 14 | ObjectMapper objectMapper = new ObjectMapper(); 15 | 16 | objectMapper.findAndRegisterModules(); 17 | 18 | return new Jackson2JsonMessageConverter(objectMapper); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/models/Pessoa.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.models; 2 | 3 | import jakarta.persistence.*; 4 | import jakarta.validation.constraints.Email; 5 | import lombok.*; 6 | 7 | import java.time.LocalDate; 8 | import java.util.UUID; 9 | 10 | @Entity 11 | @Table(name = "pessoa") 12 | @NoArgsConstructor 13 | public class Pessoa { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.AUTO) 17 | @Column(name = "idPessoa") 18 | @Getter 19 | private UUID id; 20 | 21 | @Getter 22 | @Setter 23 | private String nome; 24 | 25 | @Getter 26 | @Setter 27 | private LocalDate nascimento; 28 | 29 | @Getter 30 | @Setter 31 | @Column(unique = true) 32 | private String cpf; 33 | 34 | @Email 35 | @Getter 36 | @Setter 37 | @Column(unique = true) 38 | private String email; 39 | 40 | @Getter 41 | @Setter 42 | private String senha; 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | } 22 | ); 23 | Input.displayName = "Input"; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/exceptions/ConsumerErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.example.api.exceptions; 2 | 3 | import org.springframework.amqp.AmqpRejectAndDontRequeueException; 4 | import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException; 5 | import org.springframework.util.ErrorHandler; 6 | 7 | public class ConsumerErrorHandler implements ErrorHandler { 8 | @Override 9 | public void handleError(Throwable t) { 10 | String nomeFila = ((ListenerExecutionFailedException) t).getFailedMessage().getMessageProperties().getConsumerQueue(); 11 | System.out.println("Fila: " + nomeFila); 12 | 13 | String mensagem = new String(((ListenerExecutionFailedException) t).getFailedMessage().getBody()); 14 | System.out.println("Mensagem: " + mensagem); 15 | 16 | System.out.println("Error: " + t.getCause().getMessage()); 17 | 18 | throw new AmqpRejectAndDontRequeueException("Erro ao processar a mensagem"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/models/Inscricao.java: -------------------------------------------------------------------------------- 1 | package com.example.api.models; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @Entity 9 | @Table(name = "inscricao") 10 | @NoArgsConstructor 11 | public class Inscricao { 12 | 13 | @EmbeddedId 14 | @Getter 15 | private InscricaoId id; 16 | 17 | @ManyToOne 18 | @JoinColumn(name = "idCurso", insertable=false, updatable=false) 19 | @Getter 20 | @Setter 21 | private Curso curso; 22 | 23 | @ManyToOne 24 | @Getter 25 | @Setter 26 | @JoinColumn(name = "idPessoa", insertable=false, updatable=false) 27 | private Pessoa pessoa; 28 | 29 | public Inscricao(Curso curso, Pessoa pessoa) { 30 | this.curso = curso; 31 | this.pessoa = pessoa; 32 | this.id = new InscricaoId(); 33 | this.id.setIdCurso(curso.getId()); 34 | this.id.setIdPessoa(pessoa.getId()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/consumers/PessoaConsumer.java: -------------------------------------------------------------------------------- 1 | package com.example.api.consumers; 2 | 3 | import com.example.api.dtos.PessoaDto; 4 | import com.example.api.repositories.PessoaRepository; 5 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.messaging.handler.annotation.Payload; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class PessoaConsumer { 12 | 13 | @Autowired 14 | PessoaRepository pessoaRepository; 15 | 16 | @RabbitListener(queues = "${broker.queue.backend.name}") 17 | public void listenEmailQueue(@Payload PessoaDto pessoaDto) { 18 | try { 19 | var pessoa = pessoaDto.toEntity(); 20 | pessoaRepository.save(pessoa); 21 | } catch (Exception e) { 22 | throw new RuntimeException("Erro ao processar a mensagem", e); // Lançar exceção para acionar o retry 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/configs/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.api.configs; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 | 9 | @Configuration 10 | @EnableWebSocketMessageBroker 11 | public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 12 | 13 | @Override 14 | public void configureMessageBroker(MessageBrokerRegistry config) { 15 | config.enableSimpleBroker("/topic"); 16 | config.setApplicationDestinationPrefixes("/app"); 17 | } 18 | 19 | @Override 20 | public void registerStompEndpoints(StompEndpointRegistry registry) { 21 | registry.addEndpoint("/ws-endpoint").setAllowedOrigins("*"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /backend/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/models/Curso.java: -------------------------------------------------------------------------------- 1 | package com.example.api.models; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.UUID; 10 | 11 | @Entity 12 | @Table(name = "curso") 13 | public class Curso { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.AUTO) // Usamos AUTO ou UUID específico para a geração. 17 | @Column(name = "idCurso") 18 | @Getter 19 | private UUID id; 20 | 21 | @Getter 22 | @Setter 23 | private String nome; 24 | 25 | @Getter 26 | @Setter 27 | private int numeroVagas; 28 | 29 | @Getter 30 | @OneToMany(mappedBy = "curso", cascade = CascadeType.ALL, orphanRemoval = true) 31 | private List inscricoes = new ArrayList<>(); 32 | 33 | public synchronized boolean decrementarVagas() { 34 | if (numeroVagas > 0) { 35 | numeroVagas--; 36 | return true; 37 | } 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/models/Pessoa.java: -------------------------------------------------------------------------------- 1 | package com.example.api.models; 2 | 3 | import jakarta.persistence.*; 4 | import jakarta.validation.constraints.Email; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | import java.time.LocalDate; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | @Entity 15 | @Table(name = "pessoa") 16 | @NoArgsConstructor 17 | public class Pessoa { 18 | 19 | @Id 20 | @Column(name = "idPessoa") 21 | @Getter 22 | @Setter 23 | private UUID id; 24 | 25 | @Getter 26 | @Setter 27 | private String nome; 28 | 29 | @Getter 30 | @Setter 31 | private LocalDate nascimento; 32 | 33 | @Getter 34 | @Setter 35 | @Column(unique = true) 36 | private String cpf; 37 | 38 | @Email 39 | @Getter 40 | @Setter 41 | @Column(unique = true) 42 | private String email; 43 | 44 | @Getter 45 | @OneToMany(mappedBy = "pessoa", cascade = CascadeType.ALL, orphanRemoval = true) 46 | private List inscricoes = new ArrayList<>(); 47 | } 48 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/controllers/PessoaController.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.controllers; 2 | 3 | import com.example.backend.dtos.request.CriarPessoaRequestDto; 4 | import com.example.backend.dtos.request.LoginRequestDto; 5 | import com.example.backend.services.PessoaService; 6 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 7 | import jakarta.validation.Valid; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | @RestController 13 | @RequestMapping("/pessoa") 14 | public class PessoaController { 15 | 16 | @Autowired 17 | PessoaService pessoaService; 18 | 19 | @PostMapping 20 | public ResponseEntity criarPessoa(@Valid @RequestBody CriarPessoaRequestDto request) { 21 | return ResponseEntity.ok(pessoaService.criarPessoa(request)); 22 | } 23 | 24 | @PostMapping("/login") 25 | public ResponseEntity login(@Valid @RequestBody LoginRequestDto request) { 26 | return ResponseEntity.ok(pessoaService.login(request)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/producers/PessoaProducer.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.producers; 2 | 3 | import com.example.backend.dtos.PessoaDto; 4 | import com.example.backend.dtos.request.CriarPessoaRequestDto; 5 | import com.example.backend.models.Pessoa; 6 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class PessoaProducer { 12 | final RabbitTemplate rabbitTemplate; 13 | 14 | public PessoaProducer(RabbitTemplate rabbitTemplate) { 15 | this.rabbitTemplate = rabbitTemplate; 16 | } 17 | 18 | @Value(value = "${broker.queue.backend.name}") 19 | private String routingKey; 20 | 21 | public void publishMessagePessoa(Pessoa pessoa){ 22 | 23 | var pessoaDto = new PessoaDto(); 24 | 25 | pessoaDto.setId(pessoa.getId()); 26 | pessoaDto.setNome(pessoa.getNome()); 27 | pessoaDto.setNascimento(pessoa.getNascimento()); 28 | pessoaDto.setCpf(pessoa.getCpf()); 29 | pessoaDto.setEmail(pessoa.getEmail()); 30 | 31 | rabbitTemplate.convertAndSend("", routingKey, pessoaDto); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/controllers/CursoController.java: -------------------------------------------------------------------------------- 1 | package com.example.api.controllers; 2 | 3 | import com.example.api.dtos.request.CriarCursoRequestDto; 4 | import com.example.api.services.CursoService; 5 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 6 | import jakarta.validation.Valid; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.UUID; 12 | 13 | @RestController 14 | @RequestMapping("/curso") 15 | @SecurityRequirement(name = "Authorization") 16 | public class CursoController { 17 | 18 | @Autowired 19 | CursoService cursoService; 20 | 21 | @PostMapping 22 | public ResponseEntity criarCurso(@Valid @RequestBody CriarCursoRequestDto request) { 23 | return ResponseEntity.ok(cursoService.criarCurso(request)); 24 | } 25 | 26 | @GetMapping 27 | public ResponseEntity listarCursos() { 28 | return ResponseEntity.ok(cursoService.listarCursos()); 29 | } 30 | 31 | @GetMapping("/inscritos/{idCurso}") 32 | public ResponseEntity listarInscritos(@PathVariable UUID idCurso) { 33 | return ResponseEntity.ok(cursoService.listarInscritos(idCurso)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import type { NextRequest } from "next/server"; 3 | import { KEY_CACHE_USER } from "./services/authService"; 4 | 5 | export interface User { 6 | nome: string; 7 | cpf: string; 8 | } 9 | 10 | const PROTECTED_PATHS = ['/cursos'] 11 | 12 | export async function middleware(request: NextRequest) { 13 | 14 | const userDataCookie = request.cookies.get(KEY_CACHE_USER)?.value; 15 | 16 | const isAuthenticated = Boolean(userDataCookie); 17 | const requestedPath = request.nextUrl.pathname; 18 | 19 | const isProtectedRoute = PROTECTED_PATHS.includes(requestedPath); 20 | 21 | let user: User | null = null; 22 | 23 | if (isAuthenticated && userDataCookie) { 24 | try { 25 | user = JSON.parse(userDataCookie) 26 | } catch (error) { 27 | console.error('Falha ao analisar os dados do usuário do cookie:', error) 28 | } 29 | } 30 | 31 | if (!isAuthenticated && isProtectedRoute) { 32 | console.log('Usuário não autenticado, redirecionando para /login') 33 | return NextResponse.redirect(new URL('/login', request.url)) 34 | } 35 | 36 | return NextResponse.next(); 37 | } 38 | 39 | export const config = { 40 | matcher: ['/cursos/:path*'] 41 | } -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/controllers/InscricaoController.java: -------------------------------------------------------------------------------- 1 | package com.example.api.controllers; 2 | 3 | import com.example.api.dtos.request.InscricaoRequestDto; 4 | import com.example.api.repositories.InscricaoRepository; 5 | import com.example.api.services.CursoService; 6 | import com.example.api.services.InscricaoService; 7 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 8 | import jakarta.validation.Valid; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.messaging.simp.SimpMessagingTemplate; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import java.util.UUID; 15 | 16 | @RestController 17 | @RequestMapping("/inscricao") 18 | @SecurityRequirement(name = "Authorization") 19 | public class InscricaoController { 20 | 21 | @Autowired 22 | InscricaoService inscricaoService; 23 | 24 | @PostMapping 25 | public ResponseEntity realizarInsricao(@Valid @RequestBody InscricaoRequestDto request) { 26 | return ResponseEntity.ok(inscricaoService.inscrever(request)); 27 | } 28 | 29 | @GetMapping("{cpf}") 30 | public ResponseEntity listarInscricoes(@PathVariable String cpf) { 31 | return ResponseEntity.ok(inscricaoService.listarInscricoes(cpf)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/app/cursos/components/CourseCard.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { Card } from "@/components/ui/card"; 5 | 6 | interface CourseCardProps { 7 | curso: { 8 | idCurso: string; 9 | nomeCurso: string; 10 | numeroInscricoes: number; 11 | numeroVagas: number; 12 | }; 13 | inscricoes: any; 14 | onEnroll: (idCurso: string) => void; 15 | } 16 | 17 | export function CourseCard({ curso, inscricoes, onEnroll }: CourseCardProps) { 18 | const isEnrolled = inscricoes.find( 19 | (inscricao: any) => inscricao === curso.idCurso 20 | ); 21 | 22 | return ( 23 | 24 |

{curso.nomeCurso}

25 |
26 |

Inscrições: {curso.numeroInscricoes}

27 |

Vagas: {curso.numeroVagas}

28 |
29 | {curso.numeroVagas > 0 && 30 | curso.numeroVagas > curso.numeroInscricoes && 31 | !isEnrolled && ( 32 | 35 | )} 36 | {isEnrolled &&

Inscrito

} 37 | {curso.numeroInscricoes >= curso.numeroVagas && !isEnrolled && ( 38 |

Sem vagas

39 | )} 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/services/api.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getToken } from "@/services/authService"; 3 | 4 | const authApi = axios.create({ 5 | baseURL: "http://localhost:8081", 6 | }); 7 | 8 | const courseApi = axios.create({ 9 | baseURL: "http://localhost:8082", 10 | }); 11 | 12 | // Add request interceptor to include auth token 13 | courseApi.interceptors.request.use((config) => { 14 | const token = getToken("authToken"); 15 | 16 | if (token) { 17 | config.headers.Authorization = `Bearer ${token}`; 18 | } 19 | return config; 20 | }); 21 | 22 | export const api = { 23 | auth: { 24 | login: async (credentials: { login: string; senha: string }) => { 25 | const response = await authApi.post("/pessoa/login", credentials); 26 | return response.data; 27 | }, 28 | register: async (userData: any) => { 29 | const response = await authApi.post("/pessoa", userData); 30 | return response.data; 31 | }, 32 | }, 33 | courses: { 34 | list: async () => { 35 | const response = await courseApi.get("/curso"); 36 | return response.data; 37 | }, 38 | listInscricoes: async (cpf: string) => { 39 | const response = await courseApi.get(`/inscricao/${cpf}`); 40 | return response.data; 41 | }, 42 | enroll: async (credentials: {idCurso: string, cpf: string}) => { 43 | const response = await courseApi.post(`/inscricao`, credentials); 44 | return response.data; 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /frontend/src/services/authService/index.ts: -------------------------------------------------------------------------------- 1 | import { api } from "@/services/api"; 2 | import Cookies from "js-cookie"; 3 | 4 | export interface LoginData { 5 | login: string; 6 | senha: string; 7 | } 8 | 9 | export interface LoginResponse { 10 | user: { 11 | nome: string; 12 | cpf: string; 13 | }; 14 | token: string; 15 | expiresIn: number; 16 | } 17 | 18 | export const KEY_CACHE_TOKEN = "authToken"; 19 | export const KEY_CACHE_USER = "user"; 20 | 21 | export async function login(data: LoginData): Promise { 22 | try { 23 | const response = await api.auth.login(data); 24 | return { 25 | user: response.user, 26 | token: response.accessToken, 27 | expiresIn: response.expiresIn, 28 | }; 29 | } catch (error: any) { 30 | throw new Error("Erro ao fazer login"); 31 | } 32 | } 33 | 34 | export function setToken(accessToken: string) { 35 | Cookies.set(KEY_CACHE_TOKEN, accessToken, { 36 | secure: true, 37 | sameSite: "strict", 38 | }); 39 | } 40 | 41 | export function setUser(user: LoginResponse["user"]) { 42 | Cookies.set(KEY_CACHE_USER, JSON.stringify(user), { 43 | secure: true, 44 | sameSite: "lax", 45 | }); 46 | } 47 | 48 | export function getUser() { 49 | const user = Cookies.get(KEY_CACHE_USER); 50 | 51 | if (user) { 52 | return JSON.parse(user); 53 | } 54 | 55 | return null; 56 | } 57 | 58 | export function getToken(name: string) { 59 | return Cookies.get(name); 60 | } 61 | 62 | export function clearToken() { 63 | Cookies.remove(KEY_CACHE_TOKEN); 64 | Cookies.remove(KEY_CACHE_USER); 65 | } 66 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/configs/AdminUserConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.configs; 2 | 3 | import com.example.backend.models.Pessoa; 4 | import com.example.backend.repositories.PessoaRepository; 5 | import jakarta.transaction.Transactional; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 10 | 11 | import java.util.Optional; 12 | 13 | @Configuration 14 | public class AdminUserConfig implements CommandLineRunner { 15 | 16 | 17 | private BCryptPasswordEncoder passwordEncoder; 18 | 19 | @Autowired 20 | PessoaRepository pessoaRepository; 21 | 22 | public AdminUserConfig(BCryptPasswordEncoder passwordEncoder) { 23 | this.passwordEncoder = passwordEncoder; 24 | } 25 | 26 | @Override 27 | @Transactional 28 | public void run(String... args) throws Exception { 29 | 30 | Optional userAdmin = pessoaRepository.findAdmin(); 31 | 32 | userAdmin.ifPresentOrElse( 33 | user -> { 34 | System.out.println("admin ja existe"); 35 | }, 36 | () -> { 37 | System.out.println("criando usuário..."); 38 | 39 | var pessoa = new Pessoa(); 40 | pessoa.setNome("admin"); 41 | pessoa.setSenha(passwordEncoder.encode("123")); 42 | pessoa.setEmail("admin@email.com"); 43 | pessoa.setCpf("12345678900"); 44 | pessoaRepository.save(pessoa); 45 | } 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/configs/RabbitMQConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.api.configs; 2 | 3 | import com.example.api.exceptions.ConsumerErrorHandler; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.springframework.amqp.core.*; 6 | import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; 7 | import org.springframework.amqp.rabbit.connection.ConnectionFactory; 8 | import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; 9 | import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | @Configuration 15 | public class RabbitMQConfig { 16 | @Value("${broker.queue.backend.name}") 17 | private String queue; 18 | 19 | @Bean 20 | public Queue queue() { 21 | return new Queue(queue, true); 22 | } 23 | 24 | @Bean 25 | public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) { 26 | SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); 27 | 28 | factory.setConnectionFactory(connectionFactory); 29 | factory.setErrorHandler(new ConsumerErrorHandler()); 30 | factory.setMessageConverter(messageConverter()); 31 | return factory; 32 | } 33 | 34 | @Bean 35 | public Jackson2JsonMessageConverter messageConverter(){ 36 | ObjectMapper objectMapper = new ObjectMapper(); 37 | 38 | objectMapper.findAndRegisterModules(); 39 | 40 | return new Jackson2JsonMessageConverter(objectMapper); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button"; 45 | return ( 46 | 51 | ); 52 | } 53 | ); 54 | Button.displayName = "Button"; 55 | 56 | export { Button, buttonVariants }; 57 | -------------------------------------------------------------------------------- /frontend/src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )); 18 | Card.displayName = "Card"; 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )); 30 | CardHeader.displayName = "CardHeader"; 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )); 45 | CardTitle.displayName = "CardTitle"; 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )); 57 | CardDescription.displayName = "CardDescription"; 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )); 65 | CardContent.displayName = "CardContent"; 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )); 77 | CardFooter.displayName = "CardFooter"; 78 | 79 | export { 80 | Card, 81 | CardHeader, 82 | CardFooter, 83 | CardTitle, 84 | CardDescription, 85 | CardContent, 86 | }; 87 | -------------------------------------------------------------------------------- /frontend/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | @layer base { 20 | :root { 21 | --background: 0 0% 100%; 22 | --foreground: 0 0% 3.9%; 23 | --card: 0 0% 100%; 24 | --card-foreground: 0 0% 3.9%; 25 | --popover: 0 0% 100%; 26 | --popover-foreground: 0 0% 3.9%; 27 | --primary: 0 0% 9%; 28 | --primary-foreground: 0 0% 98%; 29 | --secondary: 0 0% 96.1%; 30 | --secondary-foreground: 0 0% 9%; 31 | --muted: 0 0% 96.1%; 32 | --muted-foreground: 0 0% 45.1%; 33 | --accent: 0 0% 96.1%; 34 | --accent-foreground: 0 0% 9%; 35 | --destructive: 0 84.2% 60.2%; 36 | --destructive-foreground: 0 0% 98%; 37 | --border: 0 0% 89.8%; 38 | --input: 0 0% 89.8%; 39 | --ring: 0 0% 3.9%; 40 | --chart-1: 12 76% 61%; 41 | --chart-2: 173 58% 39%; 42 | --chart-3: 197 37% 24%; 43 | --chart-4: 43 74% 66%; 44 | --chart-5: 27 87% 67%; 45 | --radius: 0.5rem; 46 | } 47 | .dark { 48 | --background: 0 0% 3.9%; 49 | --foreground: 0 0% 98%; 50 | --card: 0 0% 3.9%; 51 | --card-foreground: 0 0% 98%; 52 | --popover: 0 0% 3.9%; 53 | --popover-foreground: 0 0% 98%; 54 | --primary: 0 0% 98%; 55 | --primary-foreground: 0 0% 9%; 56 | --secondary: 0 0% 14.9%; 57 | --secondary-foreground: 0 0% 98%; 58 | --muted: 0 0% 14.9%; 59 | --muted-foreground: 0 0% 63.9%; 60 | --accent: 0 0% 14.9%; 61 | --accent-foreground: 0 0% 98%; 62 | --destructive: 0 62.8% 30.6%; 63 | --destructive-foreground: 0 0% 98%; 64 | --border: 0 0% 14.9%; 65 | --input: 0 0% 14.9%; 66 | --ring: 0 0% 83.1%; 67 | --chart-1: 220 70% 50%; 68 | --chart-2: 160 60% 45%; 69 | --chart-3: 30 80% 55%; 70 | --chart-4: 280 65% 60%; 71 | --chart-5: 340 75% 55%; 72 | } 73 | } 74 | 75 | @layer base { 76 | * { 77 | @apply border-border; 78 | } 79 | body { 80 | @apply bg-background text-foreground; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/services/InscricaoService.java: -------------------------------------------------------------------------------- 1 | package com.example.api.services; 2 | 3 | import com.example.api.dtos.request.InscricaoRequestDto; 4 | import com.example.api.dtos.response.CursoResponseDto; 5 | import com.example.api.dtos.response.InscricaoResponseDto; 6 | import com.example.api.models.Curso; 7 | import com.example.api.models.Inscricao; 8 | import com.example.api.models.Pessoa; 9 | import com.example.api.repositories.CursoRepository; 10 | import com.example.api.repositories.InscricaoRepository; 11 | import com.example.api.repositories.PessoaRepository; 12 | import jakarta.transaction.Transactional; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.messaging.simp.SimpMessagingTemplate; 15 | import org.springframework.scheduling.annotation.Async; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.util.List; 19 | import java.util.UUID; 20 | import java.util.stream.Collectors; 21 | 22 | @Service 23 | public class InscricaoService { 24 | 25 | @Autowired 26 | InscricaoRepository inscricaoRepository; 27 | 28 | @Autowired 29 | CursoRepository cursoRepository; 30 | 31 | @Autowired 32 | PessoaRepository pessoaRepository; 33 | 34 | @Autowired 35 | SimpMessagingTemplate messagingTemplate; 36 | 37 | @Autowired 38 | CursoService cursoService; 39 | 40 | @Transactional 41 | @Async 42 | public InscricaoResponseDto inscrever(InscricaoRequestDto request) { 43 | Curso curso = cursoRepository.findById(request.idCurso()).orElseThrow(); 44 | Pessoa pessoa = pessoaRepository.findByCpf(request.cpf()).orElseThrow(); 45 | 46 | if(pessoa.getInscricoes().stream().anyMatch(inscricao -> inscricao.getCurso().getId().equals(curso.getId()))) { 47 | throw new RuntimeException("Inscrição já realizada"); 48 | } 49 | 50 | if (curso.decrementarVagas()) { 51 | try { 52 | Inscricao inscricao = new Inscricao(curso, pessoa); 53 | var res = inscricaoRepository.save(inscricao); 54 | cursoRepository.save(curso); 55 | 56 | // Envia a atualização para os clientes via WebSocket 57 | messagingTemplate.convertAndSend("/topic", cursoService.listarCursos()); 58 | 59 | return new InscricaoResponseDto(res.getId(), "Inscrição realizada com sucesso!"); 60 | } catch (Exception e) { 61 | throw new RuntimeException("Erro ao realizar inscrição"); 62 | } 63 | } else { 64 | throw new RuntimeException("Vagas esgotadas"); 65 | } 66 | } 67 | 68 | public List listarInscricoes (String cpf) { 69 | return inscricaoRepository.findAllByPessoaCpf(cpf).stream().map(inscricao -> { 70 | return inscricao.getCurso().getId(); 71 | }).collect(Collectors.toList()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/services/CursoService.java: -------------------------------------------------------------------------------- 1 | package com.example.api.services; 2 | 3 | import com.example.api.dtos.request.CriarCursoRequestDto; 4 | import com.example.api.dtos.response.CriarCursoResponseDto; 5 | import com.example.api.dtos.response.CursoResponseDto; 6 | import com.example.api.models.Curso; 7 | import com.example.api.repositories.CursoRepository; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.Sort; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.UUID; 16 | import java.util.stream.Collectors; 17 | 18 | @Service 19 | public class CursoService { 20 | 21 | @Autowired 22 | CursoRepository cursoRepository; 23 | 24 | public CriarCursoResponseDto criarCurso (CriarCursoRequestDto request){ 25 | Curso curso = toEntity(request); 26 | cursoRepository.save(curso); 27 | 28 | return new CriarCursoResponseDto(curso.getId(), "Curso criado com sucesso"); 29 | } 30 | 31 | public CursoResponseDto buscarCurso(UUID id) { 32 | return cursoRepository.findById(id).map(this::toResponse).orElse(null); 33 | } 34 | 35 | public List listarCursos(){ 36 | List cursos = cursoRepository.findAll(Sort.by(Sort.Direction.ASC, "nome")); 37 | return listToResponse(cursos); 38 | } 39 | 40 | public Map>> listarInscritos(UUID idCurso){ 41 | return cursoRepository.findById(idCurso).map( 42 | curso -> { 43 | List> inscritos = curso.getInscricoes().stream() 44 | .map(inscricao -> { 45 | Map inscrito = new HashMap<>(); 46 | inscrito.put("cpf", inscricao.getPessoa().getCpf()); 47 | return inscrito; 48 | }) 49 | .collect(Collectors.toList()); 50 | Map>> response = new HashMap<>(); 51 | response.put("inscritos", inscritos); 52 | return response; 53 | }).orElse(null); 54 | 55 | } 56 | 57 | 58 | List listToResponse(List cursos){ 59 | return cursos.stream().map(this::toResponse).collect(Collectors.toList()); 60 | 61 | } 62 | 63 | CursoResponseDto toResponse(Curso curso) { 64 | return new CursoResponseDto(curso.getId(), curso.getNome(), curso.getNumeroVagas(), curso.getInscricoes().size()); 65 | } 66 | 67 | 68 | Curso toEntity(CriarCursoRequestDto request){ 69 | Curso curso = new Curso(); 70 | curso.setNome(request.nome()); 71 | curso.setNumeroVagas(request.numeroVagas()); 72 | 73 | return curso; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /frontend/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss'; 2 | 3 | const config: Config = { 4 | darkMode: ['class'], 5 | content: [ 6 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 8 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 9 | ], 10 | theme: { 11 | extend: { 12 | backgroundImage: { 13 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 14 | 'gradient-conic': 15 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 16 | }, 17 | borderRadius: { 18 | lg: 'var(--radius)', 19 | md: 'calc(var(--radius) - 2px)', 20 | sm: 'calc(var(--radius) - 4px)', 21 | }, 22 | colors: { 23 | background: 'hsl(var(--background))', 24 | foreground: 'hsl(var(--foreground))', 25 | card: { 26 | DEFAULT: 'hsl(var(--card))', 27 | foreground: 'hsl(var(--card-foreground))', 28 | }, 29 | popover: { 30 | DEFAULT: 'hsl(var(--popover))', 31 | foreground: 'hsl(var(--popover-foreground))', 32 | }, 33 | primary: { 34 | DEFAULT: 'hsl(var(--primary))', 35 | foreground: 'hsl(var(--primary-foreground))', 36 | }, 37 | secondary: { 38 | DEFAULT: 'hsl(var(--secondary))', 39 | foreground: 'hsl(var(--secondary-foreground))', 40 | }, 41 | muted: { 42 | DEFAULT: 'hsl(var(--muted))', 43 | foreground: 'hsl(var(--muted-foreground))', 44 | }, 45 | accent: { 46 | DEFAULT: 'hsl(var(--accent))', 47 | foreground: 'hsl(var(--accent-foreground))', 48 | }, 49 | destructive: { 50 | DEFAULT: 'hsl(var(--destructive))', 51 | foreground: 'hsl(var(--destructive-foreground))', 52 | }, 53 | border: 'hsl(var(--border))', 54 | input: 'hsl(var(--input))', 55 | ring: 'hsl(var(--ring))', 56 | chart: { 57 | '1': 'hsl(var(--chart-1))', 58 | '2': 'hsl(var(--chart-2))', 59 | '3': 'hsl(var(--chart-3))', 60 | '4': 'hsl(var(--chart-4))', 61 | '5': 'hsl(var(--chart-5))', 62 | }, 63 | }, 64 | keyframes: { 65 | 'accordion-down': { 66 | from: { 67 | height: '0', 68 | }, 69 | to: { 70 | height: 'var(--radix-accordion-content-height)', 71 | }, 72 | }, 73 | 'accordion-up': { 74 | from: { 75 | height: 'var(--radix-accordion-content-height)', 76 | }, 77 | to: { 78 | height: '0', 79 | }, 80 | }, 81 | }, 82 | animation: { 83 | 'accordion-down': 'accordion-down 0.2s ease-out', 84 | 'accordion-up': 'accordion-up 0.2s ease-out', 85 | }, 86 | }, 87 | }, 88 | plugins: [require('tailwindcss-animate')], 89 | }; 90 | export default config; 91 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "@hookform/resolvers": "^3.9.0", 14 | "@next/swc-wasm-nodejs": "13.5.1", 15 | "@radix-ui/react-accordion": "^1.2.0", 16 | "@radix-ui/react-alert-dialog": "^1.1.1", 17 | "@radix-ui/react-aspect-ratio": "^1.1.0", 18 | "@radix-ui/react-avatar": "^1.1.0", 19 | "@radix-ui/react-checkbox": "^1.1.1", 20 | "@radix-ui/react-collapsible": "^1.1.0", 21 | "@radix-ui/react-context-menu": "^2.2.1", 22 | "@radix-ui/react-dialog": "^1.1.1", 23 | "@radix-ui/react-dropdown-menu": "^2.1.1", 24 | "@radix-ui/react-hover-card": "^1.1.1", 25 | "@radix-ui/react-label": "^2.1.0", 26 | "@radix-ui/react-menubar": "^1.1.1", 27 | "@radix-ui/react-navigation-menu": "^1.2.0", 28 | "@radix-ui/react-popover": "^1.1.1", 29 | "@radix-ui/react-progress": "^1.1.0", 30 | "@radix-ui/react-radio-group": "^1.2.0", 31 | "@radix-ui/react-scroll-area": "^1.1.0", 32 | "@radix-ui/react-select": "^2.1.1", 33 | "@radix-ui/react-separator": "^1.1.0", 34 | "@radix-ui/react-slider": "^1.2.0", 35 | "@radix-ui/react-slot": "^1.1.0", 36 | "@radix-ui/react-switch": "^1.1.0", 37 | "@radix-ui/react-tabs": "^1.1.0", 38 | "@radix-ui/react-toast": "^1.2.1", 39 | "@radix-ui/react-toggle": "^1.1.0", 40 | "@radix-ui/react-toggle-group": "^1.1.0", 41 | "@radix-ui/react-tooltip": "^1.1.2", 42 | "@stomp/stompjs": "^7.0.0", 43 | "@types/js-cookie": "^3.0.6", 44 | "@types/node": "20.6.2", 45 | "@types/react": "18.2.22", 46 | "@types/react-dom": "18.2.7", 47 | "autoprefixer": "10.4.15", 48 | "axios": "^1.6.8", 49 | "class-variance-authority": "^0.7.0", 50 | "clsx": "^2.1.1", 51 | "cmdk": "^1.0.0", 52 | "date-fns": "^3.6.0", 53 | "embla-carousel-react": "^8.3.0", 54 | "eslint": "8.49.0", 55 | "eslint-config-next": "13.5.1", 56 | "input-otp": "^1.2.4", 57 | "js-cookie": "^3.0.5", 58 | "jwt-simple": "^0.5.6", 59 | "lucide-react": "^0.446.0", 60 | "next": "13.5.1", 61 | "next-themes": "^0.3.0", 62 | "postcss": "8.4.30", 63 | "react": "18.2.0", 64 | "react-day-picker": "^8.10.1", 65 | "react-dom": "18.2.0", 66 | "react-hook-form": "^7.53.0", 67 | "react-hot-toast": "^2.4.1", 68 | "react-resizable-panels": "^2.1.3", 69 | "recharts": "^2.12.7", 70 | "sonner": "^1.5.0", 71 | "tailwind-merge": "^2.5.2", 72 | "tailwindcss": "3.3.3", 73 | "tailwindcss-animate": "^1.0.7", 74 | "typescript": "5.2.2", 75 | "vaul": "^0.9.9", 76 | "zod": "^3.23.8", 77 | "zustand": "^4.5.2" 78 | }, 79 | "devDependencies": { 80 | "@testing-library/jest-dom": "^6.4.2", 81 | "@testing-library/react": "^14.2.1", 82 | "@types/jest": "^29.5.12", 83 | "@types/jwt-simple": "^0.5.36", 84 | "jest": "^29.7.0", 85 | "jest-environment-jsdom": "^29.7.0" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/services/PessoaService.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.services; 2 | 3 | import com.example.backend.dtos.request.CriarPessoaRequestDto; 4 | import com.example.backend.dtos.request.LoginRequestDto; 5 | import com.example.backend.dtos.response.CriarPessoaResponseDto; 6 | import com.example.backend.dtos.response.LoginResponseDto; 7 | import com.example.backend.models.Pessoa; 8 | import com.example.backend.producers.PessoaProducer; 9 | import com.example.backend.repositories.PessoaRepository; 10 | import jakarta.transaction.Transactional; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | import org.springframework.security.oauth2.jwt.JwtClaimsSet; 14 | import org.springframework.security.oauth2.jwt.JwtEncoder; 15 | import org.springframework.security.oauth2.jwt.JwtEncoderParameters; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.time.Instant; 19 | import java.util.Map; 20 | 21 | @Service 22 | public class PessoaService { 23 | 24 | @Autowired 25 | PessoaRepository pessoaRepository; 26 | 27 | @Autowired 28 | BCryptPasswordEncoder passwordEncoder; 29 | 30 | @Autowired 31 | JwtEncoder jwtEncoder; 32 | 33 | @Autowired 34 | PessoaProducer pessoaProducer; 35 | 36 | @Transactional 37 | public CriarPessoaResponseDto criarPessoa(CriarPessoaRequestDto request){ 38 | Pessoa pessoa = toEntity(request); 39 | var res = pessoaRepository.save(pessoa); 40 | 41 | pessoaProducer.publishMessagePessoa(res); 42 | 43 | return new CriarPessoaResponseDto(res.getId(), "Usuário criado com sucesso"); 44 | } 45 | 46 | public LoginResponseDto login(LoginRequestDto request) { 47 | 48 | Pessoa pessoa = pessoaRepository.findByLogin(request.login()); 49 | 50 | if (pessoa == null) { 51 | throw new RuntimeException("Usuário não encontrado"); 52 | } 53 | 54 | if (!passwordEncoder.matches(request.senha(), pessoa.getSenha())) { 55 | throw new RuntimeException("Senha inválida"); 56 | } 57 | 58 | var now = Instant.now(); 59 | var expiresIn = 300L; 60 | 61 | Map user = Map.of("nome", pessoa.getNome(), "cpf", pessoa.getCpf()); 62 | 63 | var claims = JwtClaimsSet.builder() 64 | .issuer("backend") 65 | .subject(pessoa.getId().toString()) 66 | .issuedAt(now) 67 | .expiresAt(now.plusSeconds(expiresIn)) 68 | .build(); 69 | 70 | var jwtValue = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); 71 | 72 | return new LoginResponseDto(user, jwtValue, expiresIn); 73 | } 74 | 75 | public Pessoa toEntity(CriarPessoaRequestDto pessoaDTO) { 76 | 77 | Pessoa pessoa = new Pessoa(); 78 | pessoa.setNome(pessoaDTO.nome()); 79 | pessoa.setNascimento(pessoaDTO.nascimento()); 80 | pessoa.setCpf(pessoaDTO.cpf()); 81 | pessoa.setEmail(pessoaDTO.email()); 82 | pessoa.setSenha(passwordEncoder.encode(pessoaDTO.senha())); 83 | 84 | return pessoa; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /frontend/src/app/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { useForm } from "react-hook-form"; 5 | import { zodResolver } from "@hookform/resolvers/zod"; 6 | import * as z from "zod"; 7 | import { useRouter } from "next/navigation"; 8 | import Link from "next/link"; 9 | import { Button } from "@/components/ui/button"; 10 | import { Input } from "@/components/ui/input"; 11 | import { useAuthContext } from "@/context/AuthContext"; 12 | 13 | const loginSchema = z.object({ 14 | login: z.string().min(1, "CPF ou E-mail é obrigatório"), 15 | senha: z.string().min(1, "Senha é obrigatória"), 16 | }); 17 | 18 | export default function LoginPage() { 19 | const router = useRouter(); 20 | const { handleLogin } = useAuthContext(); 21 | const [isLoading, setIsLoading] = useState(false); 22 | 23 | const { 24 | register, 25 | handleSubmit, 26 | formState: { errors }, 27 | } = useForm({ 28 | resolver: zodResolver(loginSchema), 29 | }); 30 | 31 | const onSubmit = async (data: any) => { 32 | try { 33 | setIsLoading(true); 34 | const credentials = { 35 | login: data.login, 36 | senha: data.senha, 37 | }; 38 | //const response = await api.auth.login(credentials); 39 | 40 | await handleLogin(credentials.login, credentials.senha); 41 | 42 | //setAuth(response.user, response.accessToken, response.expiresIn); 43 | router.push("/cursos"); 44 | } catch (error: any) { 45 | // toast({ 46 | // title: 'Erro ao fazer login', 47 | // description: 'CPF/E-mail ou senha incorretos', 48 | // variant: 'destructive', 49 | // }); 50 | } finally { 51 | setIsLoading(false); 52 | } 53 | }; 54 | 55 | return ( 56 |
57 |
58 |

Login

59 |
60 |
61 | 66 | {errors.login && ( 67 |

68 | {errors.login.message as string} 69 |

70 | )} 71 |
72 |
73 | 79 | {errors.senha && ( 80 |

81 | {errors.senha.message as string} 82 |

83 | )} 84 |
85 | 88 |
89 |

90 | Não tem uma conta?{" "} 91 | 92 | Cadastre-se 93 | 94 |

95 |
96 |
97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /api/src/main/java/com/example/api/configs/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.api.configs; 2 | 3 | import com.nimbusds.jose.jwk.JWK; 4 | import com.nimbusds.jose.jwk.JWKSet; 5 | import com.nimbusds.jose.jwk.RSAKey; 6 | import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.security.config.Customizer; 12 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 14 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 15 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 16 | import org.springframework.security.oauth2.jwt.JwtDecoder; 17 | import org.springframework.security.oauth2.jwt.JwtEncoder; 18 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; 19 | import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; 20 | import org.springframework.security.web.SecurityFilterChain; 21 | import org.springframework.web.cors.CorsConfiguration; 22 | 23 | import java.security.interfaces.RSAPrivateKey; 24 | import java.security.interfaces.RSAPublicKey; 25 | 26 | @Configuration 27 | @EnableWebSecurity 28 | @EnableMethodSecurity 29 | public class SecurityConfig { 30 | 31 | @Value("${jwt.public.key}") 32 | private RSAPublicKey publicKey; 33 | 34 | @Value("${jwt.private.key}") 35 | private RSAPrivateKey privateKey; 36 | 37 | @Bean 38 | public SecurityFilterChain seurityFilterChain(HttpSecurity http) throws Exception { 39 | http.authorizeHttpRequests( 40 | auth -> auth 41 | .requestMatchers(HttpMethod.GET, "/swagger-ui/**").permitAll() 42 | .requestMatchers(HttpMethod.GET, "/v3/api-docs/**").permitAll() 43 | .requestMatchers("/ws-endpoint/**").permitAll() // Libera acesso ao WebSocket 44 | .anyRequest().authenticated()) 45 | .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) 46 | .cors( 47 | httpSecurityCorsConfigurer -> 48 | httpSecurityCorsConfigurer.configurationSource( 49 | request -> { 50 | var cors = new CorsConfiguration(); 51 | cors.applyPermitDefaultValues(); 52 | cors.addAllowedMethod("*"); 53 | return cors; 54 | })) 55 | .csrf(AbstractHttpConfigurer::disable); 56 | 57 | return http.build(); 58 | } 59 | 60 | @Bean 61 | public JwtDecoder jwtDecoder() { 62 | return NimbusJwtDecoder.withPublicKey(publicKey).build(); 63 | } 64 | 65 | @Bean 66 | public JwtEncoder jwtEncoder() { 67 | JWK jwk = new RSAKey.Builder(this.publicKey).privateKey(privateKey).build(); 68 | var jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); 69 | return new NimbusJwtEncoder(jwks); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Plataforma de Cursos 💻

2 | 3 |

4 | Sobre   |    5 | Tecnologias 6 |    |    7 | Desafios   |    8 | Próximos passos   |    9 | Rotas   |    10 | Requisitos   |    11 | Iniciando 12 |

13 | 14 |
15 | Tela de tela de login 16 | Tela de registro 17 | Tela de dos cursos 18 |
19 | 20 | ## :page_with_curl: Sobre 21 | 22 |
23 | System Design 24 |
25 | 26 | O desafio consistia na criação de uma plataforma de cursos online, utilizando Java com Spring Boot no back, arquitetura de microsserviços e RabbitMQ para gerenciamento de filas. Também era necessário a utilização de concorrência e WebSocket para comunicação em tempo real, além de implementar autenticação JWT com o OAuth2. Já no front-end era mais simples, utilizei NextJs com React, junto com Talwhind para estilização. Também foi implementado a conexão com WebSocket do microsserviço API. 27 | 28 | ## :hammer: Tecnologias 29 | 30 | - Java 23 31 | - Maven 32 | - NextJS 13 33 | - Spring Boot 34 | - Spring AMQP 35 | - Spring JPA 36 | - Spring OAuth2 37 | - Spring Security 38 | - Spring Validation 39 | - Lombok 40 | - Jackson 41 | - RabbitMQ 42 | - Docker 43 | - Postgres 44 | 45 | 46 | ## :sos: Desafios 47 | 48 | O desafio não é só na questão técnica, mas também na forma que você se organiza para completá-lo. O tempo limite era 72h (3 dias) e uma série de requisitos para serem satisfeitos. Mas acabou que deixei de apenas implementar os testes, tanto os unitários quanto o de integração, do front e do back. 49 | 50 | Uma coisa que eu queria ter implementado mas pelo tempo não rolou, foi a questão da criação das imagens para o docker e o deploy em uma cloud, juntamente com uma API Gateway (com NGINX ou Spring Cloud). 51 | 52 | ## :books: Requisitos 53 | 54 | As tecnologias a seguir são necessárias para conseguir rodar o projeto em sua máquina. 55 | 56 | - Ter [**Git**](https://git-scm.com/) para clonar o projeto. 57 | - Ter o Java 23 instalado. 58 | - Ter o NodeJs instalado. 59 | - Ter [**Docker**](https://www.docker.com/get-started/) para executar o projeto. 60 | 61 | ## :golf: Rotas 62 | A API oferece Swagger para documentação das rotas 63 | 64 | ### Swagger Backend 65 | http://localhost:8081/swagger-ui/index.html# 66 | 67 | ### Swagger API 68 | http://localhost:8082/swagger-ui/index.html# 69 | 70 | ## :rocket: Iniciando 71 | ``` bash 72 | # Clonar o projeto: 73 | $ git clone git@github.com:davifariasp/plataforma-cursos.git 74 | 75 | # Entrar no diretório: 76 | $ cd plataforma-cursos 77 | 78 | # Compor o projeto: 79 | $ docker compose up -d 80 | 81 | # Entrar no serviço backend: 82 | $ cd backend 83 | 84 | # Entrar no serviço api: 85 | $ cd api 86 | 87 | # Entrar da spa: 88 | $ cd frontend 89 | ``` 90 | -------------------------------------------------------------------------------- /backend/src/main/java/com/example/backend/configs/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.backend.configs; 2 | 3 | import com.nimbusds.jose.jwk.JWK; 4 | import com.nimbusds.jose.jwk.JWKSet; 5 | import com.nimbusds.jose.jwk.RSAKey; 6 | import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.security.config.Customizer; 12 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 14 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 15 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 16 | import org.springframework.security.oauth2.jwt.JwtDecoder; 17 | import org.springframework.security.oauth2.jwt.JwtEncoder; 18 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; 19 | import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; 20 | import org.springframework.security.web.SecurityFilterChain; 21 | import org.springframework.web.cors.CorsConfiguration; 22 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 23 | 24 | import java.security.interfaces.RSAPrivateKey; 25 | import java.security.interfaces.RSAPublicKey; 26 | 27 | @Configuration 28 | @EnableWebSecurity 29 | @EnableMethodSecurity 30 | public class SecurityConfig { 31 | 32 | @Value("${jwt.public.key}") 33 | private RSAPublicKey publicKey; 34 | 35 | @Value("${jwt.private.key}") 36 | private RSAPrivateKey privateKey; 37 | 38 | @Bean 39 | public SecurityFilterChain seurityFilterChain(HttpSecurity http) throws Exception { 40 | http.authorizeHttpRequests( 41 | auth -> auth 42 | .requestMatchers(HttpMethod.POST, "/pessoa/**").permitAll() 43 | .requestMatchers(HttpMethod.GET, "/swagger-ui/**").permitAll() 44 | .requestMatchers(HttpMethod.GET, "/v3/api-docs/**").permitAll() 45 | .anyRequest().authenticated()) 46 | .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) 47 | .cors( 48 | httpSecurityCorsConfigurer -> 49 | httpSecurityCorsConfigurer.configurationSource( 50 | request -> { 51 | var cors = new CorsConfiguration(); 52 | cors.applyPermitDefaultValues(); 53 | cors.addAllowedMethod("*"); 54 | return cors; 55 | })) 56 | .csrf(AbstractHttpConfigurer::disable); 57 | 58 | return http.build(); 59 | } 60 | 61 | @Bean 62 | public JwtDecoder jwtDecoder() { 63 | return NimbusJwtDecoder.withPublicKey(publicKey).build(); 64 | } 65 | 66 | @Bean 67 | public JwtEncoder jwtEncoder() { 68 | JWK jwk = new RSAKey.Builder(this.publicKey).privateKey(privateKey).build(); 69 | var jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); 70 | return new NimbusJwtEncoder(jwks); 71 | } 72 | 73 | @Bean 74 | public BCryptPasswordEncoder bCryptPasswordEncoder() { 75 | return new BCryptPasswordEncoder(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /frontend/src/app/cursos/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { useRouter } from "next/navigation"; 5 | import { toast } from "react-hot-toast"; 6 | import { api } from "@/services/api"; 7 | //import { useAuth } from "@/lib/auth"; 8 | import { useAuth } from "@/hooks/useAuth"; 9 | import { getUser } from "@/services/authService"; 10 | import { CourseCard } from "./components/CourseCard"; 11 | import { Header } from "./components/Header"; 12 | import { Client } from "@stomp/stompjs"; 13 | 14 | export default function CoursesPage() { 15 | const router = useRouter(); 16 | const { handleLogout } = useAuth(); 17 | const [courses, setCourses] = useState([]); 18 | const [inscricoes, setInscricoes] = useState([]); 19 | const [isLoading, setIsLoading] = useState(true); 20 | 21 | const user = getUser(); 22 | 23 | useEffect(() => { 24 | const fetchCourses = async () => { 25 | try { 26 | const data = await api.courses.list(); 27 | 28 | setCourses(data); 29 | } catch (error) { 30 | toast.error("Erro ao buscar cursos"); 31 | } finally { 32 | setIsLoading(false); 33 | } 34 | }; 35 | fetchCourses(); 36 | }, []); 37 | 38 | useEffect(() => { 39 | // Verificar se o `user` e `cpf` estão carregados 40 | if (user && user.cpf) { 41 | fetchInscricoes(); 42 | } else { 43 | setIsLoading(false); // Garantir que o loading seja removido 44 | } 45 | },[]); 46 | 47 | useEffect(() => { 48 | const client = new Client({ 49 | brokerURL: "ws://localhost:8082/ws-endpoint", 50 | connectHeaders: { 51 | // Se precisar enviar um token de autenticação, adicione aqui 52 | }, 53 | onConnect: () => { 54 | client.subscribe("/topic", (message) => { 55 | const updatedCourses = JSON.parse(message.body); 56 | 57 | setCourses(updatedCourses); 58 | }); 59 | }, 60 | }); 61 | 62 | client.activate(); 63 | 64 | return () => { 65 | client.deactivate(); 66 | }; 67 | }, []); 68 | 69 | const fetchInscricoes = async () => { 70 | try { 71 | const data = await api.courses.listInscricoes(user.cpf); 72 | setInscricoes(data); 73 | } catch (error) { 74 | toast.error("Erro ao buscar inscrições"); 75 | } finally { 76 | setIsLoading(false); 77 | } 78 | }; 79 | 80 | const handleEnroll = async (cursoId: string) => { 81 | const credentials = { 82 | idCurso: cursoId, 83 | cpf: user.cpf, 84 | }; 85 | 86 | console.log(credentials); 87 | 88 | try { 89 | await api.courses.enroll(credentials); 90 | await fetchInscricoes(); 91 | } catch (error: any) { 92 | toast.error("Erro ao se inscrever"); 93 | } 94 | }; 95 | 96 | if (isLoading) { 97 | return ( 98 |
99 |

Carregando...

100 |
101 | ); 102 | } 103 | 104 | return ( 105 |
106 |
107 |
108 |
109 | {courses.map((course: any) => ( 110 | 116 | ))} 117 |
118 |
119 |
120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /backend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.0 9 | 10 | 11 | com.example 12 | backend 13 | 0.0.1-SNAPSHOT 14 | backend 15 | Backend for FIESC 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 23 31 | 32 | 33 | 34 | org.springdoc 35 | springdoc-openapi-starter-webmvc-ui 36 | 2.0.2 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-amqp 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-data-jpa 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-security 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-validation 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-web 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-oauth2-resource-server 61 | 62 | 63 | org.postgresql 64 | postgresql 65 | runtime 66 | 67 | 68 | org.projectlombok 69 | lombok 70 | true 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-starter-test 75 | test 76 | 77 | 78 | org.springframework.amqp 79 | spring-rabbit-test 80 | test 81 | 82 | 83 | org.springframework.security 84 | spring-security-test 85 | test 86 | 87 | 88 | com.fasterxml.jackson.module 89 | jackson-module-parameter-names 90 | 91 | 92 | com.fasterxml.jackson.datatype 93 | jackson-datatype-jsr310 94 | 95 | 96 | com.fasterxml.jackson.datatype 97 | jackson-datatype-jdk8 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.springframework.boot 105 | spring-boot-maven-plugin 106 | 107 | 108 | 109 | org.projectlombok 110 | lombok 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.0 9 | 10 | 11 | com.example 12 | api 13 | 0.0.1-SNAPSHOT 14 | api 15 | API for FIESC 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 23 31 | 32 | 33 | 34 | org.springdoc 35 | springdoc-openapi-starter-webmvc-ui 36 | 2.0.2 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-amqp 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-data-jpa 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-security 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-validation 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-web 57 | 58 | 59 | 60 | org.postgresql 61 | postgresql 62 | runtime 63 | 64 | 65 | org.projectlombok 66 | lombok 67 | true 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-test 72 | test 73 | 74 | 75 | org.springframework.amqp 76 | spring-rabbit-test 77 | test 78 | 79 | 80 | org.springframework.security 81 | spring-security-test 82 | test 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-starter-websocket 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-starter-oauth2-resource-server 91 | 92 | 93 | com.fasterxml.jackson.module 94 | jackson-module-parameter-names 95 | 96 | 97 | com.fasterxml.jackson.datatype 98 | jackson-datatype-jsr310 99 | 100 | 101 | com.fasterxml.jackson.datatype 102 | jackson-datatype-jdk8 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.springframework.boot 110 | spring-boot-maven-plugin 111 | 112 | 113 | 114 | org.projectlombok 115 | lombok 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /frontend/src/app/cadastro/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { useForm } from "react-hook-form"; 5 | import { zodResolver } from "@hookform/resolvers/zod"; 6 | import * as z from "zod"; 7 | import { useRouter } from "next/navigation"; 8 | import Link from "next/link"; 9 | import { Button } from "@/components/ui/button"; 10 | import { Input } from "@/components/ui/input"; 11 | import { api } from "@/services/api"; 12 | import { useAuth } from "@/hooks/useAuth"; 13 | 14 | const nameRegex = /^[A-Z][a-z]+ [A-Z][a-z]+( [A-Z][a-z]+)*$/; 15 | const cpfRegex = /^\d{3}\.\d{3}\.\d{3}-\d{2}$/; 16 | const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; 17 | const senhaRegex = 18 | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; 19 | 20 | const registerSchema = z.object({ 21 | nome: z 22 | .string() 23 | .min(1, "Nome é obrigatório") 24 | .regex( 25 | nameRegex, 26 | "Nome deve ter pelo menos dois nomes, iniciando com maiúsculas" 27 | ), 28 | nascimento: z.string().optional(), 29 | cpf: z.string().min(1, "CPF é obrigatório").regex(cpfRegex, "CPF inválido"), 30 | email: z 31 | .string() 32 | .optional() 33 | .refine((val) => !val || emailRegex.test(val), "E-mail inválido"), 34 | senha: z 35 | .string() 36 | .min(1, "Senha é obrigatória") 37 | .regex( 38 | senhaRegex, 39 | "Senha deve conter 8+ caracteres, maiúsculas, minúsculas, números e caracteres especiais" 40 | ), 41 | }); 42 | 43 | export default function RegisterPage() { 44 | const router = useRouter(); 45 | const { handleLogin } = useAuth(); 46 | const [isLoading, setIsLoading] = useState(false); 47 | 48 | const { 49 | register, 50 | handleSubmit, 51 | formState: { errors }, 52 | } = useForm({ 53 | resolver: zodResolver(registerSchema), 54 | }); 55 | 56 | const onSubmit = async (data: any) => { 57 | try { 58 | setIsLoading(true); 59 | const formattedData = { 60 | ...data, 61 | cpf: data.cpf.replace(/\D/g, ""), 62 | senha: data.senha, 63 | }; 64 | const response = await api.auth.register(formattedData); 65 | 66 | await handleLogin(formattedData.cpf, formattedData.senha); 67 | 68 | router.push("/cursos"); 69 | } catch (error: any) { 70 | // toast({ 71 | // title: 'Erro ao cadastrar', 72 | // description: error.response?.data?.message || 'Ocorreu um erro ao cadastrar', 73 | // variant: 'destructive', 74 | // }); 75 | } finally { 76 | setIsLoading(false); 77 | } 78 | }; 79 | 80 | return ( 81 |
82 |
83 |

Cadastro

84 |
85 |
86 | 91 | {errors.nome && ( 92 |

93 | {errors.nome.message as string} 94 |

95 | )} 96 |
97 |
98 | 104 | {errors.nascimento && ( 105 |

106 | {errors.nascimento.message as string} 107 |

108 | )} 109 |
110 |
111 | 116 | {errors.cpf && ( 117 |

118 | {errors.cpf.message as string} 119 |

120 | )} 121 |
122 |
123 | 129 | {errors.email && ( 130 |

131 | {errors.email.message as string} 132 |

133 | )} 134 |
135 |
136 | 142 | {errors.senha && ( 143 |

144 | {errors.senha.message as string} 145 |

146 | )} 147 |
148 | 151 |
152 |

153 | Já tem uma conta?{" "} 154 | 155 | Faça login 156 | 157 |

158 |
159 |
160 | ); 161 | } 162 | -------------------------------------------------------------------------------- /api/mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /backend/mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /api/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /backend/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | --------------------------------------------------------------------------------