1
0
forked from baron/baron-sso

chore: snapshot local state before dev merge

This commit is contained in:
2026-06-17 21:25:42 +09:00
parent b2808759d2
commit 49560e8a8c
107 changed files with 8958 additions and 939 deletions

View File

@@ -69,6 +69,26 @@ func (r *userRepository) withTenantMembershipFilter(db *gorm.DB, tenantIDs []str
clauses = append(clauses, "metadata @> ?::jsonb")
args = append(args, string(payload))
}
clauses = append(clauses, `EXISTS (
SELECT 1
FROM tenants AS membership_tenants
WHERE membership_tenants.id IN ?
AND users.metadata @> jsonb_build_object(
'additionalAppointments',
jsonb_build_array(jsonb_build_object('tenantSlug', membership_tenants.slug))
)
)`)
args = append(args, tenantIDs)
clauses = append(clauses, `EXISTS (
SELECT 1
FROM tenants AS membership_tenants
WHERE membership_tenants.id IN ?
AND users.metadata @> jsonb_build_object(
'additionalAppointments',
jsonb_build_array(jsonb_build_object('tenant_slug', membership_tenants.slug))
)
)`)
args = append(args, tenantIDs)
return db.Where("("+strings.Join(clauses, " OR ")+")", args...)
}
@@ -170,11 +190,63 @@ func (r *userRepository) CountByTenantIDs(ctx context.Context, tenantIDs []strin
}
for _, tenantID := range tenantIDs {
var count int64
if err := r.withTenantMembershipFilter(r.db.WithContext(ctx).Model(&domain.User{}), []string{tenantID}).Count(&count).Error; err != nil {
return nil, err
tenantID = strings.TrimSpace(tenantID)
if tenantID != "" {
counts[tenantID] = 0
}
counts[tenantID] = count
}
type result struct {
TenantID string
Count int64
}
var results []result
if err := r.db.WithContext(ctx).Raw(`
WITH requested_tenants AS (
SELECT id, slug
FROM tenants
WHERE id IN ?
),
memberships AS (
SELECT users.id AS user_id, users.tenant_id::text AS tenant_id
FROM users
WHERE users.deleted_at IS NULL
AND users.tenant_id IN ?
UNION ALL
SELECT users.id AS user_id, appointment ->> 'tenantId' AS tenant_id
FROM users
CROSS JOIN LATERAL jsonb_array_elements(COALESCE(users.metadata -> 'additionalAppointments', '[]'::jsonb)) AS appointment
WHERE users.deleted_at IS NULL
AND appointment ->> 'tenantId' IN ?
UNION ALL
SELECT users.id AS user_id, requested_tenants.id::text AS tenant_id
FROM users
CROSS JOIN LATERAL jsonb_array_elements(COALESCE(users.metadata -> 'additionalAppointments', '[]'::jsonb)) AS appointment
JOIN requested_tenants ON LOWER(requested_tenants.slug) = LOWER(appointment ->> 'tenantSlug')
WHERE users.deleted_at IS NULL
UNION ALL
SELECT users.id AS user_id, requested_tenants.id::text AS tenant_id
FROM users
CROSS JOIN LATERAL jsonb_array_elements(COALESCE(users.metadata -> 'additionalAppointments', '[]'::jsonb)) AS appointment
JOIN requested_tenants ON LOWER(requested_tenants.slug) = LOWER(appointment ->> 'tenant_slug')
WHERE users.deleted_at IS NULL
)
SELECT tenant_id, COUNT(DISTINCT user_id) AS count
FROM memberships
WHERE tenant_id IN ?
GROUP BY tenant_id
`, tenantIDs, tenantIDs, tenantIDs, tenantIDs).Scan(&results).Error; err != nil {
return nil, err
}
for _, result := range results {
counts[result.TenantID] = result.Count
}
return counts, nil
}

View File

@@ -54,6 +54,36 @@ func TestUserRepository(t *testing.T) {
assert.Equal(t, "010-1234-5678", found.Phone)
})
t.Run("Create and Update preserve top-level user grade for compatibility", func(t *testing.T) {
testDB.Exec("DELETE FROM user_login_ids")
testDB.Exec("DELETE FROM users WHERE email IN ?", []string{"grade-create@example.com", "grade-update@example.com"})
created := &domain.User{
Email: "grade-create@example.com",
Name: "Grade Create",
Role: domain.RoleUser,
Grade: "책임",
}
require.NoError(t, repo.Create(ctx, created))
found, err := repo.FindByEmail(ctx, created.Email)
require.NoError(t, err)
require.Equal(t, "책임", found.Grade)
updated := &domain.User{
ID: uuid.NewString(),
Email: "grade-update@example.com",
Name: "Grade Update",
Role: domain.RoleUser,
Grade: "수석",
}
require.NoError(t, repo.Update(ctx, updated))
found, err = repo.FindByEmail(ctx, updated.Email)
require.NoError(t, err)
require.Equal(t, "수석", found.Grade)
})
t.Run("Update preserves archived email reservation", func(t *testing.T) {
testDB.Exec("DELETE FROM user_login_ids")
testDB.Exec("DELETE FROM users")
@@ -273,6 +303,45 @@ func TestUserRepository_ListIncludesAdditionalTenantAppointments(t *testing.T) {
assert.Equal(t, int64(2), counts[additionalTenant.ID])
}
func TestUserRepository_ListIncludesAdditionalTenantAppointmentsBySlug(t *testing.T) {
repo := NewUserRepository(testDB)
ctx := context.Background()
require.NoError(t, testDB.Exec("DELETE FROM user_login_ids").Error)
require.NoError(t, testDB.Exec("DELETE FROM users").Error)
primaryTenant := createUserRepositoryTestTenant(t, "repo-private-primary-tenant")
visibleTenant := createUserRepositoryTestTenant(t, "repo-visible-leader-tenant")
primaryTenantID := primaryTenant.ID
user := domain.User{
ID: uuid.NewString(),
Email: "slug-appointment-leader@example.com",
Name: "Slug Appointment Leader",
Role: domain.RoleUser,
TenantID: &primaryTenantID,
Metadata: domain.JSONMap{
"additionalAppointments": []any{
map[string]any{
"tenantSlug": visibleTenant.Slug,
"tenantName": visibleTenant.Name,
"isOwner": true,
},
},
},
}
require.NoError(t, repo.Create(ctx, &user))
listed, total, _, err := repo.List(ctx, 0, 20, "", []string{visibleTenant.ID}, "")
require.NoError(t, err)
require.Equal(t, int64(1), total)
require.Len(t, listed, 1)
assert.Equal(t, "slug-appointment-leader@example.com", listed[0].Email)
counts, err := repo.CountByTenantIDs(ctx, []string{visibleTenant.ID})
require.NoError(t, err)
assert.Equal(t, int64(1), counts[visibleTenant.ID])
}
func createUserRepositoryTestTenant(t *testing.T, slug string) domain.Tenant {
t.Helper()
require.NoError(t, testDB.Unscoped().Where("slug = ?", slug).Delete(&domain.Tenant{}).Error)