mirror of https://github.com/usememos/memos.git
refactor: migrate HOST roles to ADMIN
- Updated the isSuperUser function to only check for ADMIN role. - Added SQL migration scripts for MySQL, PostgreSQL, and SQLite to change user roles from HOST to ADMIN. - Created a new SQLite migration to alter the user table structure and ensure data integrity during the migration process.
This commit is contained in:
parent
47ebb04dc3
commit
0f3c9a467d
|
|
@ -38,7 +38,7 @@ var (
|
|||
|
||||
if err := instanceProfile.Validate(); err != nil {
|
||||
slog.Error("failed to validate profile", "error", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
|
@ -46,21 +46,21 @@ var (
|
|||
if err != nil {
|
||||
cancel()
|
||||
slog.Error("failed to create db driver", "error", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
storeInstance := store.New(dbDriver, instanceProfile)
|
||||
if err := storeInstance.Migrate(ctx); err != nil {
|
||||
cancel()
|
||||
slog.Error("failed to migrate", "error", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := server.NewServer(ctx, instanceProfile, storeInstance)
|
||||
if err != nil {
|
||||
cancel()
|
||||
slog.Error("failed to create server", "error", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
|
|
@ -73,7 +73,7 @@ var (
|
|||
if err != http.ErrServerClosed {
|
||||
slog.Error("failed to start server", "error", err)
|
||||
cancel()
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -203,13 +203,10 @@ message User {
|
|||
|
||||
// User role enumeration.
|
||||
enum Role {
|
||||
// Unspecified role.
|
||||
ROLE_UNSPECIFIED = 0;
|
||||
// Host role with full system access.
|
||||
HOST = 1;
|
||||
// Admin role with administrative privileges.
|
||||
// Admin role with system access.
|
||||
ADMIN = 2;
|
||||
// Regular user role.
|
||||
// User role with limited access.
|
||||
USER = 3;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,13 +29,10 @@ const (
|
|||
type User_Role int32
|
||||
|
||||
const (
|
||||
// Unspecified role.
|
||||
User_ROLE_UNSPECIFIED User_Role = 0
|
||||
// Host role with full system access.
|
||||
User_HOST User_Role = 1
|
||||
// Admin role with administrative privileges.
|
||||
// Admin role with system access.
|
||||
User_ADMIN User_Role = 2
|
||||
// Regular user role.
|
||||
// User role with limited access.
|
||||
User_USER User_Role = 3
|
||||
)
|
||||
|
||||
|
|
@ -43,13 +40,11 @@ const (
|
|||
var (
|
||||
User_Role_name = map[int32]string{
|
||||
0: "ROLE_UNSPECIFIED",
|
||||
1: "HOST",
|
||||
2: "ADMIN",
|
||||
3: "USER",
|
||||
}
|
||||
User_Role_value = map[string]int32{
|
||||
"ROLE_UNSPECIFIED": 0,
|
||||
"HOST": 1,
|
||||
"ADMIN": 2,
|
||||
"USER": 3,
|
||||
}
|
||||
|
|
@ -2509,7 +2504,7 @@ var File_api_v1_user_service_proto protoreflect.FileDescriptor
|
|||
|
||||
const file_api_v1_user_service_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x19api/v1/user_service.proto\x12\fmemos.api.v1\x1a\x13api/v1/common.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xcb\x04\n" +
|
||||
"\x19api/v1/user_service.proto\x12\fmemos.api.v1\x1a\x13api/v1/common.proto\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xc1\x04\n" +
|
||||
"\x04User\x12\x17\n" +
|
||||
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x120\n" +
|
||||
"\x04role\x18\x02 \x01(\x0e2\x17.memos.api.v1.User.RoleB\x03\xe0A\x02R\x04role\x12\x1f\n" +
|
||||
|
|
@ -2525,10 +2520,9 @@ const file_api_v1_user_service_proto_rawDesc = "" +
|
|||
" \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
|
||||
"createTime\x12@\n" +
|
||||
"\vupdate_time\x18\v \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
|
||||
"updateTime\";\n" +
|
||||
"updateTime\"1\n" +
|
||||
"\x04Role\x12\x14\n" +
|
||||
"\x10ROLE_UNSPECIFIED\x10\x00\x12\b\n" +
|
||||
"\x04HOST\x10\x01\x12\t\n" +
|
||||
"\x10ROLE_UNSPECIFIED\x10\x00\x12\t\n" +
|
||||
"\x05ADMIN\x10\x02\x12\b\n" +
|
||||
"\x04USER\x10\x03:7\xeaA4\n" +
|
||||
"\x11memos.api.v1/User\x12\fusers/{user}\x1a\x04name*\x05users2\x04user\"\x9d\x01\n" +
|
||||
|
|
|
|||
|
|
@ -2859,7 +2859,6 @@ components:
|
|||
role:
|
||||
enum:
|
||||
- ROLE_UNSPECIFIED
|
||||
- HOST
|
||||
- ADMIN
|
||||
- USER
|
||||
type: string
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ func TestParseAccessTokenV2(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("parses token with different roles", func(t *testing.T) {
|
||||
roles := []string{"USER", "ADMIN", "HOST"}
|
||||
roles := []string{"USER", "ADMIN"}
|
||||
for _, role := range roles {
|
||||
token, _, err := GenerateAccessTokenV2(1, "testuser", role, "ACTIVE", secret)
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -64,5 +64,5 @@ func unmarshalPageToken(s string, pageToken *v1pb.PageToken) error {
|
|||
}
|
||||
|
||||
func isSuperUser(user *store.User) bool {
|
||||
return user.Role == store.RoleAdmin || user.Role == store.RoleHost
|
||||
return user.Role == store.RoleAdmin
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func (s *APIV1Service) CreateIdentityProvider(ctx context.Context, request *v1pb
|
|||
if currentUser == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if currentUser.Role != store.RoleHost {
|
||||
if currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ func (s *APIV1Service) UpdateIdentityProvider(ctx context.Context, request *v1pb
|
|||
if currentUser == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if currentUser.Role != store.RoleHost {
|
||||
if currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ func (s *APIV1Service) DeleteIdentityProvider(ctx context.Context, request *v1pb
|
|||
if currentUser == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if currentUser.Role != store.RoleHost {
|
||||
if currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ func convertIdentityProviderConfigToStore(identityProviderType v1pb.IdentityProv
|
|||
}
|
||||
|
||||
func redactIdentityProviderResponse(identityProvider *v1pb.IdentityProvider, userRole store.Role) *v1pb.IdentityProvider {
|
||||
if userRole != store.RoleHost {
|
||||
if userRole != store.RoleAdmin {
|
||||
if identityProvider.Type == v1pb.IdentityProvider_OAUTH2 {
|
||||
identityProvider.Config.GetOauth2Config().ClientSecret = ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.Get
|
|||
return nil, status.Errorf(codes.NotFound, "instance setting not found")
|
||||
}
|
||||
|
||||
// For storage setting, only host can get it.
|
||||
// For storage setting, only admin can get it.
|
||||
if instanceSetting.Key == storepb.InstanceSettingKey_STORAGE {
|
||||
user, err := s.fetchCurrentUser(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -73,7 +73,7 @@ func (s *APIV1Service) GetInstanceSetting(ctx context.Context, request *v1pb.Get
|
|||
if user == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if user.Role != store.RoleHost {
|
||||
if user.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ func (s *APIV1Service) UpdateInstanceSetting(ctx context.Context, request *v1pb.
|
|||
if user == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if user.Role != store.RoleHost {
|
||||
if user.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -277,9 +277,9 @@ func (s *APIV1Service) GetInstanceOwner(ctx context.Context) (*v1pb.User, error)
|
|||
return ownerCache, nil
|
||||
}
|
||||
|
||||
hostUserType := store.RoleHost
|
||||
adminUserType := store.RoleAdmin
|
||||
user, err := s.Store.GetUser(ctx, &store.FindUser{
|
||||
Role: &hostUserType,
|
||||
Role: &adminUserType,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find owner")
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func TestGetInstanceProfile(t *testing.T) {
|
|||
|
||||
// Verify the response contains expected data
|
||||
require.Equal(t, "test-1.0.0", resp.Version)
|
||||
require.Equal(t, "dev", resp.Mode)
|
||||
require.True(t, resp.Demo)
|
||||
require.Equal(t, "http://localhost:8080", resp.InstanceUrl)
|
||||
|
||||
// Owner should be empty since no users are created
|
||||
|
|
@ -55,7 +55,7 @@ func TestGetInstanceProfile(t *testing.T) {
|
|||
|
||||
// Verify the response contains expected data including owner
|
||||
require.Equal(t, "test-1.0.0", resp.Version)
|
||||
require.Equal(t, "dev", resp.Mode)
|
||||
require.True(t, resp.Demo)
|
||||
require.Equal(t, "http://localhost:8080", resp.InstanceUrl)
|
||||
|
||||
// User name should be "users/{id}" format where id is the user's ID
|
||||
|
|
@ -102,7 +102,7 @@ func TestGetInstanceProfile_Concurrency(t *testing.T) {
|
|||
case resp := <-results:
|
||||
require.NotNil(t, resp)
|
||||
require.Equal(t, "test-1.0.0", resp.Version)
|
||||
require.Equal(t, "dev", resp.Mode)
|
||||
require.True(t, resp.Demo)
|
||||
require.Equal(t, "http://localhost:8080", resp.InstanceUrl)
|
||||
require.Equal(t, expectedOwnerName, resp.Owner)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func NewTestService(t *testing.T) *TestService {
|
|||
|
||||
// Create a test profile
|
||||
testProfile := &profile.Profile{
|
||||
Mode: "dev",
|
||||
Demo: true,
|
||||
Version: "test-1.0.0",
|
||||
InstanceURL: "http://localhost:8080",
|
||||
Driver: "sqlite",
|
||||
|
|
@ -62,11 +62,11 @@ func (ts *TestService) Cleanup() {
|
|||
// Note: Owner cache is package-level in parent package, cannot clear from test package
|
||||
}
|
||||
|
||||
// CreateHostUser creates a host user for testing.
|
||||
// CreateHostUser creates an admin user for testing.
|
||||
func (ts *TestService) CreateHostUser(ctx context.Context, username string) (*store.User, error) {
|
||||
return ts.Store.CreateUser(ctx, &store.User{
|
||||
Username: username,
|
||||
Role: store.RoleHost,
|
||||
Role: store.RoleAdmin,
|
||||
Email: username + "@example.com",
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (s *APIV1Service) ListUsers(ctx context.Context, request *v1pb.ListUsersReq
|
|||
if currentUser == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
|
||||
if currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -132,17 +132,17 @@ func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserR
|
|||
// Determine the role to assign
|
||||
var roleToAssign store.Role
|
||||
if isFirstUser {
|
||||
// First-time setup: create the first user as HOST (no authentication required)
|
||||
roleToAssign = store.RoleHost
|
||||
} else if currentUser != nil && currentUser.Role == store.RoleHost {
|
||||
// Authenticated HOST user can create users with any role specified in request
|
||||
// First-time setup: create the first user as ADMIN (no authentication required)
|
||||
roleToAssign = store.RoleAdmin
|
||||
} else if currentUser != nil && currentUser.Role == store.RoleAdmin {
|
||||
// Authenticated ADMIN user can create users with any role specified in request
|
||||
if request.User.Role != v1pb.User_ROLE_UNSPECIFIED {
|
||||
roleToAssign = convertUserRoleToStore(request.User.Role)
|
||||
} else {
|
||||
roleToAssign = store.RoleUser
|
||||
}
|
||||
} else {
|
||||
// Unauthenticated or non-HOST users can only create normal users
|
||||
// Unauthenticated or non-ADMIN users can only create normal users
|
||||
roleToAssign = store.RoleUser
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR
|
|||
}
|
||||
// Check permission.
|
||||
// Only allow admin or self to update user.
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin && currentUser.Role != store.RoleHost {
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +264,7 @@ func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserR
|
|||
update.Description = &request.User.Description
|
||||
case "role":
|
||||
// Only allow admin to update role.
|
||||
if currentUser.Role != store.RoleAdmin && currentUser.Role != store.RoleHost {
|
||||
if currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
role := convertUserRoleToStore(request.User.Role)
|
||||
|
|
@ -301,7 +301,7 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR
|
|||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
|
||||
}
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin && currentUser.Role != store.RoleHost {
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -542,7 +542,7 @@ func (s *APIV1Service) ListPersonalAccessTokens(ctx context.Context, request *v1
|
|||
claims := auth.GetUserClaims(ctx)
|
||||
if claims == nil || claims.UserID != userID {
|
||||
currentUser, _ := s.fetchCurrentUser(ctx)
|
||||
if currentUser == nil || (currentUser.ID != userID && currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin) {
|
||||
if currentUser == nil || (currentUser.ID != userID && currentUser.Role != store.RoleAdmin) {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
}
|
||||
|
|
@ -689,7 +689,7 @@ func (s *APIV1Service) ListUserWebhooks(ctx context.Context, request *v1pb.ListU
|
|||
if currentUser == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -721,7 +721,7 @@ func (s *APIV1Service) CreateUserWebhook(ctx context.Context, request *v1pb.Crea
|
|||
if currentUser == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -761,7 +761,7 @@ func (s *APIV1Service) UpdateUserWebhook(ctx context.Context, request *v1pb.Upda
|
|||
if currentUser == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -833,7 +833,7 @@ func (s *APIV1Service) DeleteUserWebhook(ctx context.Context, request *v1pb.Dele
|
|||
if currentUser == nil {
|
||||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
|
||||
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
|
||||
|
|
@ -928,8 +928,6 @@ func convertUserFromStore(user *store.User) *v1pb.User {
|
|||
|
||||
func convertUserRoleFromStore(role store.Role) v1pb.User_Role {
|
||||
switch role {
|
||||
case store.RoleHost:
|
||||
return v1pb.User_HOST
|
||||
case store.RoleAdmin:
|
||||
return v1pb.User_ADMIN
|
||||
case store.RoleUser:
|
||||
|
|
@ -941,8 +939,6 @@ func convertUserRoleFromStore(role store.Role) v1pb.User_Role {
|
|||
|
||||
func convertUserRoleToStore(role v1pb.User_Role) store.Role {
|
||||
switch role {
|
||||
case v1pb.User_HOST:
|
||||
return store.RoleHost
|
||||
case v1pb.User_ADMIN:
|
||||
return store.RoleAdmin
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
UPDATE `user` SET `role` = 'ADMIN' WHERE `role` = 'HOST';
|
||||
|
|
@ -0,0 +1 @@
|
|||
UPDATE "user" SET role = 'ADMIN' WHERE role = 'HOST';
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
ALTER TABLE user RENAME TO user_old;
|
||||
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL DEFAULT 'USER',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
nickname TEXT NOT NULL DEFAULT '',
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar_url TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO user (
|
||||
id, created_ts, updated_ts, row_status, username, role, email, nickname, password_hash, avatar_url, description
|
||||
)
|
||||
SELECT
|
||||
id, created_ts, updated_ts, row_status, username, role, email, nickname, password_hash, avatar_url, description
|
||||
FROM user_old;
|
||||
|
||||
DROP TABLE user_old;
|
||||
|
|
@ -0,0 +1 @@
|
|||
UPDATE user SET role = 'ADMIN' WHERE role = 'HOST';
|
||||
|
|
@ -13,7 +13,7 @@ CREATE TABLE user (
|
|||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||
role TEXT NOT NULL DEFAULT 'USER',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
nickname TEXT NOT NULL DEFAULT '',
|
||||
password_hash TEXT NOT NULL,
|
||||
|
|
|
|||
|
|
@ -244,16 +244,16 @@ func (s *Store) preMigrate(ctx context.Context) error {
|
|||
return errors.Wrap(err, "failed to get current schema version")
|
||||
}
|
||||
slog.Info("database initialized successfully", slog.String("schemaVersion", schemaVersion))
|
||||
if err := s.updateCurrentSchemaVersion(ctx, schemaVersion); err != nil {
|
||||
return errors.Wrap(err, "failed to update current schema version")
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.checkMinimumUpgradeVersion(ctx); err != nil {
|
||||
return err // Error message is already descriptive, don't wrap it
|
||||
}
|
||||
return nil
|
||||
if err := s.updateCurrentSchemaVersion(ctx, schemaVersion); err != nil {
|
||||
return errors.Wrap(err, "failed to update current schema version")
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.checkMinimumUpgradeVersion(ctx); err != nil {
|
||||
return err // Error message is already descriptive, don't wrap it
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *Store) getMigrationBasePath() string {
|
||||
return fmt.Sprintf("migration/%s/", s.profile.Driver)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ The demo data includes **6 carefully selected memos** that showcase the key feat
|
|||
|
||||
- **Username**: `demo`
|
||||
- **Password**: `secret` (default password)
|
||||
- **Role**: HOST
|
||||
- **Role**: ADMIN
|
||||
- **Nickname**: Demo User
|
||||
|
||||
## Demo Memos (6 total)
|
||||
|
|
@ -198,7 +198,7 @@ Login with:
|
|||
|
||||
- All memos are set to PUBLIC visibility
|
||||
- **Two memos are pinned**: Welcome (#1) and Sponsor (#6)
|
||||
- User has HOST role to showcase all features
|
||||
- User has ADMIN role to showcase all features
|
||||
- Reactions are distributed across memos
|
||||
- One memo relation demonstrates linking
|
||||
- Content is optimized for the compact markdown styles
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- Demo User
|
||||
INSERT INTO user (id,username,role,nickname,password_hash) VALUES(1,'demo','HOST','Demo User','$2a$10$c.slEVgf5b/3BnAWlLb/vOu7VVSOKJ4ljwMe9xzlx9IhKnvAsJYM6');
|
||||
INSERT INTO user (id,username,role,nickname,password_hash) VALUES(1,'demo','ADMIN','Demo User','$2a$10$c.slEVgf5b/3BnAWlLb/vOu7VVSOKJ4ljwMe9xzlx9IhKnvAsJYM6');
|
||||
|
||||
-- Welcome Memo (Pinned)
|
||||
INSERT INTO memo (id,uid,creator_id,content,visibility,pinned,payload) VALUES(1,'welcome2memos001',1,replace('# Welcome to Memos!\\n\\nA privacy-first, lightweight note-taking service. Easily capture and share your great thoughts.\\n\\n## Key Features\\n\\n- **Privacy First**: Your data stays with you\\n- **Markdown Support**: Full CommonMark + GFM syntax\\n- **Quick Capture**: Jot down thoughts instantly\\n- **Organize with Tags**: Use #tags to categorize\\n- **Open Source**: Free and open source software\\n\\n---\\n\\nStart exploring the demo memos below to see what you can do! #welcome #getting-started','\\n',char(10)),'PUBLIC',1,'{"tags":["welcome","getting-started"],"property":{"hasLink":false}}');
|
||||
|
|
|
|||
|
|
@ -41,12 +41,11 @@ func NewTestingStore(ctx context.Context, t *testing.T) *store.Store {
|
|||
// This is useful for testing migrations on existing data.
|
||||
func NewTestingStoreWithDSN(_ context.Context, t *testing.T, driver, dsn string) *store.Store {
|
||||
profile := &profile.Profile{
|
||||
Mode: "prod",
|
||||
Port: getUnusedPort(),
|
||||
Data: t.TempDir(), // Dummy dir, DSN matters
|
||||
DSN: dsn,
|
||||
Driver: driver,
|
||||
Version: version.GetCurrentVersion("prod"),
|
||||
Version: version.GetCurrentVersion(),
|
||||
}
|
||||
dbDriver, err := db.NewDBDriver(profile)
|
||||
if err != nil {
|
||||
|
|
@ -95,12 +94,11 @@ func getTestingProfileForDriver(t *testing.T, driver string) *profile.Profile {
|
|||
}
|
||||
|
||||
return &profile.Profile{
|
||||
Mode: mode,
|
||||
Port: port,
|
||||
Data: dir,
|
||||
DSN: dsn,
|
||||
Driver: driver,
|
||||
Version: version.GetCurrentVersion(mode),
|
||||
Version: version.GetCurrentVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func TestUserStore(t *testing.T) {
|
|||
users, err := ts.ListUsers(ctx, &store.FindUser{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(users))
|
||||
require.Equal(t, store.RoleHost, users[0].Role)
|
||||
require.Equal(t, store.RoleAdmin, users[0].Role)
|
||||
require.Equal(t, user, users[0])
|
||||
userPatchNickname := "test_nickname_2"
|
||||
userPatch := &store.UpdateUser{
|
||||
|
|
@ -104,7 +104,7 @@ func TestUserListByRole(t *testing.T) {
|
|||
_, err := createTestingHostUser(ctx, ts)
|
||||
require.NoError(t, err)
|
||||
|
||||
adminUser, err := createTestingUserWithRole(ctx, ts, "admin_user", store.RoleAdmin)
|
||||
_, err = createTestingUserWithRole(ctx, ts, "admin_user", store.RoleAdmin)
|
||||
require.NoError(t, err)
|
||||
|
||||
regularUser, err := createTestingUserWithRole(ctx, ts, "regular_user", store.RoleUser)
|
||||
|
|
@ -115,19 +115,11 @@ func TestUserListByRole(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(allUsers))
|
||||
|
||||
// List only HOST users
|
||||
hostRole := store.RoleHost
|
||||
hostUsers, err := ts.ListUsers(ctx, &store.FindUser{Role: &hostRole})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(hostUsers))
|
||||
require.Equal(t, store.RoleHost, hostUsers[0].Role)
|
||||
|
||||
// List only ADMIN users
|
||||
adminRole := store.RoleAdmin
|
||||
adminUsers, err := ts.ListUsers(ctx, &store.FindUser{Role: &adminRole})
|
||||
adminOnlyUsers, err := ts.ListUsers(ctx, &store.FindUser{Role: &adminRole})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(adminUsers))
|
||||
require.Equal(t, adminUser.ID, adminUsers[0].ID)
|
||||
require.Equal(t, 2, len(adminOnlyUsers))
|
||||
|
||||
// List only USER role users
|
||||
userRole := store.RoleUser
|
||||
|
|
@ -227,7 +219,7 @@ func TestUserListWithLimit(t *testing.T) {
|
|||
for i := 0; i < 5; i++ {
|
||||
role := store.RoleUser
|
||||
if i == 0 {
|
||||
role = store.RoleHost
|
||||
role = store.RoleAdmin
|
||||
}
|
||||
_, err := createTestingUserWithRole(ctx, ts, fmt.Sprintf("user%d", i), role)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -243,7 +235,7 @@ func TestUserListWithLimit(t *testing.T) {
|
|||
}
|
||||
|
||||
func createTestingHostUser(ctx context.Context, ts *store.Store) (*store.User, error) {
|
||||
return createTestingUserWithRole(ctx, ts, "test", store.RoleHost)
|
||||
return createTestingUserWithRole(ctx, ts, "test", store.RoleAdmin)
|
||||
}
|
||||
|
||||
func createTestingUserWithRole(ctx context.Context, ts *store.Store, username string, role store.Role) (*store.User, error) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import (
|
|||
type Role string
|
||||
|
||||
const (
|
||||
// RoleHost is the HOST role.
|
||||
RoleHost Role = "HOST"
|
||||
// RoleAdmin is the ADMIN role.
|
||||
RoleAdmin Role = "ADMIN"
|
||||
// RoleUser is the USER role.
|
||||
|
|
@ -18,8 +16,6 @@ const (
|
|||
|
||||
func (e Role) String() string {
|
||||
switch e {
|
||||
case RoleHost:
|
||||
return "HOST"
|
||||
case RoleAdmin:
|
||||
return "ADMIN"
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@ const MemberSection = () => {
|
|||
const [deleteTarget, setDeleteTarget] = useState<User | undefined>(undefined);
|
||||
|
||||
const stringifyUserRole = (role: User_Role) => {
|
||||
if (role === User_Role.HOST) {
|
||||
return "Host";
|
||||
} else if (role === User_Role.ADMIN) {
|
||||
if (role === User_Role.ADMIN) {
|
||||
return t("setting.member-section.admin");
|
||||
} else {
|
||||
return t("setting.member-section.user");
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const Setting = () => {
|
|||
const [state, setState] = useState<State>({
|
||||
selectedSection: "my-account",
|
||||
});
|
||||
const isHost = user?.role === User_Role.HOST;
|
||||
const isHost = user?.role === User_Role.ADMIN;
|
||||
|
||||
const settingsSectionList = useMemo(() => {
|
||||
let settingList = [...BASIC_SECTIONS];
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,5 @@
|
|||
import { User, User_Role } from "@/types/proto/api/v1/user_service_pb";
|
||||
|
||||
export const isSuperUser = (user: User | undefined) => {
|
||||
return user && (user.role === User_Role.ADMIN || user.role === User_Role.HOST);
|
||||
return user && user.role === User_Role.ADMIN;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue