mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-05-09 04:07:31 +08:00
Merge pull request #206 from nagisa77/codex/add-whitelist-invitation-registration-mode
Implement whitelist registration mode
This commit is contained in:
@@ -5,6 +5,8 @@ import com.openisle.model.PublishMode;
|
|||||||
import com.openisle.service.PasswordValidator;
|
import com.openisle.service.PasswordValidator;
|
||||||
import com.openisle.service.PostService;
|
import com.openisle.service.PostService;
|
||||||
import com.openisle.service.AiUsageService;
|
import com.openisle.service.AiUsageService;
|
||||||
|
import com.openisle.service.RegisterModeService;
|
||||||
|
import com.openisle.model.RegisterMode;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -16,6 +18,7 @@ public class AdminConfigController {
|
|||||||
private final PostService postService;
|
private final PostService postService;
|
||||||
private final PasswordValidator passwordValidator;
|
private final PasswordValidator passwordValidator;
|
||||||
private final AiUsageService aiUsageService;
|
private final AiUsageService aiUsageService;
|
||||||
|
private final RegisterModeService registerModeService;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ConfigDto getConfig() {
|
public ConfigDto getConfig() {
|
||||||
@@ -23,6 +26,7 @@ public class AdminConfigController {
|
|||||||
dto.setPublishMode(postService.getPublishMode());
|
dto.setPublishMode(postService.getPublishMode());
|
||||||
dto.setPasswordStrength(passwordValidator.getStrength());
|
dto.setPasswordStrength(passwordValidator.getStrength());
|
||||||
dto.setAiFormatLimit(aiUsageService.getFormatLimit());
|
dto.setAiFormatLimit(aiUsageService.getFormatLimit());
|
||||||
|
dto.setRegisterMode(registerModeService.getRegisterMode());
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +41,9 @@ public class AdminConfigController {
|
|||||||
if (dto.getAiFormatLimit() != null) {
|
if (dto.getAiFormatLimit() != null) {
|
||||||
aiUsageService.setFormatLimit(dto.getAiFormatLimit());
|
aiUsageService.setFormatLimit(dto.getAiFormatLimit());
|
||||||
}
|
}
|
||||||
|
if (dto.getRegisterMode() != null) {
|
||||||
|
registerModeService.setRegisterMode(dto.getRegisterMode());
|
||||||
|
}
|
||||||
return getConfig();
|
return getConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,5 +52,6 @@ public class AdminConfigController {
|
|||||||
private PublishMode publishMode;
|
private PublishMode publishMode;
|
||||||
private PasswordStrength passwordStrength;
|
private PasswordStrength passwordStrength;
|
||||||
private Integer aiFormatLimit;
|
private Integer aiFormatLimit;
|
||||||
|
private RegisterMode registerMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.openisle.controller;
|
||||||
|
|
||||||
|
import com.openisle.model.User;
|
||||||
|
import com.openisle.service.EmailSender;
|
||||||
|
import com.openisle.repository.UserRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/admin/users")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdminUserController {
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final EmailSender emailSender;
|
||||||
|
|
||||||
|
@PostMapping("/{id}/approve")
|
||||||
|
public ResponseEntity<?> approve(@PathVariable Long id) {
|
||||||
|
User user = userRepository.findById(id).orElseThrow();
|
||||||
|
user.setApproved(true);
|
||||||
|
userRepository.save(user);
|
||||||
|
emailSender.sendEmail(user.getEmail(), "Registration Approved",
|
||||||
|
"Your account has been approved. Visit: https://www.open-isle.com");
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/reject")
|
||||||
|
public ResponseEntity<?> reject(@PathVariable Long id) {
|
||||||
|
User user = userRepository.findById(id).orElseThrow();
|
||||||
|
user.setApproved(false);
|
||||||
|
userRepository.save(user);
|
||||||
|
emailSender.sendEmail(user.getEmail(), "Registration Rejected",
|
||||||
|
"Your account request was rejected. Visit: https://www.open-isle.com");
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import com.openisle.service.JwtService;
|
|||||||
import com.openisle.service.UserService;
|
import com.openisle.service.UserService;
|
||||||
import com.openisle.service.CaptchaService;
|
import com.openisle.service.CaptchaService;
|
||||||
import com.openisle.service.GoogleAuthService;
|
import com.openisle.service.GoogleAuthService;
|
||||||
|
import com.openisle.service.RegisterModeService;
|
||||||
|
import com.openisle.model.RegisterMode;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -23,6 +25,7 @@ public class AuthController {
|
|||||||
private final EmailSender emailService;
|
private final EmailSender emailService;
|
||||||
private final CaptchaService captchaService;
|
private final CaptchaService captchaService;
|
||||||
private final GoogleAuthService googleAuthService;
|
private final GoogleAuthService googleAuthService;
|
||||||
|
private final RegisterModeService registerModeService;
|
||||||
|
|
||||||
@Value("${app.captcha.enabled:false}")
|
@Value("${app.captcha.enabled:false}")
|
||||||
private boolean captchaEnabled;
|
private boolean captchaEnabled;
|
||||||
@@ -38,7 +41,8 @@ public class AuthController {
|
|||||||
if (captchaEnabled && registerCaptchaEnabled && !captchaService.verify(req.getCaptcha())) {
|
if (captchaEnabled && registerCaptchaEnabled && !captchaService.verify(req.getCaptcha())) {
|
||||||
return ResponseEntity.badRequest().body(Map.of("error", "Invalid captcha"));
|
return ResponseEntity.badRequest().body(Map.of("error", "Invalid captcha"));
|
||||||
}
|
}
|
||||||
User user = userService.register(req.getUsername(), req.getEmail(), req.getPassword());
|
User user = userService.register(
|
||||||
|
req.getUsername(), req.getEmail(), req.getPassword(), req.getReason(), registerModeService.getRegisterMode());
|
||||||
emailService.sendEmail(user.getEmail(), "Verification Code", "Your verification code is " + user.getVerificationCode());
|
emailService.sendEmail(user.getEmail(), "Verification Code", "Your verification code is " + user.getVerificationCode());
|
||||||
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
|
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
|
||||||
}
|
}
|
||||||
@@ -67,8 +71,11 @@ public class AuthController {
|
|||||||
|
|
||||||
@PostMapping("/google")
|
@PostMapping("/google")
|
||||||
public ResponseEntity<?> loginWithGoogle(@RequestBody GoogleLoginRequest req) {
|
public ResponseEntity<?> loginWithGoogle(@RequestBody GoogleLoginRequest req) {
|
||||||
Optional<User> user = googleAuthService.authenticate(req.getIdToken());
|
Optional<User> user = googleAuthService.authenticate(req.getIdToken(), req.getReason(), registerModeService.getRegisterMode());
|
||||||
if (user.isPresent()) {
|
if (user.isPresent()) {
|
||||||
|
if (!user.get().isApproved()) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "Account awaiting approval"));
|
||||||
|
}
|
||||||
return ResponseEntity.ok(Map.of("token", jwtService.generateToken(user.get().getUsername())));
|
return ResponseEntity.ok(Map.of("token", jwtService.generateToken(user.get().getUsername())));
|
||||||
}
|
}
|
||||||
return ResponseEntity.badRequest().body(Map.of("error", "Invalid google token"));
|
return ResponseEntity.badRequest().body(Map.of("error", "Invalid google token"));
|
||||||
@@ -85,6 +92,7 @@ public class AuthController {
|
|||||||
private String email;
|
private String email;
|
||||||
private String password;
|
private String password;
|
||||||
private String captcha;
|
private String captcha;
|
||||||
|
private String reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -97,6 +105,7 @@ public class AuthController {
|
|||||||
@Data
|
@Data
|
||||||
private static class GoogleLoginRequest {
|
private static class GoogleLoginRequest {
|
||||||
private String idToken;
|
private String idToken;
|
||||||
|
private String reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package com.openisle.controller;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import com.openisle.service.RegisterModeService;
|
||||||
|
import com.openisle.model.RegisterMode;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api")
|
@RequestMapping("/api")
|
||||||
|
@lombok.RequiredArgsConstructor
|
||||||
public class ConfigController {
|
public class ConfigController {
|
||||||
|
|
||||||
@Value("${app.captcha.enabled:false}")
|
@Value("${app.captcha.enabled:false}")
|
||||||
@@ -28,6 +31,8 @@ public class ConfigController {
|
|||||||
@Value("${app.ai.format-limit:3}")
|
@Value("${app.ai.format-limit:3}")
|
||||||
private int aiFormatLimit;
|
private int aiFormatLimit;
|
||||||
|
|
||||||
|
private final RegisterModeService registerModeService;
|
||||||
|
|
||||||
@GetMapping("/config")
|
@GetMapping("/config")
|
||||||
public ConfigResponse getConfig() {
|
public ConfigResponse getConfig() {
|
||||||
ConfigResponse resp = new ConfigResponse();
|
ConfigResponse resp = new ConfigResponse();
|
||||||
@@ -37,6 +42,7 @@ public class ConfigController {
|
|||||||
resp.setPostCaptchaEnabled(postCaptchaEnabled);
|
resp.setPostCaptchaEnabled(postCaptchaEnabled);
|
||||||
resp.setCommentCaptchaEnabled(commentCaptchaEnabled);
|
resp.setCommentCaptchaEnabled(commentCaptchaEnabled);
|
||||||
resp.setAiFormatLimit(aiFormatLimit);
|
resp.setAiFormatLimit(aiFormatLimit);
|
||||||
|
resp.setRegisterMode(registerModeService.getRegisterMode());
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,5 +54,6 @@ public class ConfigController {
|
|||||||
private boolean postCaptchaEnabled;
|
private boolean postCaptchaEnabled;
|
||||||
private boolean commentCaptchaEnabled;
|
private boolean commentCaptchaEnabled;
|
||||||
private int aiFormatLimit;
|
private int aiFormatLimit;
|
||||||
|
private RegisterMode registerMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ public class Notification {
|
|||||||
@Column(name = "reaction_type")
|
@Column(name = "reaction_type")
|
||||||
private ReactionType reactionType;
|
private ReactionType reactionType;
|
||||||
|
|
||||||
|
@Column(length = 1000)
|
||||||
|
private String content;
|
||||||
|
|
||||||
@Column
|
@Column
|
||||||
private Boolean approved;
|
private Boolean approved;
|
||||||
|
|
||||||
|
|||||||
@@ -27,5 +27,7 @@ public enum NotificationType {
|
|||||||
/** Someone unfollowed you */
|
/** Someone unfollowed you */
|
||||||
USER_UNFOLLOWED,
|
USER_UNFOLLOWED,
|
||||||
/** A user you subscribe to created a post or comment */
|
/** A user you subscribe to created a post or comment */
|
||||||
USER_ACTIVITY
|
USER_ACTIVITY,
|
||||||
|
/** A user requested registration approval */
|
||||||
|
REGISTER_REQUEST
|
||||||
}
|
}
|
||||||
|
|||||||
9
src/main/java/com/openisle/model/RegisterMode.java
Normal file
9
src/main/java/com/openisle/model/RegisterMode.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package com.openisle.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application-wide user registration mode.
|
||||||
|
*/
|
||||||
|
public enum RegisterMode {
|
||||||
|
DIRECT,
|
||||||
|
WHITELIST
|
||||||
|
}
|
||||||
@@ -42,6 +42,12 @@ public class User {
|
|||||||
@Column(length = 1000)
|
@Column(length = 1000)
|
||||||
private String introduction;
|
private String introduction;
|
||||||
|
|
||||||
|
@Column(length = 1000)
|
||||||
|
private String registerReason;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private boolean approved = true;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Role role = Role.USER;
|
private Role role = Role.USER;
|
||||||
|
|||||||
@@ -43,16 +43,16 @@ public class CommentService {
|
|||||||
comment.setContent(content);
|
comment.setContent(content);
|
||||||
comment = commentRepository.save(comment);
|
comment = commentRepository.save(comment);
|
||||||
if (!author.getId().equals(post.getAuthor().getId())) {
|
if (!author.getId().equals(post.getAuthor().getId())) {
|
||||||
notificationService.createNotification(post.getAuthor(), NotificationType.COMMENT_REPLY, post, comment, null);
|
notificationService.createNotification(post.getAuthor(), NotificationType.COMMENT_REPLY, post, comment, null, null, null, null);
|
||||||
}
|
}
|
||||||
for (User u : subscriptionService.getPostSubscribers(postId)) {
|
for (User u : subscriptionService.getPostSubscribers(postId)) {
|
||||||
if (!u.getId().equals(author.getId())) {
|
if (!u.getId().equals(author.getId())) {
|
||||||
notificationService.createNotification(u, NotificationType.POST_UPDATED, post, comment, null);
|
notificationService.createNotification(u, NotificationType.POST_UPDATED, post, comment, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
||||||
if (!u.getId().equals(author.getId())) {
|
if (!u.getId().equals(author.getId())) {
|
||||||
notificationService.createNotification(u, NotificationType.USER_ACTIVITY, post, comment, null);
|
notificationService.createNotification(u, NotificationType.USER_ACTIVITY, post, comment, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return comment;
|
return comment;
|
||||||
@@ -70,21 +70,21 @@ public class CommentService {
|
|||||||
comment.setContent(content);
|
comment.setContent(content);
|
||||||
comment = commentRepository.save(comment);
|
comment = commentRepository.save(comment);
|
||||||
if (!author.getId().equals(parent.getAuthor().getId())) {
|
if (!author.getId().equals(parent.getAuthor().getId())) {
|
||||||
notificationService.createNotification(parent.getAuthor(), NotificationType.COMMENT_REPLY, parent.getPost(), comment, null);
|
notificationService.createNotification(parent.getAuthor(), NotificationType.COMMENT_REPLY, parent.getPost(), comment, null, null, null, null);
|
||||||
}
|
}
|
||||||
for (User u : subscriptionService.getCommentSubscribers(parentId)) {
|
for (User u : subscriptionService.getCommentSubscribers(parentId)) {
|
||||||
if (!u.getId().equals(author.getId())) {
|
if (!u.getId().equals(author.getId())) {
|
||||||
notificationService.createNotification(u, NotificationType.COMMENT_REPLY, parent.getPost(), comment, null);
|
notificationService.createNotification(u, NotificationType.COMMENT_REPLY, parent.getPost(), comment, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (User u : subscriptionService.getPostSubscribers(parent.getPost().getId())) {
|
for (User u : subscriptionService.getPostSubscribers(parent.getPost().getId())) {
|
||||||
if (!u.getId().equals(author.getId())) {
|
if (!u.getId().equals(author.getId())) {
|
||||||
notificationService.createNotification(u, NotificationType.POST_UPDATED, parent.getPost(), comment, null);
|
notificationService.createNotification(u, NotificationType.POST_UPDATED, parent.getPost(), comment, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
||||||
if (!u.getId().equals(author.getId())) {
|
if (!u.getId().equals(author.getId())) {
|
||||||
notificationService.createNotification(u, NotificationType.USER_ACTIVITY, parent.getPost(), comment, null);
|
notificationService.createNotification(u, NotificationType.USER_ACTIVITY, parent.getPost(), comment, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return comment;
|
return comment;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class GoogleAuthService {
|
|||||||
@Value("${google.client-id:}")
|
@Value("${google.client-id:}")
|
||||||
private String clientId;
|
private String clientId;
|
||||||
|
|
||||||
public Optional<User> authenticate(String idTokenString) {
|
public Optional<User> authenticate(String idTokenString, String reason, com.openisle.model.RegisterMode mode) {
|
||||||
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
|
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
|
||||||
.setAudience(Collections.singletonList(clientId))
|
.setAudience(Collections.singletonList(clientId))
|
||||||
.build();
|
.build();
|
||||||
@@ -35,13 +35,13 @@ public class GoogleAuthService {
|
|||||||
GoogleIdToken.Payload payload = idToken.getPayload();
|
GoogleIdToken.Payload payload = idToken.getPayload();
|
||||||
String email = payload.getEmail();
|
String email = payload.getEmail();
|
||||||
String name = (String) payload.get("name");
|
String name = (String) payload.get("name");
|
||||||
return Optional.of(processUser(email, name));
|
return Optional.of(processUser(email, name, reason, mode));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private User processUser(String email, String name) {
|
private User processUser(String email, String name, String reason, com.openisle.model.RegisterMode mode) {
|
||||||
Optional<User> existing = userRepository.findByEmail(email);
|
Optional<User> existing = userRepository.findByEmail(email);
|
||||||
if (existing.isPresent()) {
|
if (existing.isPresent()) {
|
||||||
User user = existing.get();
|
User user = existing.get();
|
||||||
@@ -64,6 +64,8 @@ public class GoogleAuthService {
|
|||||||
user.setPassword("");
|
user.setPassword("");
|
||||||
user.setRole(Role.USER);
|
user.setRole(Role.USER);
|
||||||
user.setVerified(true);
|
user.setVerified(true);
|
||||||
|
user.setRegisterReason(reason);
|
||||||
|
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||||
user.setAvatar("https://github.com/identicons/" + username + ".png");
|
user.setAvatar("https://github.com/identicons/" + username + ".png");
|
||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ public class NotificationService {
|
|||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
public Notification createNotification(User user, NotificationType type, Post post, Comment comment, Boolean approved) {
|
public Notification createNotification(User user, NotificationType type, Post post, Comment comment, Boolean approved) {
|
||||||
return createNotification(user, type, post, comment, approved, null, null);
|
return createNotification(user, type, post, comment, approved, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Notification createNotification(User user, NotificationType type, Post post, Comment comment, Boolean approved,
|
public Notification createNotification(User user, NotificationType type, Post post, Comment comment, Boolean approved,
|
||||||
User fromUser, ReactionType reactionType) {
|
User fromUser, ReactionType reactionType, String content) {
|
||||||
Notification n = new Notification();
|
Notification n = new Notification();
|
||||||
n.setUser(user);
|
n.setUser(user);
|
||||||
n.setType(type);
|
n.setType(type);
|
||||||
@@ -29,6 +29,7 @@ public class NotificationService {
|
|||||||
n.setApproved(approved);
|
n.setApproved(approved);
|
||||||
n.setFromUser(fromUser);
|
n.setFromUser(fromUser);
|
||||||
n.setReactionType(reactionType);
|
n.setReactionType(reactionType);
|
||||||
|
n.setContent(content);
|
||||||
return notificationRepository.save(n);
|
return notificationRepository.save(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,10 +109,10 @@ public class PostService {
|
|||||||
java.util.List<User> admins = userRepository.findByRole(com.openisle.model.Role.ADMIN);
|
java.util.List<User> admins = userRepository.findByRole(com.openisle.model.Role.ADMIN);
|
||||||
for (User admin : admins) {
|
for (User admin : admins) {
|
||||||
notificationService.createNotification(admin,
|
notificationService.createNotification(admin,
|
||||||
NotificationType.POST_REVIEW_REQUEST, post, null, null, author, null);
|
NotificationType.POST_REVIEW_REQUEST, post, null, null, author, null, null);
|
||||||
}
|
}
|
||||||
notificationService.createNotification(author,
|
notificationService.createNotification(author,
|
||||||
NotificationType.POST_REVIEW_REQUEST, post, null, null, null, null);
|
NotificationType.POST_REVIEW_REQUEST, post, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
// notify followers of author
|
// notify followers of author
|
||||||
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
for (User u : subscriptionService.getSubscribers(author.getUsername())) {
|
||||||
@@ -124,6 +124,7 @@ public class PostService {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
author,
|
author,
|
||||||
|
null,
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,9 +152,9 @@ public class PostService {
|
|||||||
if (viewer != null && !viewer.equals(post.getAuthor().getUsername())) {
|
if (viewer != null && !viewer.equals(post.getAuthor().getUsername())) {
|
||||||
User viewerUser = userRepository.findByUsername(viewer).orElse(null);
|
User viewerUser = userRepository.findByUsername(viewer).orElse(null);
|
||||||
if (viewerUser != null) {
|
if (viewerUser != null) {
|
||||||
notificationService.createNotification(post.getAuthor(), NotificationType.POST_VIEWED, post, null, null, viewerUser, null);
|
notificationService.createNotification(post.getAuthor(), NotificationType.POST_VIEWED, post, null, null, viewerUser, null, null);
|
||||||
} else {
|
} else {
|
||||||
notificationService.createNotification(post.getAuthor(), NotificationType.POST_VIEWED, post, null, null);
|
notificationService.createNotification(post.getAuthor(), NotificationType.POST_VIEWED, post, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return post;
|
return post;
|
||||||
@@ -321,7 +322,7 @@ public class PostService {
|
|||||||
}
|
}
|
||||||
post.setStatus(PostStatus.PUBLISHED);
|
post.setStatus(PostStatus.PUBLISHED);
|
||||||
post = postRepository.save(post);
|
post = postRepository.save(post);
|
||||||
notificationService.createNotification(post.getAuthor(), NotificationType.POST_REVIEWED, post, null, true);
|
notificationService.createNotification(post.getAuthor(), NotificationType.POST_REVIEWED, post, null, true, null, null, null);
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,7 +342,7 @@ public class PostService {
|
|||||||
}
|
}
|
||||||
post.setStatus(PostStatus.REJECTED);
|
post.setStatus(PostStatus.REJECTED);
|
||||||
post = postRepository.save(post);
|
post = postRepository.save(post);
|
||||||
notificationService.createNotification(post.getAuthor(), NotificationType.POST_REVIEWED, post, null, false);
|
notificationService.createNotification(post.getAuthor(), NotificationType.POST_REVIEWED, post, null, false, null, null, null);
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class ReactionService {
|
|||||||
reaction.setType(type);
|
reaction.setType(type);
|
||||||
reaction = reactionRepository.save(reaction);
|
reaction = reactionRepository.save(reaction);
|
||||||
if (!user.getId().equals(post.getAuthor().getId())) {
|
if (!user.getId().equals(post.getAuthor().getId())) {
|
||||||
notificationService.createNotification(post.getAuthor(), NotificationType.REACTION, post, null, null, user, type);
|
notificationService.createNotification(post.getAuthor(), NotificationType.REACTION, post, null, null, user, type, null);
|
||||||
}
|
}
|
||||||
return reaction;
|
return reaction;
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ public class ReactionService {
|
|||||||
reaction.setType(type);
|
reaction.setType(type);
|
||||||
reaction = reactionRepository.save(reaction);
|
reaction = reactionRepository.save(reaction);
|
||||||
if (!user.getId().equals(comment.getAuthor().getId())) {
|
if (!user.getId().equals(comment.getAuthor().getId())) {
|
||||||
notificationService.createNotification(comment.getAuthor(), NotificationType.REACTION, comment.getPost(), comment, null, user, type);
|
notificationService.createNotification(comment.getAuthor(), NotificationType.REACTION, comment.getPost(), comment, null, user, type, null);
|
||||||
}
|
}
|
||||||
return reaction;
|
return reaction;
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/main/java/com/openisle/service/RegisterModeService.java
Normal file
25
src/main/java/com/openisle/service/RegisterModeService.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package com.openisle.service;
|
||||||
|
|
||||||
|
import com.openisle.model.RegisterMode;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds current registration mode. Configurable at runtime.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class RegisterModeService {
|
||||||
|
private RegisterMode registerMode;
|
||||||
|
|
||||||
|
public RegisterModeService(@Value("${app.register.mode:WHITELIST}") RegisterMode registerMode) {
|
||||||
|
this.registerMode = registerMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegisterMode getRegisterMode() {
|
||||||
|
return registerMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegisterMode(RegisterMode mode) {
|
||||||
|
this.registerMode = mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ public class SubscriptionService {
|
|||||||
ps.setPost(post);
|
ps.setPost(post);
|
||||||
if (!user.getId().equals(post.getAuthor().getId())) {
|
if (!user.getId().equals(post.getAuthor().getId())) {
|
||||||
notificationService.createNotification(post.getAuthor(),
|
notificationService.createNotification(post.getAuthor(),
|
||||||
NotificationType.POST_SUBSCRIBED, post, null, null, user, null);
|
NotificationType.POST_SUBSCRIBED, post, null, null, user, null, null);
|
||||||
}
|
}
|
||||||
return postSubRepo.save(ps);
|
return postSubRepo.save(ps);
|
||||||
});
|
});
|
||||||
@@ -41,7 +41,7 @@ public class SubscriptionService {
|
|||||||
postSubRepo.delete(ps);
|
postSubRepo.delete(ps);
|
||||||
if (!user.getId().equals(post.getAuthor().getId())) {
|
if (!user.getId().equals(post.getAuthor().getId())) {
|
||||||
notificationService.createNotification(post.getAuthor(),
|
notificationService.createNotification(post.getAuthor(),
|
||||||
NotificationType.POST_UNSUBSCRIBED, post, null, null, user, null);
|
NotificationType.POST_UNSUBSCRIBED, post, null, null, user, null, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ public class SubscriptionService {
|
|||||||
us.setSubscriber(subscriber);
|
us.setSubscriber(subscriber);
|
||||||
us.setTarget(target);
|
us.setTarget(target);
|
||||||
notificationService.createNotification(target,
|
notificationService.createNotification(target,
|
||||||
NotificationType.USER_FOLLOWED, null, null, null, subscriber, null);
|
NotificationType.USER_FOLLOWED, null, null, null, subscriber, null, null);
|
||||||
return userSubRepo.save(us);
|
return userSubRepo.save(us);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ public class SubscriptionService {
|
|||||||
userSubRepo.findBySubscriberAndTarget(subscriber, target).ifPresent(us -> {
|
userSubRepo.findBySubscriberAndTarget(subscriber, target).ifPresent(us -> {
|
||||||
userSubRepo.delete(us);
|
userSubRepo.delete(us);
|
||||||
notificationService.createNotification(target,
|
notificationService.createNotification(target,
|
||||||
NotificationType.USER_UNFOLLOWED, null, null, null, subscriber, null);
|
NotificationType.USER_UNFOLLOWED, null, null, null, subscriber, null, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class UserService {
|
|||||||
private final UsernameValidator usernameValidator;
|
private final UsernameValidator usernameValidator;
|
||||||
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||||
|
|
||||||
public User register(String username, String email, String password) {
|
public User register(String username, String email, String password, String reason, com.openisle.model.RegisterMode mode) {
|
||||||
usernameValidator.validate(username);
|
usernameValidator.validate(username);
|
||||||
passwordValidator.validate(password);
|
passwordValidator.validate(password);
|
||||||
// ── 先按用户名查 ──────────────────────────────────────────
|
// ── 先按用户名查 ──────────────────────────────────────────
|
||||||
@@ -36,6 +36,8 @@ public class UserService {
|
|||||||
u.setEmail(email); // 若不允许改邮箱可去掉
|
u.setEmail(email); // 若不允许改邮箱可去掉
|
||||||
u.setPassword(passwordEncoder.encode(password));
|
u.setPassword(passwordEncoder.encode(password));
|
||||||
u.setVerificationCode(genCode());
|
u.setVerificationCode(genCode());
|
||||||
|
u.setRegisterReason(reason);
|
||||||
|
u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||||
return userRepository.save(u);
|
return userRepository.save(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +52,8 @@ public class UserService {
|
|||||||
u.setUsername(username); // 若不允许改用户名可去掉
|
u.setUsername(username); // 若不允许改用户名可去掉
|
||||||
u.setPassword(passwordEncoder.encode(password));
|
u.setPassword(passwordEncoder.encode(password));
|
||||||
u.setVerificationCode(genCode());
|
u.setVerificationCode(genCode());
|
||||||
|
u.setRegisterReason(reason);
|
||||||
|
u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||||
return userRepository.save(u);
|
return userRepository.save(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +66,8 @@ public class UserService {
|
|||||||
user.setVerified(false);
|
user.setVerified(false);
|
||||||
user.setVerificationCode(genCode());
|
user.setVerificationCode(genCode());
|
||||||
user.setAvatar("https://github.com/identicons/" + username + ".png");
|
user.setAvatar("https://github.com/identicons/" + username + ".png");
|
||||||
|
user.setRegisterReason(reason);
|
||||||
|
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
|
||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +90,7 @@ public class UserService {
|
|||||||
public Optional<User> authenticate(String username, String password) {
|
public Optional<User> authenticate(String username, String password) {
|
||||||
return userRepository.findByUsername(username)
|
return userRepository.findByUsername(username)
|
||||||
.filter(User::isVerified)
|
.filter(User::isVerified)
|
||||||
|
.filter(User::isApproved)
|
||||||
.filter(user -> passwordEncoder.matches(password, user.getPassword()));
|
.filter(user -> passwordEncoder.matches(password, user.getPassword()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ app.password.strength=${PASSWORD_STRENGTH:LOW}
|
|||||||
# Post publish mode: DIRECT or REVIEW
|
# Post publish mode: DIRECT or REVIEW
|
||||||
app.post.publish-mode=${POST_PUBLISH_MODE:DIRECT}
|
app.post.publish-mode=${POST_PUBLISH_MODE:DIRECT}
|
||||||
|
|
||||||
|
# User register mode: DIRECT or WHITELIST
|
||||||
|
app.register.mode=${REGISTER_MODE:WHITELIST}
|
||||||
|
|
||||||
# Image upload configuration
|
# Image upload configuration
|
||||||
app.upload.check-type=${UPLOAD_CHECK_TYPE:true}
|
app.upload.check-type=${UPLOAD_CHECK_TYPE:true}
|
||||||
app.upload.max-size=${UPLOAD_MAX_SIZE:5242880}
|
app.upload.max-size=${UPLOAD_MAX_SIZE:5242880}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.openisle.controller;
|
|||||||
|
|
||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
import com.openisle.service.*;
|
import com.openisle.service.*;
|
||||||
|
import com.openisle.model.RegisterMode;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -36,6 +37,8 @@ class AuthControllerTest {
|
|||||||
private CaptchaService captchaService;
|
private CaptchaService captchaService;
|
||||||
@MockBean
|
@MockBean
|
||||||
private GoogleAuthService googleAuthService;
|
private GoogleAuthService googleAuthService;
|
||||||
|
@MockBean
|
||||||
|
private RegisterModeService registerModeService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void registerSendsEmail() throws Exception {
|
void registerSendsEmail() throws Exception {
|
||||||
@@ -43,11 +46,12 @@ class AuthControllerTest {
|
|||||||
user.setEmail("a@b.com");
|
user.setEmail("a@b.com");
|
||||||
user.setUsername("u");
|
user.setUsername("u");
|
||||||
user.setVerificationCode("123456");
|
user.setVerificationCode("123456");
|
||||||
Mockito.when(userService.register(eq("u"), eq("a@b.com"), eq("p"))).thenReturn(user);
|
Mockito.when(registerModeService.getRegisterMode()).thenReturn(RegisterMode.DIRECT);
|
||||||
|
Mockito.when(userService.register(eq("u"), eq("a@b.com"), eq("p"), any(), eq(RegisterMode.DIRECT))).thenReturn(user);
|
||||||
|
|
||||||
mockMvc.perform(post("/api/auth/register")
|
mockMvc.perform(post("/api/auth/register")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content("{\"username\":\"u\",\"email\":\"a@b.com\",\"password\":\"p\"}"))
|
.content("{\"username\":\"u\",\"email\":\"a@b.com\",\"password\":\"p\",\"reason\":\"test reason more than twenty\"}"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.message").exists());
|
.andExpect(jsonPath("$.message").exists());
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
|
properties = "app.register.mode=DIRECT")
|
||||||
class ComplexFlowIntegrationTest {
|
class ComplexFlowIntegrationTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -33,7 +34,7 @@ class ComplexFlowIntegrationTest {
|
|||||||
HttpHeaders h = new HttpHeaders();
|
HttpHeaders h = new HttpHeaders();
|
||||||
h.setContentType(MediaType.APPLICATION_JSON);
|
h.setContentType(MediaType.APPLICATION_JSON);
|
||||||
rest.postForEntity("/api/auth/register", new HttpEntity<>(
|
rest.postForEntity("/api/auth/register", new HttpEntity<>(
|
||||||
Map.of("username", username, "email", email, "password", "pass123"), h), Map.class);
|
Map.of("username", username, "email", email, "password", "pass123", "reason", "integration test reason more than twenty"), h), Map.class);
|
||||||
User u = users.findByUsername(username).orElseThrow();
|
User u = users.findByUsername(username).orElseThrow();
|
||||||
if (u.getVerificationCode() != null) {
|
if (u.getVerificationCode() != null) {
|
||||||
rest.postForEntity("/api/auth/verify", new HttpEntity<>(
|
rest.postForEntity("/api/auth/verify", new HttpEntity<>(
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
|
|
||||||
/** Integration tests for review publish mode. */
|
/** Integration tests for review publish mode. */
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
properties = "app.post.publish-mode=REVIEW")
|
properties = {"app.post.publish-mode=REVIEW","app.register.mode=DIRECT"})
|
||||||
class PublishModeIntegrationTest {
|
class PublishModeIntegrationTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -34,7 +34,7 @@ class PublishModeIntegrationTest {
|
|||||||
HttpHeaders h = new HttpHeaders();
|
HttpHeaders h = new HttpHeaders();
|
||||||
h.setContentType(MediaType.APPLICATION_JSON);
|
h.setContentType(MediaType.APPLICATION_JSON);
|
||||||
rest.postForEntity("/api/auth/register", new HttpEntity<>(
|
rest.postForEntity("/api/auth/register", new HttpEntity<>(
|
||||||
Map.of("username", username, "email", email, "password", "pass123"), h), Map.class);
|
Map.of("username", username, "email", email, "password", "pass123", "reason", "integration test reason more than twenty"), h), Map.class);
|
||||||
User u = users.findByUsername(username).orElseThrow();
|
User u = users.findByUsername(username).orElseThrow();
|
||||||
if (u.getVerificationCode() != null) {
|
if (u.getVerificationCode() != null) {
|
||||||
rest.postForEntity("/api/auth/verify", new HttpEntity<>(
|
rest.postForEntity("/api/auth/verify", new HttpEntity<>(
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
|
properties = "app.register.mode=DIRECT")
|
||||||
class SearchIntegrationTest {
|
class SearchIntegrationTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
private TestRestTemplate rest;
|
private TestRestTemplate rest;
|
||||||
@@ -29,7 +30,7 @@ class SearchIntegrationTest {
|
|||||||
HttpHeaders h = new HttpHeaders();
|
HttpHeaders h = new HttpHeaders();
|
||||||
h.setContentType(MediaType.APPLICATION_JSON);
|
h.setContentType(MediaType.APPLICATION_JSON);
|
||||||
rest.postForEntity("/api/auth/register", new HttpEntity<>(
|
rest.postForEntity("/api/auth/register", new HttpEntity<>(
|
||||||
Map.of("username", username, "email", email, "password", "pass123"), h), Map.class);
|
Map.of("username", username, "email", email, "password", "pass123", "reason", "integration test reason more than twenty"), h), Map.class);
|
||||||
User u = users.findByUsername(username).orElseThrow();
|
User u = users.findByUsername(username).orElseThrow();
|
||||||
if (u.getVerificationCode() != null) {
|
if (u.getVerificationCode() != null) {
|
||||||
rest.postForEntity("/api/auth/verify", new HttpEntity<>(
|
rest.postForEntity("/api/auth/verify", new HttpEntity<>(
|
||||||
|
|||||||
Reference in New Issue
Block a user