Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 标记需要管理员权限的 Controller 方法或类。
*
* <p>可用于方法级覆盖类级行为。拦截器检查当前用户 role_id == 1 时放行,否则返回 403。
* 与 {@link io.github.malonetalk.common.ErrorCode#FORBIDDEN} 联动。
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AdminOnly {}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
*
* <p>仅承载鉴权必要字段(不含 password_hash),供管理/会话等同步 API 取用。Agent 异步链路
* (Reactor 弹性线程)拿不到此 ThreadLocal——权限轮次会改为通过 ToolCallContext 显式传 userId。
*
* <p>{@code roleId} 用于 @AdminOnly 权限判定:1=管理员,其他值=普通用户。
*/
public record UserContext(Integer userId, String username, String displayName) {
public record UserContext(Integer userId, String username, String displayName, Integer roleId) {

private static final ThreadLocal<UserContext> HOLDER = new ThreadLocal<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void run(String... args) {
admin.setUsername("admin");
admin.setPasswordHash(PasswordUtil.hash(adminInitPassword));
admin.setDisplayName("管理员");
admin.setRoleId(0);
admin.setRoleId(1); // 管理员角色,对应 @AdminOnly 权限判定
admin.setIdpType("LOCAL");
admin.setIdpUserId(null);
admin.setStatus(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package io.github.malonetalk.controller;

import io.github.malonetalk.agent.datasource.DataSourceType;
import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.ErrorCode;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.convertor.DatasourceConverter;
Expand Down Expand Up @@ -59,6 +60,7 @@ public Result<DatasourceResponse> findById(@PathVariable Integer id) {
return Result.success(datasourceConverter.toResponse(requireDatasource(id)));
}

@AdminOnly
@PostMapping
public Result<Boolean> save(@Valid @RequestBody DatasourceRequest request) {
DataSourceType type = requireDatasourceType(request.type());
Expand All @@ -70,6 +72,7 @@ public Result<Boolean> save(@Valid @RequestBody DatasourceRequest request) {
return Result.success();
}

@AdminOnly
@PutMapping("/{id}")
public Result<Boolean> update(
@PathVariable Integer id, @Valid @RequestBody DatasourceRequest request) {
Expand All @@ -91,6 +94,7 @@ public Result<Boolean> update(
return Result.success(true);
}

@AdminOnly
@DeleteMapping("/{id}")
public Result<Boolean> deleteById(@PathVariable Integer id) {
requireDatasource(id);
Expand All @@ -117,6 +121,7 @@ public Result<List<DatasourceResponse>> findByType(@PathVariable String type) {
return Result.success(list);
}

@AdminOnly
@PutMapping("/{id}/activate")
public Result<Boolean> activate(@PathVariable Integer id) {
requireDatasource(id);
Expand All @@ -126,6 +131,7 @@ public Result<Boolean> activate(@PathVariable Integer id) {
return Result.success(true);
}

@AdminOnly
@PutMapping("/{id}/deactivate")
public Result<Boolean> deactivate(@PathVariable Integer id) {
requireDatasource(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.ErrorCode;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.DomainCreateRequest;
Expand All @@ -39,6 +40,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@AdminOnly
@RestController
@RequestMapping("/api/domains")
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.ErrorCode;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.convertor.McpServerConverter;
Expand All @@ -38,6 +39,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@AdminOnly
@RestController
@AllArgsConstructor
@RequestMapping("/api/mcp-server")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.convertor.MetricConverter;
import io.github.malonetalk.dto.MetricRequest;
Expand All @@ -38,6 +39,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@AdminOnly
@Slf4j
@RestController
@AllArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.ReportPageQuery;
import io.github.malonetalk.dto.ReportResponse;
Expand All @@ -43,6 +44,7 @@ public Result<PageResponse<ReportResponse>> findReports(@Valid ReportPageQuery q
return Result.success(reportService.getReportPage(query));
}

@AdminOnly
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Integer id) {
RequestAssert.requireNonNegative(id, "id must be non-negative.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.ResetPasswordRequest;
import io.github.malonetalk.dto.UserCreateRequest;
import io.github.malonetalk.dto.UserResponse;
import io.github.malonetalk.dto.UserUpdateRequest;
import io.github.malonetalk.service.SysUserService;
import jakarta.validation.Valid;
import java.util.List;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* 用户管理 CRUD(权限轮次再加 @AdminOnly;本轮登录后即可用)。
*
* <p>username 唯一性由 Service 层保证;password 不允许通过 update 接口修改(需调用重置密码)。
*/
@AdminOnly
@RestController
@AllArgsConstructor
@RequestMapping("/api/sys/user")
@Validated
public class SysUserController {

private final SysUserService sysUserService;

@GetMapping
public Result<List<UserResponse>> listAll() {
return Result.success(sysUserService.listAll());
}

@PostMapping
public Result<UserResponse> create(@Valid @RequestBody UserCreateRequest request) {
return Result.success(sysUserService.create(request));
}

@PutMapping("/{id}")
public Result<UserResponse> update(
@PathVariable Integer id, @Valid @RequestBody UserUpdateRequest request) {
return Result.success(sysUserService.update(id, request));
}

/** 管理员重置用户密码(不需旧密码)。 */
@PutMapping("/{id}/password")
public Result<Boolean> resetPassword(
@PathVariable Integer id, @Valid @RequestBody ResetPasswordRequest request) {
sysUserService.resetPassword(id, request.newPassword());
return Result.success(true);
}

/** 启 / 停用户。 */
@PutMapping("/{id}/status")
public Result<Boolean> updateStatus(
@PathVariable Integer id,
@RequestParam
@jakarta.validation.constraints.Min(0)
@jakarta.validation.constraints.Max(1)
Integer status) {
sysUserService.updateStatus(id, status);
return Result.success(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.pagination.PageResponse;
import io.github.malonetalk.dto.semantic.BatchResetColumnSemanticRequest;
Expand All @@ -39,6 +40,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@AdminOnly
@RestController
@Validated
@RequestMapping("/api/semantic/tables/columns/{tableName}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.pagination.PageResponse;
import io.github.malonetalk.dto.semantic.BatchDeleteLogicalTableRelationRequest;
Expand All @@ -42,6 +43,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@AdminOnly
@RestController
@Validated
@RequestMapping("/api/semantic/tables/relations/{tableName}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.semantic.RelationWorkspacePageQuery;
import io.github.malonetalk.dto.semantic.RelationWorkspaceResponse;
Expand All @@ -27,6 +28,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@AdminOnly
@RestController
@RequestMapping("/api/semantic/tables/relations/workspace")
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.pagination.PageResponse;
import io.github.malonetalk.dto.semantic.BatchResetTableSemanticRequest;
Expand All @@ -39,6 +40,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@AdminOnly
@RestController
@Validated
@RequestMapping("/api/semantic/tables")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package io.github.malonetalk.controller;

import io.github.malonetalk.annotation.AdminOnly;
import io.github.malonetalk.common.Result;
import io.github.malonetalk.dto.pagination.PageResponse;
import io.github.malonetalk.dto.semantic.PhysicalTableCandidatePageQuery;
Expand All @@ -33,6 +34,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@AdminOnly
@RestController
@RequestMapping("/api/semantic/tables/sync")
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

/** 管理员重置用户密码(不需旧密码);区别于 {@link ChangePasswordRequest}(用户自己改,需验旧密码)。 */
public record ResetPasswordRequest(
@NotBlank(message = "newPassword 不能为空")
@Size(min = 6, max = 64, message = "newPassword 长度需在 6-64 之间")
String newPassword) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2026 github.com/MaloneTalk
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* limitations under the License.
*/
package io.github.malonetalk.dto;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record UserCreateRequest(
@NotBlank(message = "username 不能为空") String username,
@NotBlank(message = "password 不能为空")
@Size(min = 6, max = 64, message = "password 长度需在 6-64 之间")
String password,
@NotBlank(message = "displayName 不能为空") String displayName,
@NotNull @Min(0) @Max(1) Integer roleId) {}
Loading
Loading