mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-09 03:17:52 +00:00
docs: enrich backend code documentation
This commit is contained in:
parent
6a62fec9e8
commit
8ef53d0fdd
257 changed files with 1476 additions and 4 deletions
249
docs/17-backend-annotation-findings.md
Normal file
249
docs/17-backend-annotation-findings.md
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
# Backend Structure Findings During Annotation Pass
|
||||
|
||||
This document records architecture and structure issues that became consistently visible while enriching backend comments. The goal is to preserve concrete observations discovered during code reading, not to propose a full redesign.
|
||||
|
||||
## 1. Admin user management is split across overlapping application services
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/service/AdminUserAppService.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/service/AdminUserManagementService.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- Both services sit in the application layer and are named as if they own the same capability.
|
||||
- The naming does not make the responsibility boundary obvious to a reader.
|
||||
- This increases the chance that new admin-user use cases get placed inconsistently.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Either consolidate them into one application service, or split them with an explicit boundary such as query vs. command, or account governance vs. account operations.
|
||||
|
||||
## 2. Several controllers still perform orchestration that belongs in application services
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/NamespaceController.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/ReviewController.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/PromotionController.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillLifecycleController.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/compat/ClawHubCompatController.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- Some controllers coordinate multiple repositories, domain services, request-derived identities, and response assembly in one place.
|
||||
- The controller layer is therefore carrying request translation and business workflow orchestration at the same time.
|
||||
- This makes endpoint behavior harder to reuse, test, and document consistently.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Move multi-step orchestration into dedicated application services and keep controllers focused on transport concerns.
|
||||
|
||||
## 3. Compatibility endpoints are tightly coupled to canonical domain and repository internals
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/compat/ClawHubCompatController.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/compat/ClawHubRegistryFacade.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- The compatibility layer pulls from repositories, domain services, and DTO-mapping concerns at the same time.
|
||||
- The layer is useful, but it is not isolated enough to act as a clean anti-corruption boundary.
|
||||
- Changes in canonical read models or publish flows are more likely to leak into compatibility code.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Treat compatibility support as a dedicated adapter layer with narrower upstream contracts and fewer direct repository dependencies.
|
||||
|
||||
## 4. Security route policy is spread across configuration and implementation classes
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/filter/AuthContextFilter.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- Route access rules, token-scope rules, and request-context projection are all related to request authorization, but they are not expressed from one central policy model.
|
||||
- A reader has to jump across modules to reconstruct how one API route is actually protected.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Centralize route policy metadata or at least define one authoritative mapping between path patterns, authentication modes, and scope requirements.
|
||||
|
||||
## 5. Governance behavior is distributed across multiple services without one clear workflow owner
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/namespace/NamespaceGovernanceService.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillGovernanceService.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/review/ReviewService.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/review/PromotionService.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- Governance rules are present in the right domain areas, but the end-to-end moderation and publishing workflow is distributed.
|
||||
- Readers need to reconstruct lifecycle rules by navigating several services and controllers.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Keep the domain split, but introduce a clearer workflow owner or workflow-facing facade for governance use cases.
|
||||
|
||||
## 6. Search-related read paths are split in a way that is hard to follow at first glance
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-search/src/main/java/com/iflytek/skillhub/search/postgres/PostgresFullTextQueryService.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillSearchAppService.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/service/SkillQueryService.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- The codebase has a sensible separation between search, application assembly, and canonical detail reads, but the naming alone does not make their responsibilities obvious.
|
||||
- New contributors may need several passes to understand which service is the authoritative entry point for each read scenario.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Clarify the boundary in naming or package-level docs, especially around "search result assembly" vs. "authoritative skill detail query."
|
||||
|
||||
## 7. Event-driven counter maintenance is useful but not yet modeled as a distinct projection concern
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/listener/SkillStarEventListener.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/listener/SkillRatingEventListener.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- Listeners are maintaining derived counters, which is a legitimate pattern.
|
||||
- The projection/update responsibility is implicit rather than explicitly named as a read-model maintenance concern.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Consider naming this area more explicitly as projection maintenance or read-model synchronization if the pattern continues to grow.
|
||||
|
||||
## 8. Exception modeling is duplicated across application, domain, and auth layers
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/exception/LocalizedException.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/shared/exception/LocalizedDomainException.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/exception/AuthFlowException.java`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/exception/GlobalExceptionHandler.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- The codebase uses localized error codes consistently, which is good, but several layers define parallel exception abstractions with overlapping semantics.
|
||||
- The global exception handler then has to understand each branch separately.
|
||||
- This makes it harder to tell whether a new business error belongs to the app layer, the auth layer, or the shared domain exception model.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Keep layer-specific exception types only where they represent a real boundary, and consider converging on a smaller shared contract for localized API-facing errors.
|
||||
|
||||
## 9. Repository and read-model access patterns are mixed across layers
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/repository/AdminUserSearchRepository.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/*/*Repository.java`
|
||||
- `server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/*`
|
||||
- `server/skillhub-app/src/main/java/com/iflytek/skillhub/compat/ClawHubCompatController.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- Some flows use domain repository ports, some use infra JPA repositories, and some app-layer read logic uses `EntityManager` directly.
|
||||
- This is not wrong in itself, but the conventions are not explicit, so contributors have to infer when bypassing the domain port layer is acceptable.
|
||||
- The mixed style increases the chance that query behavior and write behavior evolve under different architectural rules.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Define explicit rules for when a use case should depend on domain repository ports, dedicated query repositories, or direct persistence adapters.
|
||||
|
||||
## 10. OAuth login behavior is decomposed into many small classes without one visible flow owner
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/SkillHubOAuth2AuthorizationRequestResolver.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/CustomOAuth2UserService.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/GitHubClaimsExtractor.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginSuccessHandler.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/oauth/OAuth2LoginFailureHandler.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/policy/AccessPolicyFactory.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- The current decomposition is modular, but understanding one OAuth login request still requires following state across request resolution, provider-specific claim extraction, access-policy evaluation, account provisioning, and redirect handling.
|
||||
- The extension points are good, yet the absence of one flow-oriented facade or documented orchestration path increases onboarding cost.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Keep the provider-specific strategy types, but consider a clearer flow owner or a compact architecture note that names the stages of the OAuth pipeline.
|
||||
|
||||
## 11. Some domain repository ports leak Spring Data pagination types
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillRepository.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/review/ReviewTaskRepository.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/audit/AuditLogQueryService.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- Several domain-facing repository contracts use `Page` and `Pageable` directly.
|
||||
- This makes the domain boundary more dependent on Spring Data semantics than on a framework-neutral query model.
|
||||
- It is workable, but it weakens the separation between domain contracts and persistence tooling.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Either accept Spring Data as an intentional part of the domain boundary and document that choice, or introduce domain-oriented page/query abstractions where long-term isolation matters.
|
||||
|
||||
## 12. The auth module follows a more direct JPA style than the business-domain modules
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/repository/ApiTokenRepository.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/repository/IdentityBindingRepository.java`
|
||||
- `server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/repository/RoleRepository.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/SkillRepository.java`
|
||||
- `server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/JpaSkillRepositoryAdapter.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- The auth area usually talks directly to Spring Data JPA repositories over auth entities.
|
||||
- The business-domain area more often exposes domain repository ports and implements them through infra adapters.
|
||||
- Both styles are valid, but using them side by side without an explicit rationale makes the overall architecture feel uneven.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- Decide whether auth is intentionally allowed to stay as a more direct persistence-oriented module, and document that distinction so contributors know which style to apply in new code.
|
||||
|
||||
## 13. Many domain objects double as persistence entities instead of being isolated from JPA concerns
|
||||
|
||||
Observed files:
|
||||
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/skill/Skill.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/namespace/Namespace.java`
|
||||
- `server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/review/ReviewTask.java`
|
||||
- `server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/SkillJpaRepository.java`
|
||||
- `server/skillhub-infra/src/main/java/com/iflytek/skillhub/infra/jpa/NamespaceMemberJpaRepository.java`
|
||||
|
||||
Why this stands out:
|
||||
|
||||
- The codebase often uses the same classes as both domain models and JPA persistence entities.
|
||||
- This keeps implementation compact, but it also means persistence annotations, lazy-loading behavior, and storage-driven shape decisions can leak into domain modeling concerns.
|
||||
- Combined with the repository-style differences already noted above, the codebase can feel partly domain-driven and partly persistence-driven depending on the module.
|
||||
|
||||
Suggested direction:
|
||||
|
||||
- If this is an intentional tradeoff, document it clearly as the project's default. Otherwise, consider introducing stronger separation only in areas where persistence concerns are starting to distort domain logic.
|
||||
|
||||
## Priority Recommendation
|
||||
|
||||
If only a small amount of structural cleanup is feasible, the highest-value items are:
|
||||
|
||||
1. Reduce controller orchestration by introducing a few focused application services.
|
||||
2. Clarify the admin-user service boundary.
|
||||
3. Centralize security route policy so access behavior is easier to reason about.
|
||||
|
|
@ -5,6 +5,9 @@ import org.springframework.boot.SpringApplication;
|
|||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Main Spring Boot entry point for the SkillHub backend application.
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(ProfileModerationProperties.class)
|
||||
public class SkillhubApplication {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package com.iflytek.skillhub.bootstrap;
|
|||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Configuration properties for bootstrapping a default admin account in controlled environments.
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "skillhub.bootstrap.admin")
|
||||
public class BootstrapAdminProperties {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import org.springframework.context.annotation.Profile;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Seeds predictable users, memberships, and admin roles for the local development profile.
|
||||
*/
|
||||
@Component
|
||||
@Profile("local")
|
||||
public class LocalDevDataInitializer implements ApplicationRunner {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Startup initializers that prepare local development data and required system
|
||||
* accounts before the application begins serving traffic.
|
||||
*/
|
||||
package com.iflytek.skillhub.bootstrap;
|
||||
|
|
@ -43,6 +43,10 @@ import java.time.ZoneOffset;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Compatibility controller that exposes SkillHub content using ClawHub-style routes and payload
|
||||
* shapes expected by legacy clients.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1")
|
||||
public class ClawHubCompatController {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Facade that assembles registry-style compatibility responses from the platform's canonical search
|
||||
* and skill services.
|
||||
*/
|
||||
@Component
|
||||
public class ClawHubRegistryFacade {
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
/**
|
||||
* Declares a dedicated stateless security chain for public compatibility endpoints used by
|
||||
* registry-style clients.
|
||||
*/
|
||||
@Configuration
|
||||
public class ClawHubRegistrySecurityConfig {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
package com.iflytek.skillhub.compat;
|
||||
|
||||
/**
|
||||
* Canonical namespace-and-slug pair used by compatibility adapters to address one skill.
|
||||
*/
|
||||
public record SkillCoordinate(String namespace, String slug) {}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Serves well-known compatibility metadata used by external clients to discover the API base.
|
||||
*/
|
||||
@RestController
|
||||
public class WellKnownController {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* DTOs dedicated to compatibility controllers so legacy response contracts do
|
||||
* not leak into the primary application API surface.
|
||||
*/
|
||||
package com.iflytek.skillhub.compat.dto;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Compatibility endpoints and helpers that expose SkillHub data using
|
||||
* conventions expected by external or legacy clients.
|
||||
*/
|
||||
package com.iflytek.skillhub.compat;
|
||||
|
|
@ -8,6 +8,10 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* Enables asynchronous event handling and other background execution features used by the
|
||||
* application module.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import org.springframework.context.annotation.Configuration;
|
|||
|
||||
import java.time.Clock;
|
||||
|
||||
/**
|
||||
* Wires application-level Spring beans that adapt configurable infrastructure into domain-facing
|
||||
* ports.
|
||||
*/
|
||||
@Configuration
|
||||
public class DomainBeanConfig {
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import org.springframework.context.annotation.Configuration;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OpenAPI metadata configuration for generated API documentation.
|
||||
*/
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Registers MVC interceptors related to request rate limiting.
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcRateLimitConfig implements WebMvcConfigurer {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Spring configuration properties and lightweight application wiring for the
|
||||
* web layer.
|
||||
*/
|
||||
package com.iflytek.skillhub.config;
|
||||
|
|
@ -16,6 +16,10 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Endpoints for initiating, verifying, and confirming account merge flows
|
||||
* across multiple identities owned by the same user.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/account/merge")
|
||||
public class AccountMergeController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Authentication-facing HTTP endpoints.
|
||||
*
|
||||
* <p>This controller keeps transport concerns at the boundary and delegates the
|
||||
* actual authentication, session bootstrap, and direct-login workflows to
|
||||
* dedicated application or auth services.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth")
|
||||
public class AuthController extends BaseApiController {
|
||||
|
|
@ -68,6 +75,10 @@ public class AuthController extends BaseApiController {
|
|||
this.userAccountRepository = userAccountRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current authenticated principal and refreshes the session if
|
||||
* the persisted user state has diverged from the in-session snapshot.
|
||||
*/
|
||||
@GetMapping("/me")
|
||||
public ApiResponse<AuthMeResponse> me(@AuthenticationPrincipal PlatformPrincipal principal,
|
||||
Authentication authentication,
|
||||
|
|
@ -103,18 +114,32 @@ public class AuthController extends BaseApiController {
|
|||
return ok("response.success.read", AuthMeResponse.from(principal));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists browser-based authentication providers that can initiate an OAuth
|
||||
* login flow for the current client.
|
||||
*/
|
||||
@GetMapping("/providers")
|
||||
public ApiResponse<List<AuthProviderResponse>> providers(
|
||||
@RequestParam(name = "returnTo", required = false) String returnTo) {
|
||||
return ok("response.success.read", authMethodCatalog.listOAuthProviders(returnTo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all authentication methods exposed to the UI, including direct and
|
||||
* OAuth-based flows.
|
||||
*/
|
||||
@GetMapping("/methods")
|
||||
public ApiResponse<List<AuthMethodResponse>> methods(
|
||||
@RequestParam(name = "returnTo", required = false) String returnTo) {
|
||||
return ok("response.success.read", authMethodCatalog.listMethods(returnTo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds an authenticated session from an upstream identity assertion.
|
||||
*
|
||||
* <p>This endpoint is used by trusted frontends or gateway flows that have
|
||||
* already authenticated the user elsewhere.
|
||||
*/
|
||||
@PostMapping("/session/bootstrap")
|
||||
@RateLimit(category = "auth-session-bootstrap", authenticated = 30, anonymous = 15, windowSeconds = 60)
|
||||
public ApiResponse<AuthMeResponse> bootstrapSession(@Valid @RequestBody SessionBootstrapRequest request,
|
||||
|
|
@ -125,6 +150,10 @@ public class AuthController extends BaseApiController {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a direct-login flow and establishes a first-party web session on
|
||||
* success.
|
||||
*/
|
||||
@PostMapping("/direct/login")
|
||||
@RateLimit(category = "auth-direct-login", authenticated = 20, anonymous = 10, windowSeconds = 60)
|
||||
public ApiResponse<AuthMeResponse> directLogin(@Valid @RequestBody DirectLoginRequest request,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package com.iflytek.skillhub.controller;
|
|||
import com.iflytek.skillhub.dto.ApiResponse;
|
||||
import com.iflytek.skillhub.dto.ApiResponseFactory;
|
||||
|
||||
/**
|
||||
* Minimal controller base class that centralizes access to the standard API response factory.
|
||||
*/
|
||||
public abstract class BaseApiController {
|
||||
|
||||
private final ApiResponseFactory responseFactory;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* API endpoints for the CLI-style device authorization flow.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth/device")
|
||||
public class DeviceAuthController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Browser-side endpoint that lets an authenticated user authorize a pending
|
||||
* device code and records the operation in the audit log.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/device")
|
||||
public class DeviceAuthWebController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Minimal liveness endpoint used by tests, probes, and basic uptime checks.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1")
|
||||
public class HealthController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* HTTP endpoints for local account registration, login, and password changes.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/auth/local")
|
||||
public class LocalAuthController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import org.springframework.web.bind.annotation.*;
|
|||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Self-service API token management endpoints for authenticated users.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/tokens")
|
||||
public class TokenController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Administrative skill-governance endpoints reserved for platform-level
|
||||
* moderation actions such as hide and unhide.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/admin/skills")
|
||||
public class AdminSkillController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Administrative endpoints for reviewing and resolving user-submitted skill
|
||||
* reports.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/admin/skill-reports")
|
||||
public class AdminSkillReportController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import org.springframework.web.bind.annotation.*;
|
|||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Read-only audit log endpoints for auditors and super administrators.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/admin/audit-logs")
|
||||
public class AuditLogController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
|||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Administrative endpoints for listing users and mutating user roles or
|
||||
* account status.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/admin/users")
|
||||
public class UserManagementController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Administrative controllers that expose platform-level management operations
|
||||
* such as audit access, moderation, and user governance.
|
||||
*/
|
||||
package com.iflytek.skillhub.controller.admin;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* HTTP controllers for authentication, profile management, and public API
|
||||
* endpoints that do not belong to a more specialized sub-area.
|
||||
*/
|
||||
package com.iflytek.skillhub.controller;
|
||||
|
|
@ -23,6 +23,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Portal endpoints that expose governance dashboards, inbox items, activity,
|
||||
* and user-facing governance notifications.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/governance", "/api/web/governance"})
|
||||
public class GovernanceController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Portal endpoints scoped to the current authenticated user, such as owned and
|
||||
* starred skill listings.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/me", "/api/web/me"})
|
||||
public class MeController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ import java.util.Comparator;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Namespace portal endpoints for discovery, membership management, and
|
||||
* namespace governance operations.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1", "/api/web"})
|
||||
public class NamespaceController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Promotion workflow endpoints that expose submission, review, and query
|
||||
* operations for cross-namespace promotion requests.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/promotions", "/api/web/promotions"})
|
||||
public class PromotionController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Endpoints for submitting, browsing, approving, rejecting, and withdrawing
|
||||
* review tasks.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/reviews", "/api/web/reviews"})
|
||||
public class ReviewController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Read-oriented skill endpoints for detail pages, lifecycle inspection, file
|
||||
* browsing, version resolution, and download delivery.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/skills", "/api/web/skills"})
|
||||
public class SkillController extends BaseApiController {
|
||||
|
|
@ -53,6 +57,10 @@ public class SkillController extends BaseApiController {
|
|||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the viewer-specific projection of a skill, including lifecycle
|
||||
* pointers and interaction permissions derived from the caller context.
|
||||
*/
|
||||
@GetMapping("/{namespace}/{slug}")
|
||||
public ApiResponse<SkillDetailResponse> getSkillDetail(
|
||||
@PathVariable String namespace,
|
||||
|
|
@ -90,6 +98,10 @@ public class SkillController extends BaseApiController {
|
|||
return ok("response.success.read", response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists versions visible to the caller rather than every persisted version
|
||||
* of the skill.
|
||||
*/
|
||||
@GetMapping("/{namespace}/{slug}/versions")
|
||||
public ApiResponse<PageResponse<SkillVersionResponse>> listVersions(
|
||||
@PathVariable String namespace,
|
||||
|
|
@ -120,6 +132,10 @@ public class SkillController extends BaseApiController {
|
|||
return ok("response.success.read", response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metadata for a concrete version that the current caller is
|
||||
* allowed to inspect.
|
||||
*/
|
||||
@GetMapping("/{namespace}/{slug}/versions/{version}")
|
||||
public ApiResponse<SkillVersionDetailResponse> getVersionDetail(
|
||||
@PathVariable String namespace,
|
||||
|
|
@ -150,6 +166,10 @@ public class SkillController extends BaseApiController {
|
|||
return ok("response.success.read", response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists packaged files for a concrete version after visibility checks have
|
||||
* been applied.
|
||||
*/
|
||||
@GetMapping("/{namespace}/{slug}/versions/{version}/files")
|
||||
public ApiResponse<List<SkillFileResponse>> listFiles(
|
||||
@PathVariable String namespace,
|
||||
|
|
@ -208,6 +228,10 @@ public class SkillController extends BaseApiController {
|
|||
return ok("response.success.read", response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams a single packaged file directly from object storage through the
|
||||
* application API.
|
||||
*/
|
||||
@GetMapping("/{namespace}/{slug}/versions/{version}/file")
|
||||
public ResponseEntity<InputStreamResource> getFileContent(
|
||||
@PathVariable String namespace,
|
||||
|
|
@ -254,6 +278,10 @@ public class SkillController extends BaseApiController {
|
|||
.body(new InputStreamResource(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a human-facing version selector to the exact version that would
|
||||
* be downloaded by the caller.
|
||||
*/
|
||||
@GetMapping("/{namespace}/{slug}/resolve")
|
||||
public ApiResponse<ResolveVersionResponse> resolveVersion(
|
||||
@PathVariable String namespace,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Endpoints that mutate skill lifecycle state, including archive, unarchive,
|
||||
* withdraw-review, delete-version, and rerelease operations.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/skills", "/api/web/skills"})
|
||||
public class SkillLifecycleController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Upload endpoints for skill packages.
|
||||
*
|
||||
* <p>The controller is responsible for archive extraction and request shaping,
|
||||
* while the domain service owns all publication validation and state changes.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/skills", "/api/web/skills"})
|
||||
public class SkillPublishController extends BaseApiController {
|
||||
|
|
@ -37,6 +43,10 @@ public class SkillPublishController extends BaseApiController {
|
|||
this.skillHubMetrics = skillHubMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes an uploaded package into the target namespace after archive
|
||||
* extraction and visibility parsing.
|
||||
*/
|
||||
@PostMapping("/{namespace}/publish")
|
||||
@RateLimit(category = "publish", authenticated = 10, anonymous = 0)
|
||||
public ApiResponse<PublishResponse> publish(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
|||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Endpoints for reading and mutating the current user's rating on a skill.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/skills", "/api/web/skills"})
|
||||
public class SkillRatingController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Endpoints that let authenticated users report a skill for moderation.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/skills", "/api/web/skills"})
|
||||
public class SkillReportController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ import org.springframework.web.bind.annotation.*;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Portal search endpoint that adapts HTTP query parameters to the search
|
||||
* application service and visibility scope.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/web/skills"})
|
||||
public class SkillSearchController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import com.iflytek.skillhub.domain.social.SkillStarService;
|
|||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Endpoints for starring, unstarring, and checking star state on a skill.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/api/v1/skills", "/api/web/skills"})
|
||||
public class SkillStarController extends BaseApiController {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Endpoints for reading and mutating named tags that point to skill versions.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({
|
||||
"/api/v1/skills/{namespace}/{slug}/tags",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Primary portal-facing API controllers for namespaces, skills, review flows,
|
||||
* search, and other end-user operations.
|
||||
*/
|
||||
package com.iflytek.skillhub.controller.portal;
|
||||
|
|
@ -14,6 +14,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Builds a publishable package model from multipart form uploads while enforcing package safety
|
||||
* and size constraints.
|
||||
*/
|
||||
@Component
|
||||
public class MultipartPackageExtractor {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import java.util.Set;
|
|||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
/**
|
||||
* Extracts zip uploads into validated package entries that can be consumed by the publish flow.
|
||||
*/
|
||||
@Component
|
||||
public class ZipPackageExtractor {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Controller support utilities for multipart parsing, archive extraction, and
|
||||
* other transport-specific request preparation concerns.
|
||||
*/
|
||||
package com.iflytek.skillhub.controller.support;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Application DTOs used to keep HTTP request and response contracts separate
|
||||
* from domain entities.
|
||||
*/
|
||||
package com.iflytek.skillhub.dto;
|
||||
|
|
@ -2,6 +2,9 @@ package com.iflytek.skillhub.exception;
|
|||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Application-layer exception mapped to HTTP 400 with a localized error code.
|
||||
*/
|
||||
public class BadRequestException extends LocalizedException {
|
||||
|
||||
public BadRequestException(String messageCode, Object... messageArgs) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.iflytek.skillhub.exception;
|
|||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Application-layer exception mapped to HTTP 403 with a localized error code.
|
||||
*/
|
||||
public class ForbiddenException extends LocalizedException {
|
||||
|
||||
public ForbiddenException(String messageCode, Object... messageArgs) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ import org.springframework.web.bind.MethodArgumentNotValidException;
|
|||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* Translates application, domain, auth, and infrastructure exceptions into the platform's JSON API
|
||||
* error envelope.
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.iflytek.skillhub.exception;
|
|||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Common contract for errors that can be rendered as localized API responses.
|
||||
*/
|
||||
public interface LocalizedError {
|
||||
String messageCode();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.iflytek.skillhub.exception;
|
|||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Base class for application-layer exceptions that carry a localized message code and HTTP status.
|
||||
*/
|
||||
public abstract class LocalizedException extends RuntimeException implements LocalizedError {
|
||||
|
||||
private final String messageCode;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.iflytek.skillhub.exception;
|
|||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Application-layer exception mapped to HTTP 401 with a localized error code.
|
||||
*/
|
||||
public class UnauthorizedException extends LocalizedException {
|
||||
|
||||
public UnauthorizedException(String messageCode, Object... messageArgs) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Application-level exception translation and localized error payload support
|
||||
* for the HTTP boundary.
|
||||
*/
|
||||
package com.iflytek.skillhub.exception;
|
||||
|
|
@ -23,6 +23,9 @@ import org.springframework.security.web.context.HttpSessionSecurityContextReposi
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* Projects the authenticated principal into request attributes consumed by the controller layer.
|
||||
*/
|
||||
@Component
|
||||
public class AuthContextFilter extends OncePerRequestFilter {
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ import java.time.Instant;
|
|||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Prevents duplicate execution of mutating HTTP requests identified by
|
||||
* {@code X-Request-Id}.
|
||||
*
|
||||
* <p>Redis is treated as the fast-path cache, while PostgreSQL remains the
|
||||
* durable source of truth when cache access fails.
|
||||
*/
|
||||
@Component
|
||||
public class IdempotencyInterceptor implements HandlerInterceptor {
|
||||
|
||||
|
|
@ -38,6 +45,10 @@ public class IdempotencyInterceptor implements HandlerInterceptor {
|
|||
this.clock = clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects duplicate mutating requests before controller execution and
|
||||
* creates a processing marker for first-seen request identifiers.
|
||||
*/
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
String method = request.getMethod();
|
||||
|
|
@ -94,6 +105,10 @@ public class IdempotencyInterceptor implements HandlerInterceptor {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the idempotency record with the observed response status once
|
||||
* request processing has completed.
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
String method = request.getMethod();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
|||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Ensures every request has a request identifier for logs, responses, and downstream audit
|
||||
* correlation.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class RequestIdFilter extends OncePerRequestFilter {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import java.util.Enumeration;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Logs inbound HTTP requests and responses with truncation suitable for operational debugging.
|
||||
*/
|
||||
@Component
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||
public class RequestLoggingFilter extends OncePerRequestFilter {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Servlet filters and MVC interceptors that enrich requests with cross-cutting
|
||||
* concerns such as logging, idempotency, and caller context.
|
||||
*/
|
||||
package com.iflytek.skillhub.filter;
|
||||
|
|
@ -7,6 +7,10 @@ import org.springframework.scheduling.annotation.Async;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
/**
|
||||
* Updates denormalized skill rating counters when rating events are emitted by
|
||||
* the social domain.
|
||||
*/
|
||||
@Component
|
||||
public class SkillRatingEventListener {
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import org.springframework.scheduling.annotation.Async;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
/**
|
||||
* Keeps the stored star count in sync with the star/unstar event stream.
|
||||
*/
|
||||
@Component
|
||||
public class SkillStarEventListener {
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Application event listeners that react to domain events to update read-side
|
||||
* counters and other eventually consistent projections.
|
||||
*/
|
||||
package com.iflytek.skillhub.listener;
|
||||
|
|
@ -3,6 +3,9 @@ package com.iflytek.skillhub.metrics;
|
|||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Small facade over Micrometer that centralizes metric names and tags used by backend flows.
|
||||
*/
|
||||
@Component
|
||||
public class SkillHubMetrics {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Metrics helpers used to publish application and product telemetry from the
|
||||
* web layer.
|
||||
*/
|
||||
package com.iflytek.skillhub.metrics;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Application-layer orchestration for the SkillHub backend.
|
||||
*
|
||||
* <p>This module adapts HTTP requests, security context, and DTO mapping to the
|
||||
* domain-layer services exposed by the other backend modules.
|
||||
*/
|
||||
package com.iflytek.skillhub;
|
||||
|
|
@ -16,6 +16,10 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Assigns stable anonymous identities for download rate limiting by combining client IP data with
|
||||
* a signed cookie.
|
||||
*/
|
||||
@Component
|
||||
public class AnonymousDownloadIdentityService {
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Resolves the best-effort client IP address from proxy-aware request headers.
|
||||
*/
|
||||
@Component
|
||||
public class ClientIpResolver {
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import java.util.Deque;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
/**
|
||||
* Test-profile rate limiter that keeps sliding-window counters in memory.
|
||||
*/
|
||||
@Component
|
||||
@Profile("test")
|
||||
public class InMemorySlidingWindowRateLimiter implements RateLimiter {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Declares per-endpoint rate-limit settings for authenticated and anonymous callers.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RateLimit {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ import org.springframework.web.servlet.HandlerMapping;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Enforces the {@link RateLimit} annotation by resolving caller identity and delegating quota
|
||||
* checks to the configured rate limiter implementation.
|
||||
*/
|
||||
@Component
|
||||
public class RateLimitInterceptor implements HandlerInterceptor {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.iflytek.skillhub.ratelimit;
|
||||
|
||||
/**
|
||||
* Contract for key-based rate limiter implementations used by API interceptors.
|
||||
*/
|
||||
public interface RateLimiter {
|
||||
|
||||
boolean tryAcquire(String key, int limit, int windowSeconds);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import org.springframework.stereotype.Component;
|
|||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Production rate limiter backed by Redis and a Lua script for atomic sliding-window checks.
|
||||
*/
|
||||
@Component
|
||||
@Profile("!test")
|
||||
public class RedisSlidingWindowRateLimiter implements RateLimiter {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Rate-limiting annotations, interceptors, and implementations used to
|
||||
* protect public APIs from abuse.
|
||||
*/
|
||||
package com.iflytek.skillhub.ratelimit;
|
||||
|
|
@ -18,6 +18,9 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Custom query repository that builds pageable admin-user search results with optional filters.
|
||||
*/
|
||||
@Repository
|
||||
public class AdminUserSearchRepository {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Application-specific query repositories that package read models tailored to
|
||||
* web and administration use cases.
|
||||
*/
|
||||
package com.iflytek.skillhub.repository;
|
||||
|
|
@ -15,6 +15,9 @@ import org.springframework.stereotype.Component;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Converts authorization failures on API routes into the platform's standard JSON error envelope.
|
||||
*/
|
||||
@Component
|
||||
public class ApiAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import org.springframework.stereotype.Component;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Converts unauthenticated API access attempts into a consistent JSON 401 response.
|
||||
*/
|
||||
@Component
|
||||
public class ApiAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Tracks repeated authentication failures and throttles abusive identifiers or client addresses.
|
||||
*/
|
||||
@Service
|
||||
public class AuthFailureThrottleService {
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import java.util.stream.Collectors;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Applies lightweight redaction rules before sensitive strings are written to logs.
|
||||
*/
|
||||
@Component
|
||||
public class SensitiveLogSanitizer {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Web security helpers that translate authorization failures, sanitize logs,
|
||||
* and coordinate security-specific application behavior.
|
||||
*/
|
||||
package com.iflytek.skillhub.security;
|
||||
|
|
@ -13,6 +13,10 @@ import java.time.Instant;
|
|||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Read-only application service that queries audit logs with dynamic filtering
|
||||
* tailored to administration screens.
|
||||
*/
|
||||
@Service
|
||||
public class AdminAuditLogAppService {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ import java.util.stream.Collectors;
|
|||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Application service that enriches raw skill report records with skill and
|
||||
* namespace context required by admin UIs.
|
||||
*/
|
||||
@Service
|
||||
public class AdminSkillReportAppService {
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ import java.util.TreeSet;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Administrative user-management application service built around the main
|
||||
* search and mutation use cases exposed by the admin API.
|
||||
*/
|
||||
@Service
|
||||
public class AdminUserAppService {
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ import org.springframework.data.domain.PageRequest;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Alternative user-management aggregation service that combines user records
|
||||
* with role bindings for management-oriented views.
|
||||
*/
|
||||
@Service
|
||||
public class AdminUserManagementService {
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ import java.util.List;
|
|||
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Builds the catalog of authentication methods and OAuth providers that the UI
|
||||
* can render dynamically.
|
||||
*/
|
||||
@Service
|
||||
public class AuthMethodCatalog {
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ import java.util.Map;
|
|||
import java.util.function.Function;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Dispatches direct-login requests to a configured provider and then binds the
|
||||
* resulting principal to the current HTTP session.
|
||||
*/
|
||||
@Service
|
||||
public class DirectAuthService {
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ import org.springframework.data.domain.Page;
|
|||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Application-facing aggregation service for the governance workbench.
|
||||
*
|
||||
* <p>It joins review, promotion, report, namespace, and audit sources into the
|
||||
* composite read models consumed by governance screens.
|
||||
*/
|
||||
@Service
|
||||
public class GovernanceWorkbenchAppService {
|
||||
|
||||
|
|
@ -75,6 +81,10 @@ public class GovernanceWorkbenchAppService {
|
|||
this.adminAuditLogAppService = adminAuditLogAppService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns top-level counts for the governance dashboard, scoped by the
|
||||
* caller's namespace and platform roles.
|
||||
*/
|
||||
public GovernanceSummaryResponse getSummary(String userId,
|
||||
Map<Long, NamespaceRole> namespaceRoles,
|
||||
Set<String> platformRoles) {
|
||||
|
|
@ -89,6 +99,10 @@ public class GovernanceWorkbenchAppService {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the governance inbox by combining pending reviews, promotions, and
|
||||
* reports that the caller is allowed to see.
|
||||
*/
|
||||
public PageResponse<GovernanceInboxItemResponse> listInbox(String userId,
|
||||
Map<Long, NamespaceRole> namespaceRoles,
|
||||
Set<String> platformRoles,
|
||||
|
|
@ -121,6 +135,10 @@ public class GovernanceWorkbenchAppService {
|
|||
return new PageResponse<>(items.subList(fromIndex, toIndex), items.size(), page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns audit-derived governance activity entries for callers with
|
||||
* platform-wide visibility.
|
||||
*/
|
||||
public PageResponse<GovernanceActivityItemResponse> listActivity(Set<String> platformRoles, int page, int size) {
|
||||
if (!canReadActivity(platformRoles)) {
|
||||
return new PageResponse<>(List.of(), 0, page, size);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ import java.util.Optional;
|
|||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Application service that assembles the current user's owned and starred
|
||||
* skill lists with lifecycle context.
|
||||
*/
|
||||
@Service
|
||||
public class MySkillAppService {
|
||||
private final SkillRepository skillRepository;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Finds candidate users that can be invited into a namespace while excluding
|
||||
* existing members and immutable namespaces.
|
||||
*/
|
||||
@Service
|
||||
public class NamespaceMemberCandidateService {
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import java.util.Map;
|
|||
import java.util.function.Function;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Restores a platform session from a passive authenticator and persists the
|
||||
* resulting principal into Spring Security's session context.
|
||||
*/
|
||||
@Service
|
||||
public class SessionBootstrapService {
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ import java.util.Set;
|
|||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Application service that adapts search queries to the search backend and
|
||||
* enriches results with authoritative skill metadata and viewer permissions.
|
||||
*/
|
||||
@Service
|
||||
public class SkillSearchAppService {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Application services that aggregate multiple domain services or repositories
|
||||
* for controller-friendly use cases.
|
||||
*/
|
||||
package com.iflytek.skillhub.service;
|
||||
|
|
@ -10,6 +10,10 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Periodic maintenance task that expires old idempotency records and marks stale processing
|
||||
* entries as failed.
|
||||
*/
|
||||
@Component
|
||||
public class IdempotencyCleanupTask {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Scheduled background tasks that maintain read models and operational data.
|
||||
*/
|
||||
package com.iflytek.skillhub.task;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Authentication bootstrap helpers that restore session state from existing
|
||||
* request context without forcing an explicit login step.
|
||||
*/
|
||||
package com.iflytek.skillhub.auth.bootstrap;
|
||||
|
|
@ -9,6 +9,9 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Provides the shared Redis template used by authentication and other cross-cutting services.
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisTemplateConfig {
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWrite
|
|||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Central Spring Security configuration for browser sessions, API tokens, and
|
||||
* public versus protected endpoints.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
|
|
@ -75,6 +79,13 @@ public class SecurityConfig {
|
|||
this.mockAuthFilterProvider = mockAuthFilterProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the ordered security filter chain used by both browser and API
|
||||
* clients.
|
||||
*
|
||||
* <p>The chain mixes session-based authentication, bearer token support,
|
||||
* CSRF rules for browser traffic, and method-level authorization.
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
var csrfHandler = new CsrfTokenRequestAttributeHandler();
|
||||
|
|
@ -210,6 +221,10 @@ public class SecurityConfig {
|
|||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the password encoder shared by local credentials and bootstrap
|
||||
* flows.
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(12);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* Spring Security and infrastructure configuration for authentication and
|
||||
* authorization concerns.
|
||||
*/
|
||||
package com.iflytek.skillhub.auth.config;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue