Merge branch 'original' of http://8.148.151.56:3000/tonytech/kindergarten into dev_unocss
This commit is contained in:
commit
327fc9fe8d
@ -1,6 +1,7 @@
|
||||
package com.reading.platform.common.response;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -15,10 +16,13 @@ public class PageResult<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@JsonProperty("items")
|
||||
private List<T> list;
|
||||
private Long total;
|
||||
@JsonProperty("page")
|
||||
private Long pageNum;
|
||||
private Long pageSize;
|
||||
@JsonProperty("totalPages")
|
||||
private Long pages;
|
||||
|
||||
public static <T> PageResult<T> of(List<T> list, Long total, Long pageNum, Long pageSize) {
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package com.reading.platform.common.serializer;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Jackson serializer that converts string values to uppercase.
|
||||
* Used to normalize status fields from DB (lowercase) to JSON output (uppercase).
|
||||
*/
|
||||
public class UpperCaseSerializer extends JsonSerializer<String> {
|
||||
|
||||
@Override
|
||||
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeString(value != null ? value.toUpperCase() : null);
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "Auth", description = "Authentication APIs")
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@RequestMapping("/api/v1/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthController {
|
||||
|
||||
@ -25,6 +25,13 @@ public class AuthController {
|
||||
return Result.success(authService.login(request));
|
||||
}
|
||||
|
||||
@Operation(summary = "User logout")
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout() {
|
||||
// JWT is stateless - client simply discards the token
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "Get current user info")
|
||||
@GetMapping("/me")
|
||||
public Result<UserInfoResponse> getCurrentUser() {
|
||||
|
||||
@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "Admin - Course", description = "System Course Management APIs for Admin")
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/courses")
|
||||
@RequestMapping("/api/v1/admin/courses")
|
||||
@RequiredArgsConstructor
|
||||
@RequireRole(UserRole.ADMIN)
|
||||
public class AdminCourseController {
|
||||
@ -48,7 +48,7 @@ public class AdminCourseController {
|
||||
@Operation(summary = "Get system course page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Course>> getCoursePage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String category) {
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
package com.reading.platform.controller.admin;
|
||||
|
||||
import com.reading.platform.common.annotation.RequireRole;
|
||||
import com.reading.platform.common.enums.UserRole;
|
||||
import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.service.SystemSettingService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "Admin - Settings", description = "Admin System Settings Management")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/admin/settings")
|
||||
@RequiredArgsConstructor
|
||||
@RequireRole(UserRole.ADMIN)
|
||||
public class AdminSettingsController {
|
||||
|
||||
// Use tenantId=0 for global admin settings
|
||||
private static final Long ADMIN_TENANT_ID = 0L;
|
||||
|
||||
private final SystemSettingService systemSettingService;
|
||||
|
||||
@Operation(summary = "Get admin system settings")
|
||||
@GetMapping
|
||||
public Result<Map<String, String>> getSettings() {
|
||||
return Result.success(systemSettingService.getSettings(ADMIN_TENANT_ID));
|
||||
}
|
||||
|
||||
@Operation(summary = "Update admin system settings")
|
||||
@PutMapping
|
||||
public Result<Void> updateSettings(@RequestBody Map<String, Object> settings) {
|
||||
Map<String, String> stringSettings = new java.util.HashMap<>();
|
||||
settings.forEach((k, v) -> {
|
||||
if (v != null) {
|
||||
stringSettings.put(k, v.toString());
|
||||
}
|
||||
});
|
||||
systemSettingService.updateSettings(ADMIN_TENANT_ID, stringSettings);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@ -16,11 +16,13 @@ import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "Admin - Tenant", description = "Tenant Management APIs for Admin")
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/tenants")
|
||||
@RequestMapping("/api/v1/admin/tenants")
|
||||
@RequiredArgsConstructor
|
||||
@RequireRole(UserRole.ADMIN)
|
||||
public class AdminTenantController {
|
||||
@ -48,10 +50,11 @@ public class AdminTenantController {
|
||||
@Operation(summary = "Get tenant page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Tenant>> getTenantPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String status) {
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(required = false) String packageType) {
|
||||
Page<Tenant> page = tenantService.getTenantPage(pageNum, pageSize, keyword, status);
|
||||
return Result.success(PageResult.of(page));
|
||||
}
|
||||
@ -69,4 +72,45 @@ public class AdminTenantController {
|
||||
return Result.success(tenantService.getAllActiveTenants());
|
||||
}
|
||||
|
||||
@Operation(summary = "Update tenant status")
|
||||
@PutMapping("/{id}/status")
|
||||
public Result<Map<String, Object>> updateTenantStatus(@PathVariable Long id, @RequestBody Map<String, String> body) {
|
||||
String status = body.get("status");
|
||||
// Normalize status to lowercase for DB
|
||||
String dbStatus = status != null ? status.toLowerCase() : "active";
|
||||
TenantUpdateRequest req = new TenantUpdateRequest();
|
||||
req.setStatus(dbStatus);
|
||||
Tenant tenant = tenantService.updateTenant(id, req);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("id", tenant.getId());
|
||||
result.put("name", tenant.getName());
|
||||
result.put("status", tenant.getStatus());
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "Reset tenant school account password")
|
||||
@PostMapping("/{id}/reset-password")
|
||||
public Result<Map<String, String>> resetTenantPassword(@PathVariable Long id) {
|
||||
String tempPassword = tenantService.resetSchoolAccountPassword(id);
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("tempPassword", tempPassword);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "Update tenant quota")
|
||||
@PutMapping("/{id}/quota")
|
||||
public Result<Tenant> updateTenantQuota(@PathVariable Long id, @RequestBody Map<String, Object> body) {
|
||||
TenantUpdateRequest req = new TenantUpdateRequest();
|
||||
if (body.get("teacherQuota") != null) {
|
||||
req.setMaxTeachers(Integer.parseInt(body.get("teacherQuota").toString()));
|
||||
}
|
||||
if (body.get("studentQuota") != null) {
|
||||
req.setMaxStudents(Integer.parseInt(body.get("studentQuota").toString()));
|
||||
}
|
||||
if (body.get("packageType") != null) {
|
||||
req.setPackageType(body.get("packageType").toString());
|
||||
}
|
||||
return Result.success(tenantService.updateTenant(id, req));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ import java.util.List;
|
||||
|
||||
@Tag(name = "Parent - Growth Record", description = "Growth Record APIs for Parent")
|
||||
@RestController
|
||||
@RequestMapping("/api/parent/growth-records")
|
||||
@RequestMapping("/api/v1/parent/growth-records")
|
||||
@RequiredArgsConstructor
|
||||
public class ParentGrowthController {
|
||||
|
||||
@ -48,7 +48,7 @@ public class ParentGrowthController {
|
||||
@GetMapping("/student/{studentId}")
|
||||
public Result<PageResult<GrowthRecord>> getGrowthRecordsByStudent(
|
||||
@PathVariable Long studentId,
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String type) {
|
||||
Page<GrowthRecord> page = growthRecordService.getGrowthRecordsByStudentId(studentId, pageNum, pageSize, type);
|
||||
|
||||
@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "Parent - Notification", description = "Notification APIs for Parent")
|
||||
@RestController
|
||||
@RequestMapping("/api/parent/notifications")
|
||||
@RequestMapping("/api/v1/parent/notifications")
|
||||
@RequiredArgsConstructor
|
||||
public class ParentNotificationController {
|
||||
|
||||
@ -28,7 +28,7 @@ public class ParentNotificationController {
|
||||
@Operation(summary = "Get my notifications")
|
||||
@GetMapping
|
||||
public Result<PageResult<Notification>> getMyNotifications(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) Integer isRead) {
|
||||
Long userId = SecurityUtils.getCurrentUserId();
|
||||
|
||||
@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "Parent - Task", description = "Task APIs for Parent")
|
||||
@RestController
|
||||
@RequestMapping("/api/parent/tasks")
|
||||
@RequestMapping("/api/v1/parent/tasks")
|
||||
@RequiredArgsConstructor
|
||||
public class ParentTaskController {
|
||||
|
||||
@ -29,7 +29,7 @@ public class ParentTaskController {
|
||||
@GetMapping("/student/{studentId}")
|
||||
public Result<PageResult<Task>> getTasksByStudent(
|
||||
@PathVariable Long studentId,
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String status) {
|
||||
Page<Task> page = taskService.getTasksByStudentId(studentId, pageNum, pageSize, status);
|
||||
|
||||
@ -18,7 +18,7 @@ import java.util.List;
|
||||
|
||||
@Tag(name = "School - Class", description = "Class Management APIs for School")
|
||||
@RestController
|
||||
@RequestMapping("/api/school/classes")
|
||||
@RequestMapping("/api/v1/school/classes")
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolClassController {
|
||||
|
||||
@ -46,7 +46,7 @@ public class SchoolClassController {
|
||||
@Operation(summary = "Get class page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Clazz>> getClassPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String grade,
|
||||
|
||||
@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "School - Growth Record", description = "Growth Record Management APIs for School")
|
||||
@RestController
|
||||
@RequestMapping("/api/school/growth-records")
|
||||
@RequestMapping("/api/v1/school/growth-records")
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolGrowthController {
|
||||
|
||||
@ -46,7 +46,7 @@ public class SchoolGrowthController {
|
||||
@Operation(summary = "Get growth record page")
|
||||
@GetMapping
|
||||
public Result<PageResult<GrowthRecord>> getGrowthRecordPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) Long studentId,
|
||||
@RequestParam(required = false) String type) {
|
||||
|
||||
@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "School - Parent", description = "Parent Management APIs for School")
|
||||
@RestController
|
||||
@RequestMapping("/api/school/parents")
|
||||
@RequestMapping("/api/v1/school/parents")
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolParentController {
|
||||
|
||||
@ -44,7 +44,7 @@ public class SchoolParentController {
|
||||
@Operation(summary = "Get parent page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Parent>> getParentPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String status) {
|
||||
|
||||
@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "School - Student", description = "Student Management APIs for School")
|
||||
@RestController
|
||||
@RequestMapping("/api/school/students")
|
||||
@RequestMapping("/api/v1/school/students")
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolStudentController {
|
||||
|
||||
@ -44,7 +44,7 @@ public class SchoolStudentController {
|
||||
@Operation(summary = "Get student page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Student>> getStudentPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String grade,
|
||||
|
||||
@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "School - Task", description = "Task Management APIs for School")
|
||||
@RestController
|
||||
@RequestMapping("/api/school/tasks")
|
||||
@RequestMapping("/api/v1/school/tasks")
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolTaskController {
|
||||
|
||||
@ -46,7 +46,7 @@ public class SchoolTaskController {
|
||||
@Operation(summary = "Get task page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Task>> getTaskPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String type,
|
||||
|
||||
@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "School - Teacher", description = "Teacher Management APIs for School")
|
||||
@RestController
|
||||
@RequestMapping("/api/school/teachers")
|
||||
@RequestMapping("/api/v1/school/teachers")
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolTeacherController {
|
||||
|
||||
@ -44,7 +44,7 @@ public class SchoolTeacherController {
|
||||
@Operation(summary = "Get teacher page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Teacher>> getTeacherPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String status) {
|
||||
|
||||
@ -15,7 +15,7 @@ import java.util.List;
|
||||
|
||||
@Tag(name = "Teacher - Course", description = "Course APIs for Teacher")
|
||||
@RestController
|
||||
@RequestMapping("/api/teacher/courses")
|
||||
@RequestMapping("/api/v1/teacher/courses")
|
||||
@RequiredArgsConstructor
|
||||
public class TeacherCourseController {
|
||||
|
||||
@ -30,7 +30,7 @@ public class TeacherCourseController {
|
||||
@Operation(summary = "Get course page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Course>> getCoursePage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String category) {
|
||||
|
||||
@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "Teacher - Growth Record", description = "Growth Record APIs for Teacher")
|
||||
@RestController
|
||||
@RequestMapping("/api/teacher/growth-records")
|
||||
@RequestMapping("/api/v1/teacher/growth-records")
|
||||
@RequiredArgsConstructor
|
||||
public class TeacherGrowthController {
|
||||
|
||||
@ -45,7 +45,7 @@ public class TeacherGrowthController {
|
||||
@Operation(summary = "Get growth record page")
|
||||
@GetMapping
|
||||
public Result<PageResult<GrowthRecord>> getGrowthRecordPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) Long studentId,
|
||||
@RequestParam(required = false) String type) {
|
||||
|
||||
@ -20,7 +20,7 @@ import java.util.List;
|
||||
|
||||
@Tag(name = "Teacher - Lesson", description = "Lesson APIs for Teacher")
|
||||
@RestController
|
||||
@RequestMapping("/api/teacher/lessons")
|
||||
@RequestMapping("/api/v1/teacher/lessons")
|
||||
@RequiredArgsConstructor
|
||||
public class TeacherLessonController {
|
||||
|
||||
@ -48,7 +48,7 @@ public class TeacherLessonController {
|
||||
@Operation(summary = "Get my lessons")
|
||||
@GetMapping
|
||||
public Result<PageResult<Lesson>> getMyLessons(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
|
||||
|
||||
@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "Teacher - Notification", description = "Notification APIs for Teacher")
|
||||
@RestController
|
||||
@RequestMapping("/api/teacher/notifications")
|
||||
@RequestMapping("/api/v1/teacher/notifications")
|
||||
@RequiredArgsConstructor
|
||||
public class TeacherNotificationController {
|
||||
|
||||
@ -28,7 +28,7 @@ public class TeacherNotificationController {
|
||||
@Operation(summary = "Get my notifications")
|
||||
@GetMapping
|
||||
public Result<PageResult<Notification>> getMyNotifications(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) Integer isRead) {
|
||||
Long userId = SecurityUtils.getCurrentUserId();
|
||||
|
||||
@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "Teacher - Task", description = "Task APIs for Teacher")
|
||||
@RestController
|
||||
@RequestMapping("/api/teacher/tasks")
|
||||
@RequestMapping("/api/v1/teacher/tasks")
|
||||
@RequiredArgsConstructor
|
||||
public class TeacherTaskController {
|
||||
|
||||
@ -45,7 +45,7 @@ public class TeacherTaskController {
|
||||
@Operation(summary = "Get task page")
|
||||
@GetMapping
|
||||
public Result<PageResult<Task>> getTaskPage(
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(value = "page", required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String type,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.reading.platform.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
@ -14,10 +15,12 @@ public class TenantCreateRequest {
|
||||
@Schema(description = "Tenant name")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "Tenant code is required")
|
||||
@Schema(description = "Tenant code")
|
||||
@NotBlank(message = "Login account is required")
|
||||
@JsonAlias("loginAccount")
|
||||
@Schema(description = "Tenant code / login account")
|
||||
private String code;
|
||||
|
||||
@JsonAlias("contactPerson")
|
||||
@Schema(description = "Contact person")
|
||||
private String contactName;
|
||||
|
||||
@ -33,13 +36,25 @@ public class TenantCreateRequest {
|
||||
@Schema(description = "Logo URL")
|
||||
private String logoUrl;
|
||||
|
||||
@JsonAlias("expireDate")
|
||||
@Schema(description = "Expiration date")
|
||||
private LocalDateTime expireAt;
|
||||
|
||||
@JsonAlias("studentQuota")
|
||||
@Schema(description = "Max students")
|
||||
private Integer maxStudents;
|
||||
|
||||
@JsonAlias("teacherQuota")
|
||||
@Schema(description = "Max teachers")
|
||||
private Integer maxTeachers;
|
||||
|
||||
@Schema(description = "Initial password (default: 123456)")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "Package type (optional)")
|
||||
private String packageType;
|
||||
|
||||
@Schema(description = "Start date (optional)")
|
||||
private String startDate;
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.reading.platform.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@ -12,6 +13,7 @@ public class TenantUpdateRequest {
|
||||
@Schema(description = "Tenant name")
|
||||
private String name;
|
||||
|
||||
@JsonAlias("contactPerson")
|
||||
@Schema(description = "Contact person")
|
||||
private String contactName;
|
||||
|
||||
@ -30,13 +32,22 @@ public class TenantUpdateRequest {
|
||||
@Schema(description = "Status")
|
||||
private String status;
|
||||
|
||||
@JsonAlias("expireDate")
|
||||
@Schema(description = "Expiration date")
|
||||
private LocalDateTime expireAt;
|
||||
|
||||
@JsonAlias("studentQuota")
|
||||
@Schema(description = "Max students")
|
||||
private Integer maxStudents;
|
||||
|
||||
@JsonAlias("teacherQuota")
|
||||
@Schema(description = "Max teachers")
|
||||
private Integer maxTeachers;
|
||||
|
||||
@Schema(description = "Package type (optional)")
|
||||
private String packageType;
|
||||
|
||||
@Schema(description = "Start date (optional)")
|
||||
private String startDate;
|
||||
|
||||
}
|
||||
|
||||
@ -17,11 +17,11 @@ public class TenantResponse {
|
||||
@Schema(description = "Tenant name")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "Tenant code")
|
||||
private String code;
|
||||
@Schema(description = "Login account (tenant code)")
|
||||
private String loginAccount;
|
||||
|
||||
@Schema(description = "Contact person")
|
||||
private String contactName;
|
||||
private String contactPerson;
|
||||
|
||||
@Schema(description = "Contact phone")
|
||||
private String contactPhone;
|
||||
@ -39,13 +39,13 @@ public class TenantResponse {
|
||||
private String status;
|
||||
|
||||
@Schema(description = "Expiration date")
|
||||
private LocalDateTime expireAt;
|
||||
private LocalDateTime expireDate;
|
||||
|
||||
@Schema(description = "Max students")
|
||||
private Integer maxStudents;
|
||||
@Schema(description = "Max students / student quota")
|
||||
private Integer studentQuota;
|
||||
|
||||
@Schema(description = "Max teachers")
|
||||
private Integer maxTeachers;
|
||||
@Schema(description = "Max teachers / teacher quota")
|
||||
private Integer teacherQuota;
|
||||
|
||||
@Schema(description = "Created at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package com.reading.platform.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.reading.platform.common.serializer.UpperCaseSerializer;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@ -17,8 +20,10 @@ public class Tenant {
|
||||
|
||||
private String name;
|
||||
|
||||
@JsonProperty("loginAccount")
|
||||
private String code;
|
||||
|
||||
@JsonProperty("contactPerson")
|
||||
private String contactName;
|
||||
|
||||
private String contactPhone;
|
||||
@ -29,12 +34,16 @@ public class Tenant {
|
||||
|
||||
private String logoUrl;
|
||||
|
||||
@JsonSerialize(using = UpperCaseSerializer.class)
|
||||
private String status;
|
||||
|
||||
@JsonProperty("expireDate")
|
||||
private LocalDateTime expireAt;
|
||||
|
||||
@JsonProperty("studentQuota")
|
||||
private Integer maxStudents;
|
||||
|
||||
@JsonProperty("teacherQuota")
|
||||
private Integer maxTeachers;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@ -46,4 +55,8 @@ public class Tenant {
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
// Not persisted - used in create response to return temp password
|
||||
@TableField(exist = false)
|
||||
private String tempPassword;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,148 @@
|
||||
package com.reading.platform.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.reading.platform.entity.*;
|
||||
import com.reading.platform.mapper.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AdminStatsService {
|
||||
|
||||
private final TenantMapper tenantMapper;
|
||||
private final TeacherMapper teacherMapper;
|
||||
private final StudentMapper studentMapper;
|
||||
private final CourseMapper courseMapper;
|
||||
private final LessonMapper lessonMapper;
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
|
||||
long tenantCount = tenantMapper.selectCount(null);
|
||||
long activeTenantCount = tenantMapper.selectCount(
|
||||
new LambdaQueryWrapper<Tenant>().eq(Tenant::getStatus, "active"));
|
||||
long courseCount = courseMapper.selectCount(null);
|
||||
long publishedCourseCount = courseMapper.selectCount(
|
||||
new LambdaQueryWrapper<Course>().eq(Course::getStatus, "published"));
|
||||
|
||||
// Monthly lessons (current month)
|
||||
LocalDate monthStart = LocalDate.now().withDayOfMonth(1);
|
||||
LocalDate monthEnd = LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth());
|
||||
long monthlyLessons = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.ge(Lesson::getLessonDate, monthStart)
|
||||
.le(Lesson::getLessonDate, monthEnd));
|
||||
|
||||
stats.put("tenantCount", tenantCount);
|
||||
stats.put("activeTenantCount", activeTenantCount);
|
||||
stats.put("teacherCount", teacherMapper.selectCount(null));
|
||||
stats.put("studentCount", studentMapper.selectCount(null));
|
||||
stats.put("courseCount", courseCount);
|
||||
stats.put("publishedCourseCount", publishedCourseCount);
|
||||
stats.put("lessonCount", lessonMapper.selectCount(null));
|
||||
stats.put("monthlyLessons", monthlyLessons);
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getTrendData() {
|
||||
List<Map<String, Object>> trend = new ArrayList<>();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
LocalDate now = LocalDate.now();
|
||||
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
LocalDate monthStart = now.minusMonths(i).withDayOfMonth(1);
|
||||
LocalDate monthEnd = monthStart.withDayOfMonth(monthStart.lengthOfMonth());
|
||||
|
||||
long lessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.ge(Lesson::getLessonDate, monthStart)
|
||||
.le(Lesson::getLessonDate, monthEnd));
|
||||
|
||||
// Count tenants created up to this month end
|
||||
long tenantCount = tenantMapper.selectCount(
|
||||
new LambdaQueryWrapper<Tenant>()
|
||||
.le(Tenant::getCreatedAt, monthEnd.atTime(23, 59, 59)));
|
||||
|
||||
// Count students created up to this month end
|
||||
long studentCount = studentMapper.selectCount(
|
||||
new LambdaQueryWrapper<Student>()
|
||||
.le(Student::getCreatedAt, monthEnd.atTime(23, 59, 59)));
|
||||
|
||||
Map<String, Object> point = new HashMap<>();
|
||||
point.put("month", monthStart.format(formatter));
|
||||
point.put("tenantCount", tenantCount);
|
||||
point.put("lessonCount", lessonCount);
|
||||
point.put("studentCount", studentCount);
|
||||
trend.add(point);
|
||||
}
|
||||
return trend;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getActiveTenants(int limit) {
|
||||
LambdaQueryWrapper<Tenant> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Tenant::getStatus, "active")
|
||||
.orderByDesc(Tenant::getCreatedAt)
|
||||
.last("LIMIT " + limit);
|
||||
List<Tenant> tenants = tenantMapper.selectList(wrapper);
|
||||
return tenants.stream().map(t -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("id", t.getId());
|
||||
map.put("name", t.getName());
|
||||
map.put("code", t.getCode());
|
||||
map.put("status", t.getStatus());
|
||||
map.put("expireAt", t.getExpireAt());
|
||||
// Count teachers and students for this tenant
|
||||
long teacherCount = teacherMapper.selectCount(
|
||||
new LambdaQueryWrapper<Teacher>().eq(Teacher::getTenantId, t.getId()));
|
||||
long studentCount = studentMapper.selectCount(
|
||||
new LambdaQueryWrapper<Student>().eq(Student::getTenantId, t.getId()));
|
||||
long lessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>().eq(Lesson::getTenantId, t.getId()));
|
||||
map.put("teacherCount", teacherCount);
|
||||
map.put("studentCount", studentCount);
|
||||
map.put("lessonCount", lessonCount);
|
||||
return map;
|
||||
}).collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getPopularCourses(int limit) {
|
||||
LambdaQueryWrapper<Course> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Course::getIsSystem, 1)
|
||||
.eq(Course::getStatus, "published")
|
||||
.orderByDesc(Course::getCreatedAt)
|
||||
.last("LIMIT " + limit);
|
||||
List<Course> courses = courseMapper.selectList(wrapper);
|
||||
return courses.stream().map(c -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("id", c.getId());
|
||||
map.put("name", c.getName());
|
||||
map.put("category", c.getCategory());
|
||||
map.put("status", c.getStatus());
|
||||
map.put("usageCount", c.getUsageCount() != null ? c.getUsageCount() : 0);
|
||||
map.put("teacherCount", c.getTeacherCount() != null ? c.getTeacherCount() : 0);
|
||||
return map;
|
||||
}).collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getRecentActivities(int limit) {
|
||||
LambdaQueryWrapper<Lesson> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.orderByDesc(Lesson::getCreatedAt).last("LIMIT " + limit);
|
||||
List<Lesson> lessons = lessonMapper.selectList(wrapper);
|
||||
return lessons.stream().map(l -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("id", l.getId());
|
||||
map.put("title", l.getTitle());
|
||||
map.put("lessonDate", l.getLessonDate());
|
||||
map.put("status", l.getStatus());
|
||||
return map;
|
||||
}).collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -25,4 +25,6 @@ public interface TenantService {
|
||||
|
||||
List<TenantResponse> getAllActiveTenants();
|
||||
|
||||
String resetSchoolAccountPassword(Long tenantId);
|
||||
|
||||
}
|
||||
|
||||
@ -193,10 +193,11 @@ public class AuthServiceImpl implements AuthService {
|
||||
teacher.setLastLoginAt(LocalDateTime.now());
|
||||
teacherMapper.updateById(teacher);
|
||||
|
||||
// Use the requested role (school or teacher) from the login request
|
||||
JwtPayload payload = JwtPayload.builder()
|
||||
.userId(teacher.getId())
|
||||
.username(teacher.getUsername())
|
||||
.role("teacher")
|
||||
.role(role)
|
||||
.tenantId(teacher.getTenantId())
|
||||
.name(teacher.getName())
|
||||
.build();
|
||||
@ -206,7 +207,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
.userId(teacher.getId())
|
||||
.username(teacher.getUsername())
|
||||
.name(teacher.getName())
|
||||
.role("teacher")
|
||||
.role(role)
|
||||
.tenantId(teacher.getTenantId())
|
||||
.build();
|
||||
}
|
||||
@ -263,7 +264,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
.tenantId(null)
|
||||
.build();
|
||||
}
|
||||
case "teacher" -> {
|
||||
case "teacher", "school" -> {
|
||||
Teacher teacher = teacherMapper.selectById(payload.getUserId());
|
||||
yield UserInfoResponse.builder()
|
||||
.id(teacher.getId())
|
||||
@ -272,7 +273,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
.email(teacher.getEmail())
|
||||
.phone(teacher.getPhone())
|
||||
.avatarUrl(teacher.getAvatarUrl())
|
||||
.role("teacher")
|
||||
.role(role)
|
||||
.tenantId(teacher.getTenantId())
|
||||
.build();
|
||||
}
|
||||
@ -308,7 +309,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
adminUser.setPassword(passwordEncoder.encode(newPassword));
|
||||
adminUserMapper.updateById(adminUser);
|
||||
}
|
||||
case "teacher" -> {
|
||||
case "teacher", "school" -> {
|
||||
Teacher teacher = teacherMapper.selectById(userId);
|
||||
if (!passwordEncoder.matches(oldPassword, teacher.getPassword())) {
|
||||
throw new BusinessException(ErrorCode.OLD_PASSWORD_ERROR);
|
||||
|
||||
@ -8,10 +8,13 @@ import com.reading.platform.common.util.PageUtils;
|
||||
import com.reading.platform.dto.request.TenantCreateRequest;
|
||||
import com.reading.platform.dto.request.TenantUpdateRequest;
|
||||
import com.reading.platform.dto.response.TenantResponse;
|
||||
import com.reading.platform.entity.Teacher;
|
||||
import com.reading.platform.entity.Tenant;
|
||||
import com.reading.platform.mapper.TeacherMapper;
|
||||
import com.reading.platform.mapper.TenantMapper;
|
||||
import com.reading.platform.service.TenantService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
@ -24,6 +27,8 @@ import java.util.stream.Collectors;
|
||||
public class TenantServiceImpl implements TenantService {
|
||||
|
||||
private final TenantMapper tenantMapper;
|
||||
private final TeacherMapper teacherMapper;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@ -33,7 +38,7 @@ public class TenantServiceImpl implements TenantService {
|
||||
new LambdaQueryWrapper<Tenant>().eq(Tenant::getCode, request.getCode())
|
||||
);
|
||||
if (existing != null) {
|
||||
throw new BusinessException(ErrorCode.DATA_ALREADY_EXISTS, "Tenant code already exists");
|
||||
throw new BusinessException(ErrorCode.DATA_ALREADY_EXISTS, "Login account already exists");
|
||||
}
|
||||
|
||||
Tenant tenant = new Tenant();
|
||||
@ -50,6 +55,19 @@ public class TenantServiceImpl implements TenantService {
|
||||
tenant.setStatus("active");
|
||||
|
||||
tenantMapper.insert(tenant);
|
||||
|
||||
// Create school login account in teachers table
|
||||
String defaultPassword = StringUtils.hasText(request.getPassword()) ? request.getPassword() : "123456";
|
||||
Teacher schoolAccount = new Teacher();
|
||||
schoolAccount.setTenantId(tenant.getId());
|
||||
schoolAccount.setUsername(request.getCode());
|
||||
schoolAccount.setPassword(passwordEncoder.encode(defaultPassword));
|
||||
schoolAccount.setName(request.getName());
|
||||
schoolAccount.setStatus("active");
|
||||
teacherMapper.insert(schoolAccount);
|
||||
|
||||
// Include temp password in response (cleared text for admin to share)
|
||||
tenant.setTempPassword(defaultPassword);
|
||||
return tenant;
|
||||
}
|
||||
|
||||
@ -117,7 +135,8 @@ public class TenantServiceImpl implements TenantService {
|
||||
);
|
||||
}
|
||||
if (StringUtils.hasText(status)) {
|
||||
wrapper.eq(Tenant::getStatus, status);
|
||||
// Frontend sends uppercase (ACTIVE/SUSPENDED), DB stores lowercase - normalize
|
||||
wrapper.eq(Tenant::getStatus, status.toLowerCase());
|
||||
}
|
||||
wrapper.orderByDesc(Tenant::getCreatedAt);
|
||||
|
||||
@ -131,6 +150,24 @@ public class TenantServiceImpl implements TenantService {
|
||||
tenantMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String resetSchoolAccountPassword(Long tenantId) {
|
||||
Tenant tenant = getTenantById(tenantId);
|
||||
// Find the school account (username = tenant code)
|
||||
Teacher schoolAccount = teacherMapper.selectOne(
|
||||
new LambdaQueryWrapper<Teacher>()
|
||||
.eq(Teacher::getTenantId, tenantId)
|
||||
.eq(Teacher::getUsername, tenant.getCode())
|
||||
);
|
||||
String newPassword = "123456";
|
||||
if (schoolAccount != null) {
|
||||
schoolAccount.setPassword(passwordEncoder.encode(newPassword));
|
||||
teacherMapper.updateById(schoolAccount);
|
||||
}
|
||||
return newPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TenantResponse> getAllActiveTenants() {
|
||||
List<Tenant> tenants = tenantMapper.selectList(
|
||||
@ -147,16 +184,16 @@ public class TenantServiceImpl implements TenantService {
|
||||
return TenantResponse.builder()
|
||||
.id(tenant.getId())
|
||||
.name(tenant.getName())
|
||||
.code(tenant.getCode())
|
||||
.contactName(tenant.getContactName())
|
||||
.loginAccount(tenant.getCode())
|
||||
.contactPerson(tenant.getContactName())
|
||||
.contactPhone(tenant.getContactPhone())
|
||||
.contactEmail(tenant.getContactEmail())
|
||||
.address(tenant.getAddress())
|
||||
.logoUrl(tenant.getLogoUrl())
|
||||
.status(tenant.getStatus())
|
||||
.expireAt(tenant.getExpireAt())
|
||||
.maxStudents(tenant.getMaxStudents())
|
||||
.maxTeachers(tenant.getMaxTeachers())
|
||||
.expireDate(tenant.getExpireAt())
|
||||
.studentQuota(tenant.getMaxStudents())
|
||||
.teacherQuota(tenant.getMaxTeachers())
|
||||
.createdAt(tenant.getCreatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user