feat: backend auth, admin panel, role-based routing
This commit is contained in:
12
backend/src/main/java/com/magistr/app/Application.java
Normal file
12
backend/src/main/java/com/magistr/app/Application.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.magistr.app;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
14
backend/src/main/java/com/magistr/app/config/AppConfig.java
Normal file
14
backend/src/main/java/com/magistr/app/config/AppConfig.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.magistr.app.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.magistr.app.config;
|
||||
|
||||
import com.magistr.app.model.Role;
|
||||
import com.magistr.app.model.User;
|
||||
import com.magistr.app.repository.UserRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public class DataInitializer implements CommandLineRunner {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DataInitializer.class);
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
public DataInitializer(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
Optional<User> existing = userRepository.findByUsername("admin");
|
||||
|
||||
if (existing.isEmpty()) {
|
||||
User admin = new User();
|
||||
admin.setUsername("admin");
|
||||
admin.setPassword(passwordEncoder.encode("admin"));
|
||||
admin.setRole(Role.ADMIN);
|
||||
userRepository.save(admin);
|
||||
log.info("Created default admin user");
|
||||
} else {
|
||||
User admin = existing.get();
|
||||
if (!passwordEncoder.matches("admin", admin.getPassword())) {
|
||||
admin.setPassword(passwordEncoder.encode("admin"));
|
||||
admin.setRole(Role.ADMIN);
|
||||
userRepository.save(admin);
|
||||
log.info("Reset admin password (hash was invalid)");
|
||||
} else {
|
||||
log.info("Admin user already exists with correct password");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.magistr.app.controller;
|
||||
|
||||
import com.magistr.app.dto.LoginRequest;
|
||||
import com.magistr.app.dto.LoginResponse;
|
||||
import com.magistr.app.model.User;
|
||||
import com.magistr.app.repository.UserRepository;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
private static final Map<String, String> ROLE_REDIRECTS = Map.of(
|
||||
"ADMIN", "/admin/",
|
||||
"TEACHER", "/teacher/",
|
||||
"STUDENT", "/student/"
|
||||
);
|
||||
|
||||
public AuthController(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||
Optional<User> userOpt = userRepository.findByUsername(request.getUsername());
|
||||
|
||||
if (userOpt.isEmpty() ||
|
||||
!passwordEncoder.matches(request.getPassword(), userOpt.get().getPassword())) {
|
||||
return ResponseEntity
|
||||
.status(401)
|
||||
.body(new LoginResponse(false, "Неверное имя пользователя или пароль", null, null, null));
|
||||
}
|
||||
|
||||
User user = userOpt.get();
|
||||
String token = UUID.randomUUID().toString();
|
||||
String roleName = user.getRole().name();
|
||||
String redirect = ROLE_REDIRECTS.getOrDefault(roleName, "/");
|
||||
|
||||
return ResponseEntity.ok(new LoginResponse(true, "OK", token, roleName, redirect));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.magistr.app.controller;
|
||||
|
||||
import com.magistr.app.dto.CreateUserRequest;
|
||||
import com.magistr.app.dto.UserResponse;
|
||||
import com.magistr.app.model.Role;
|
||||
import com.magistr.app.model.User;
|
||||
import com.magistr.app.repository.UserRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
public class UserController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
public UserController(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<UserResponse> getAllUsers() {
|
||||
return userRepository.findAll().stream()
|
||||
.map(u -> new UserResponse(u.getId(), u.getUsername(), u.getRole().name()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<?> createUser(@RequestBody CreateUserRequest request) {
|
||||
if (request.getUsername() == null || request.getUsername().isBlank()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Имя пользователя обязательно"));
|
||||
}
|
||||
if (request.getPassword() == null || request.getPassword().length() < 4) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Пароль минимум 4 символа"));
|
||||
}
|
||||
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Пользователь уже существует"));
|
||||
}
|
||||
|
||||
Role role;
|
||||
try {
|
||||
role = Role.valueOf(request.getRole());
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Недопустимая роль"));
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
user.setUsername(request.getUsername());
|
||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||
user.setRole(role);
|
||||
userRepository.save(user);
|
||||
|
||||
return ResponseEntity.ok(new UserResponse(user.getId(), user.getUsername(), user.getRole().name()));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
|
||||
if (!userRepository.existsById(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
userRepository.deleteById(id);
|
||||
return ResponseEntity.ok(Map.of("message", "Пользователь удалён"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public class CreateUserRequest {
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
private String role;
|
||||
|
||||
public CreateUserRequest() {
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
26
backend/src/main/java/com/magistr/app/dto/LoginRequest.java
Normal file
26
backend/src/main/java/com/magistr/app/dto/LoginRequest.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public class LoginRequest {
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public LoginRequest() {
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
61
backend/src/main/java/com/magistr/app/dto/LoginResponse.java
Normal file
61
backend/src/main/java/com/magistr/app/dto/LoginResponse.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public class LoginResponse {
|
||||
|
||||
private boolean success;
|
||||
private String message;
|
||||
private String token;
|
||||
private String role;
|
||||
private String redirect;
|
||||
|
||||
public LoginResponse() {
|
||||
}
|
||||
|
||||
public LoginResponse(boolean success, String message, String token, String role, String redirect) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.token = token;
|
||||
this.role = role;
|
||||
this.redirect = redirect;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getRedirect() {
|
||||
return redirect;
|
||||
}
|
||||
|
||||
public void setRedirect(String redirect) {
|
||||
this.redirect = redirect;
|
||||
}
|
||||
}
|
||||
41
backend/src/main/java/com/magistr/app/dto/UserResponse.java
Normal file
41
backend/src/main/java/com/magistr/app/dto/UserResponse.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public class UserResponse {
|
||||
|
||||
private Long id;
|
||||
private String username;
|
||||
private String role;
|
||||
|
||||
public UserResponse() {
|
||||
}
|
||||
|
||||
public UserResponse(Long id, String username, String role) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
7
backend/src/main/java/com/magistr/app/model/Role.java
Normal file
7
backend/src/main/java/com/magistr/app/model/Role.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
public enum Role {
|
||||
ADMIN,
|
||||
TEACHER,
|
||||
STUDENT
|
||||
}
|
||||
57
backend/src/main/java/com/magistr/app/model/User.java
Normal file
57
backend/src/main/java/com/magistr/app/model/User.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true, nullable = false, length = 50)
|
||||
private String username;
|
||||
|
||||
@Column(nullable = false, length = 255)
|
||||
private String password;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, length = 20)
|
||||
private Role role = Role.STUDENT;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(Role role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
Optional<User> findByUsername(String username);
|
||||
}
|
||||
12
backend/src/main/resources/application.properties
Normal file
12
backend/src/main/resources/application.properties
Normal file
@@ -0,0 +1,12 @@
|
||||
server.port=8080
|
||||
|
||||
# PostgreSQL
|
||||
spring.datasource.url=jdbc:postgresql://db:5432/app_db
|
||||
spring.datasource.username=${POSTGRES_USER}
|
||||
spring.datasource.password=${POSTGRES_PASSWORD}
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# JPA
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
spring.jpa.show-sql=false
|
||||
spring.jpa.open-in-view=false
|
||||
Reference in New Issue
Block a user