This is an automated email from the ASF dual-hosted git repository.

DImuthuUpe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git

commit 4ea551e4a3be0c3f46ac4e4c4bc35ae2b04d8b33
Author: lahiruj <[email protected]>
AuthorDate: Thu May 21 16:31:35 2026 -0400

    Drop UserDN and UserMerge models and related services from core
---
 ...ns.down.sql => 000017_user_identities.down.sql} |   1 -
 ...er_dns.up.sql => 000017_user_identities.up.sql} |  14 ---
 internal/db/migrations/000018_user_merges.down.sql |  18 ---
 internal/db/migrations/000018_user_merges.up.sql   |  30 -----
 internal/server/server.go                          |  82 +------------
 internal/store/store.go                            |  28 -----
 internal/store/user_dn_store.go                    | 103 -----------------
 internal/store/user_merge_store.go                 |  77 -------------
 pkg/events/types.go                                |   7 --
 pkg/events/user_dn_subscribe.go                    |  57 ---------
 pkg/models/identity.go                             |  10 --
 pkg/models/project.go                              |  11 --
 pkg/service/service.go                             |   8 --
 pkg/service/user_dn.go                             | 127 ---------------------
 pkg/service/user_merge.go                          |  64 ++---------
 15 files changed, 10 insertions(+), 627 deletions(-)

diff --git 
a/internal/db/migrations/000017_user_identities_and_user_dns.down.sql 
b/internal/db/migrations/000017_user_identities.down.sql
similarity index 96%
rename from internal/db/migrations/000017_user_identities_and_user_dns.down.sql
rename to internal/db/migrations/000017_user_identities.down.sql
index 72d75c517..08b342405 100644
--- a/internal/db/migrations/000017_user_identities_and_user_dns.down.sql
+++ b/internal/db/migrations/000017_user_identities.down.sql
@@ -15,5 +15,4 @@
 -- specific language governing permissions and limitations
 -- under the License.
 
-DROP TABLE IF EXISTS user_dns;
 DROP TABLE IF EXISTS user_identities;
diff --git a/internal/db/migrations/000017_user_identities_and_user_dns.up.sql 
b/internal/db/migrations/000017_user_identities.up.sql
similarity index 76%
rename from internal/db/migrations/000017_user_identities_and_user_dns.up.sql
rename to internal/db/migrations/000017_user_identities.up.sql
index 313f1de71..249468c89 100644
--- a/internal/db/migrations/000017_user_identities_and_user_dns.up.sql
+++ b/internal/db/migrations/000017_user_identities.up.sql
@@ -36,17 +36,3 @@ CREATE TABLE IF NOT EXISTS user_identities
     KEY idx_user_identities_user (user_id),
     CONSTRAINT fk_user_identities_user FOREIGN KEY (user_id) REFERENCES users 
(id) ON DELETE CASCADE
 ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
-
--- A DN is a globally-unique credential. UNIQUE on dn alone subsumes
--- (user_id, dn), so the composite index is omitted.
-CREATE TABLE IF NOT EXISTS user_dns
-(
-    id         VARCHAR(255) NOT NULL,
-    user_id    VARCHAR(255) NOT NULL,
-    dn         VARCHAR(512) NOT NULL,
-    created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
-    PRIMARY KEY (id),
-    UNIQUE KEY uq_user_dns_dn (dn),
-    KEY idx_user_dns_user (user_id),
-    CONSTRAINT fk_user_dns_user FOREIGN KEY (user_id) REFERENCES users (id) ON 
DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
diff --git a/internal/db/migrations/000018_user_merges.down.sql 
b/internal/db/migrations/000018_user_merges.down.sql
deleted file mode 100644
index bc65c15a1..000000000
--- a/internal/db/migrations/000018_user_merges.down.sql
+++ /dev/null
@@ -1,18 +0,0 @@
--- Licensed to the Apache Software Foundation (ASF) under one
--- or more contributor license agreements.  See the NOTICE file
--- distributed with this work for additional information
--- regarding copyright ownership.  The ASF licenses this file
--- to you under the Apache License, Version 2.0 (the
--- "License"); you may not use this file except in compliance
--- with the License.  You may obtain a copy of the License at
---
---   http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing,
--- software distributed under the License is distributed on an
--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
--- KIND, either express or implied.  See the License for the
--- specific language governing permissions and limitations
--- under the License.
-
-DROP TABLE IF EXISTS user_merges;
diff --git a/internal/db/migrations/000018_user_merges.up.sql 
b/internal/db/migrations/000018_user_merges.up.sql
deleted file mode 100644
index 7aeeb8844..000000000
--- a/internal/db/migrations/000018_user_merges.up.sql
+++ /dev/null
@@ -1,30 +0,0 @@
--- Licensed to the Apache Software Foundation (ASF) under one
--- or more contributor license agreements.  See the NOTICE file
--- distributed with this work for additional information
--- regarding copyright ownership.  The ASF licenses this file
--- to you under the Apache License, Version 2.0 (the
--- "License"); you may not use this file except in compliance
--- with the License.  You may obtain a copy of the License at
---
---   http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing,
--- software distributed under the License is distributed on an
--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
--- KIND, either express or implied.  See the License for the
--- specific language governing permissions and limitations
--- under the License.
-
-CREATE TABLE IF NOT EXISTS user_merges
-(
-    id                BIGINT       NOT NULL AUTO_INCREMENT,
-    retiring_user_id  VARCHAR(255) NOT NULL,
-    surviving_user_id VARCHAR(255) NOT NULL,
-    reason            TEXT         NULL,
-    merged_at         TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
-    PRIMARY KEY (id),
-    UNIQUE KEY uq_user_merges_retiring (retiring_user_id),
-    KEY idx_user_merges_surviving (surviving_user_id),
-    CONSTRAINT fk_user_merges_retiring  FOREIGN KEY (retiring_user_id)  
REFERENCES users (id) ON DELETE RESTRICT,
-    CONSTRAINT fk_user_merges_surviving FOREIGN KEY (surviving_user_id) 
REFERENCES users (id) ON DELETE RESTRICT
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
diff --git a/internal/server/server.go b/internal/server/server.go
index 30e05a9a3..9844b8d3a 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -58,8 +58,6 @@ func (s *Server) routes() {
        s.mux.HandleFunc("GET /users/{id}", s.getUser)
        s.mux.HandleFunc("PUT /users/{id}/status", s.updateUserStatus)
        s.mux.HandleFunc("POST /users/merge", s.mergeUsers)
-       s.mux.HandleFunc("GET /users/{id}/merge", s.getUserMergeByRetiringUser)
-       s.mux.HandleFunc("GET /users/{id}/merged-users", 
s.listUserMergesBySurvivingUser)
 
        s.mux.HandleFunc("POST /projects", s.createProject)
        s.mux.HandleFunc("GET /projects/{id}", s.getProject)
@@ -146,11 +144,6 @@ func (s *Server) routes() {
        s.mux.HandleFunc("GET /user-identities/oidc-subjects/{oidcSub}", 
s.getUserIdentityByOIDCSub)
        s.mux.HandleFunc("GET /users/{id}/user-identities", 
s.listUserIdentitiesForUser)
 
-       s.mux.HandleFunc("POST /user-dns", s.addUserDN)
-       s.mux.HandleFunc("GET /user-dns/{id}", s.getUserDN)
-       s.mux.HandleFunc("DELETE /user-dns/{id}", s.removeUserDN)
-       s.mux.HandleFunc("GET /user-dns/lookup", s.getUserDNByDN)
-       s.mux.HandleFunc("GET /users/{id}/user-dns", s.listUserDNs)
 }
 
 func (s *Server) healthz(w http.ResponseWriter, _ *http.Request) {
@@ -1000,64 +993,9 @@ func (s *Server) deleteUserIdentity(w 
http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNoContent)
 }
 
-func (s *Server) addUserDN(w http.ResponseWriter, r *http.Request) {
-       var d models.UserDN
-       if err := decodeJSON(r, &d); err != nil {
-               writeError(w, http.StatusBadRequest, err)
-               return
-       }
-       created, err := s.svc.AddUserDN(r.Context(), &d)
-       if err != nil {
-               writeServiceError(w, err)
-               return
-       }
-       writeJSON(w, http.StatusCreated, created)
-}
-
-func (s *Server) getUserDN(w http.ResponseWriter, r *http.Request) {
-       d, err := s.svc.GetUserDN(r.Context(), r.PathValue("id"))
-       if err != nil {
-               writeServiceError(w, err)
-               return
-       }
-       writeJSON(w, http.StatusOK, d)
-}
-
-func (s *Server) getUserDNByDN(w http.ResponseWriter, r *http.Request) {
-       dn := r.URL.Query().Get("dn")
-       if dn == "" {
-               writeError(w, http.StatusBadRequest, errors.New("dn query 
parameter is required"))
-               return
-       }
-       d, err := s.svc.GetUserDNByDN(r.Context(), dn)
-       if err != nil {
-               writeServiceError(w, err)
-               return
-       }
-       writeJSON(w, http.StatusOK, d)
-}
-
-func (s *Server) listUserDNs(w http.ResponseWriter, r *http.Request) {
-       out, err := s.svc.ListUserDNs(r.Context(), r.PathValue("id"))
-       if err != nil {
-               writeServiceError(w, err)
-               return
-       }
-       writeJSON(w, http.StatusOK, out)
-}
-
-func (s *Server) removeUserDN(w http.ResponseWriter, r *http.Request) {
-       if err := s.svc.RemoveUserDN(r.Context(), r.PathValue("id")); err != 
nil {
-               writeServiceError(w, err)
-               return
-       }
-       w.WriteHeader(http.StatusNoContent)
-}
-
 type mergeUsersRequest struct {
        SurvivingUserID string `json:"surviving_user_id"`
        RetiringUserID  string `json:"retiring_user_id"`
-       Reason          string `json:"reason,omitempty"`
 }
 
 func (s *Server) mergeUsers(w http.ResponseWriter, r *http.Request) {
@@ -1066,7 +1004,7 @@ func (s *Server) mergeUsers(w http.ResponseWriter, r 
*http.Request) {
                writeError(w, http.StatusBadRequest, err)
                return
        }
-       survivor, err := s.svc.MergeUsers(r.Context(), req.SurvivingUserID, 
req.RetiringUserID, req.Reason)
+       survivor, err := s.svc.MergeUsers(r.Context(), req.SurvivingUserID, 
req.RetiringUserID)
        if err != nil {
                writeServiceError(w, err)
                return
@@ -1074,24 +1012,6 @@ func (s *Server) mergeUsers(w http.ResponseWriter, r 
*http.Request) {
        writeJSON(w, http.StatusOK, survivor)
 }
 
-func (s *Server) getUserMergeByRetiringUser(w http.ResponseWriter, r 
*http.Request) {
-       m, err := s.svc.GetUserMergeByRetiringUser(r.Context(), 
r.PathValue("id"))
-       if err != nil {
-               writeServiceError(w, err)
-               return
-       }
-       writeJSON(w, http.StatusOK, m)
-}
-
-func (s *Server) listUserMergesBySurvivingUser(w http.ResponseWriter, r 
*http.Request) {
-       out, err := s.svc.ListUserMergesBySurvivingUser(r.Context(), 
r.PathValue("id"))
-       if err != nil {
-               writeServiceError(w, err)
-               return
-       }
-       writeJSON(w, http.StatusOK, out)
-}
-
 // LoggingMiddleware logs every request once it completes.
 func LoggingMiddleware(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/store/store.go b/internal/store/store.go
index 797bb5526..f3a7a6b00 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -43,17 +43,6 @@ type UserStore interface {
        Delete(ctx context.Context, tx *sql.Tx, id string) error
 }
 
-// UserMergeStore records when one user is consolidated into another. Rows are
-// append-only; each retiring user can be merged at most once.
-type UserMergeStore interface {
-       // Record inserts a new merge record within the provided transaction.
-       Record(ctx context.Context, tx *sql.Tx, retiringUserID, 
survivingUserID, reason string) error
-       // FindByRetiringUser returns the merge record whose retiring user 
matches, or nil if absent.
-       FindByRetiringUser(ctx context.Context, retiringUserID string) 
(*models.UserMerge, error)
-       // FindBySurvivingUser returns every merge record whose survivor 
matches, oldest first.
-       FindBySurvivingUser(ctx context.Context, survivingUserID string) 
([]models.UserMerge, error)
-}
-
 // OrganizationStore defines persistence operations for organizations.
 type OrganizationStore interface {
        // FindByID returns the organization with the given ID, or nil if not 
found.
@@ -130,23 +119,6 @@ type UserIdentityStore interface {
        Delete(ctx context.Context, tx *sql.Tx, id string) error
 }
 
-// UserDNStore defines persistence operations for X.509 distinguished-name
-// bindings against a Custos user.
-type UserDNStore interface {
-       // FindByID returns the DN binding with the given ID, or nil if not 
found.
-       FindByID(ctx context.Context, id string) (*models.UserDN, error)
-       // FindByDN returns the binding matching the given DN, or nil if absent.
-       FindByDN(ctx context.Context, dn string) (*models.UserDN, error)
-       // FindByUser returns every DN bound to the given user, ordered by 
created_at.
-       FindByUser(ctx context.Context, userID string) ([]models.UserDN, error)
-       // Create inserts a new DN binding within the provided transaction.
-       Create(ctx context.Context, tx *sql.Tx, d *models.UserDN) error
-       // ReassignUser moves every DN owned by fromUserID over to toUserID, 
dropping duplicates.
-       ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID 
string) error
-       // Delete removes a DN binding by ID within the provided transaction.
-       Delete(ctx context.Context, tx *sql.Tx, id string) error
-}
-
 // ProjectStore defines persistence operations for projects.
 type ProjectStore interface {
        // FindByID returns the project with the given ID, or nil if not found.
diff --git a/internal/store/user_dn_store.go b/internal/store/user_dn_store.go
deleted file mode 100644
index bfe6f69fe..000000000
--- a/internal/store/user_dn_store.go
+++ /dev/null
@@ -1,103 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package store
-
-import (
-       "context"
-       "database/sql"
-       "errors"
-
-       "github.com/jmoiron/sqlx"
-
-       "github.com/apache/airavata-custos/pkg/models"
-)
-
-type mysqlUserDNStore struct {
-       db *sqlx.DB
-}
-
-// NewUserDNStore returns a MySQL-backed UserDNStore.
-func NewUserDNStore(db *sqlx.DB) UserDNStore {
-       return &mysqlUserDNStore{db: db}
-}
-
-const userDNColumns = `id, user_id, dn, created_at`
-
-func (s *mysqlUserDNStore) FindByID(ctx context.Context, id string) 
(*models.UserDN, error) {
-       var d models.UserDN
-       err := s.db.GetContext(ctx, &d,
-               `SELECT `+userDNColumns+` FROM user_dns WHERE id = ?`, id)
-       if err != nil {
-               if errors.Is(err, sql.ErrNoRows) {
-                       return nil, nil
-               }
-               return nil, err
-       }
-       return &d, nil
-}
-
-func (s *mysqlUserDNStore) FindByDN(ctx context.Context, dn string) 
(*models.UserDN, error) {
-       var d models.UserDN
-       err := s.db.GetContext(ctx, &d,
-               `SELECT `+userDNColumns+` FROM user_dns WHERE dn = ?`, dn)
-       if err != nil {
-               if errors.Is(err, sql.ErrNoRows) {
-                       return nil, nil
-               }
-               return nil, err
-       }
-       return &d, nil
-}
-
-func (s *mysqlUserDNStore) FindByUser(ctx context.Context, userID string) 
([]models.UserDN, error) {
-       var out []models.UserDN
-       err := s.db.SelectContext(ctx, &out,
-               `SELECT `+userDNColumns+` FROM user_dns WHERE user_id = ? ORDER 
BY created_at ASC`,
-               userID)
-       if err != nil {
-               return nil, err
-       }
-       return out, nil
-}
-
-func (s *mysqlUserDNStore) Create(ctx context.Context, tx *sql.Tx, d 
*models.UserDN) error {
-       _, err := tx.ExecContext(ctx,
-               `INSERT INTO user_dns (id, user_id, dn) VALUES (?, ?, ?)`,
-               d.ID, d.UserID, d.DN)
-       return err
-}
-
-func (s *mysqlUserDNStore) ReassignUser(ctx context.Context, tx *sql.Tx, 
fromUserID, toUserID string) error {
-       // Drop fromUserID's DNs already held by the survivor, then move the 
rest.
-       if _, err := tx.ExecContext(ctx,
-               `DELETE FROM user_dns
-                WHERE user_id = ?
-                  AND dn IN (SELECT dn FROM (SELECT dn FROM user_dns WHERE 
user_id = ?) AS s)`,
-               fromUserID, toUserID); err != nil {
-               return err
-       }
-       _, err := tx.ExecContext(ctx,
-               `UPDATE user_dns SET user_id = ? WHERE user_id = ?`,
-               toUserID, fromUserID)
-       return err
-}
-
-func (s *mysqlUserDNStore) Delete(ctx context.Context, tx *sql.Tx, id string) 
error {
-       _, err := tx.ExecContext(ctx, `DELETE FROM user_dns WHERE id = ?`, id)
-       return err
-}
diff --git a/internal/store/user_merge_store.go 
b/internal/store/user_merge_store.go
deleted file mode 100644
index 827462e55..000000000
--- a/internal/store/user_merge_store.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package store
-
-import (
-       "context"
-       "database/sql"
-       "errors"
-
-       "github.com/jmoiron/sqlx"
-
-       "github.com/apache/airavata-custos/pkg/models"
-)
-
-type mysqlUserMergeStore struct {
-       db *sqlx.DB
-}
-
-// NewUserMergeStore returns a MySQL-backed UserMergeStore.
-func NewUserMergeStore(db *sqlx.DB) UserMergeStore {
-       return &mysqlUserMergeStore{db: db}
-}
-
-const userMergeColumns = `id, retiring_user_id, surviving_user_id, 
COALESCE(reason, '') AS reason, merged_at`
-
-func (s *mysqlUserMergeStore) Record(ctx context.Context, tx *sql.Tx, 
retiringUserID, survivingUserID, reason string) error {
-       var reasonArg any
-       if reason == "" {
-               reasonArg = nil
-       } else {
-               reasonArg = reason
-       }
-       _, err := tx.ExecContext(ctx,
-               `INSERT INTO user_merges (retiring_user_id, surviving_user_id, 
reason)
-                VALUES (?, ?, ?)`,
-               retiringUserID, survivingUserID, reasonArg)
-       return err
-}
-
-func (s *mysqlUserMergeStore) FindByRetiringUser(ctx context.Context, 
retiringUserID string) (*models.UserMerge, error) {
-       var m models.UserMerge
-       err := s.db.GetContext(ctx, &m,
-               `SELECT `+userMergeColumns+` FROM user_merges WHERE 
retiring_user_id = ?`, retiringUserID)
-       if err != nil {
-               if errors.Is(err, sql.ErrNoRows) {
-                       return nil, nil
-               }
-               return nil, err
-       }
-       return &m, nil
-}
-
-func (s *mysqlUserMergeStore) FindBySurvivingUser(ctx context.Context, 
survivingUserID string) ([]models.UserMerge, error) {
-       var out []models.UserMerge
-       err := s.db.SelectContext(ctx, &out,
-               `SELECT `+userMergeColumns+` FROM user_merges WHERE 
surviving_user_id = ? ORDER BY merged_at ASC`,
-               survivingUserID)
-       if err != nil {
-               return nil, err
-       }
-       return out, nil
-}
diff --git a/pkg/events/types.go b/pkg/events/types.go
index 6cfc6300a..4110782c6 100644
--- a/pkg/events/types.go
+++ b/pkg/events/types.go
@@ -117,13 +117,6 @@ const (
        UserIdentityDeleteEvent EventType = "user_identity::delete"
 )
 
-// UserDN lifecycle message types. DN bindings are append-only credentials, so
-// no update topic.
-const (
-       UserDNCreateEvent EventType = "user_dn::create"
-       UserDNDeleteEvent EventType = "user_dn::delete"
-)
-
 // Event represents a change in the system that downstream consumers may be 
interested in.
 // The payload is the full record after the change (e.g. the
 // new state of a project after an update).
diff --git a/pkg/events/user_dn_subscribe.go b/pkg/events/user_dn_subscribe.go
deleted file mode 100644
index 9190976e6..000000000
--- a/pkg/events/user_dn_subscribe.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package events
-
-import (
-       "log/slog"
-
-       "github.com/apache/airavata-custos/pkg/models"
-)
-
-// UserDNHandler handles DN-binding lifecycle events with a typed payload.
-type UserDNHandler func(dn models.UserDN)
-
-// SubscribeUserDNCreated registers a typed handler invoked whenever a
-// user_dn::create event is published.
-func (b *Bus) SubscribeUserDNCreated(handler UserDNHandler) {
-       b.subscribeUserDN(UserDNCreateEvent, handler)
-}
-
-// SubscribeUserDNDeleted registers a typed handler invoked whenever a
-// user_dn::delete event is published.
-func (b *Bus) SubscribeUserDNDeleted(handler UserDNHandler) {
-       b.subscribeUserDN(UserDNDeleteEvent, handler)
-}
-
-func (b *Bus) subscribeUserDN(topic EventType, handler UserDNHandler) {
-       b.Subscribe(topic, func(event Event, value interface{}) {
-               switch d := value.(type) {
-               case models.UserDN:
-                       handler(d)
-               case *models.UserDN:
-                       if d != nil {
-                               handler(*d)
-                       }
-               default:
-                       slog.Warn("user dn event payload has unexpected type",
-                               "type", event.Type,
-                               "got", value,
-                       )
-               }
-       })
-}
diff --git a/pkg/models/identity.go b/pkg/models/identity.go
index 83d24257b..149f0e855 100644
--- a/pkg/models/identity.go
+++ b/pkg/models/identity.go
@@ -33,13 +33,3 @@ type UserIdentity struct {
        Metadata   string    `json:"metadata,omitempty"  db:"metadata"`    // 
JSON-encoded source-specific fields
        CreatedAt  time.Time `json:"created_at"          db:"created_at"`
 }
-
-// UserDN binds an X.509 distinguished name (e.g. mTLS client cert subject) to
-// a User. Append-only: DNs are credentials and are added or removed, never
-// edited.
-type UserDN struct {
-       ID        string    `json:"id"         db:"id"`
-       UserID    string    `json:"user_id"    db:"user_id"`
-       DN        string    `json:"dn"         db:"dn"`
-       CreatedAt time.Time `json:"created_at" db:"created_at"`
-}
diff --git a/pkg/models/project.go b/pkg/models/project.go
index 38de362a0..f73ec63de 100644
--- a/pkg/models/project.go
+++ b/pkg/models/project.go
@@ -46,14 +46,3 @@ type User struct {
        Email          string     `json:"email"           db:"email"`
        Status         UserStatus `json:"status"          db:"status"`
 }
-
-// UserMerge is the audit record that links a retiring user to the surviving
-// user that absorbed its identity-forward state. Each retiring user can be
-// merged at most once; merges are not reversed in-place.
-type UserMerge struct {
-       ID              int64     `json:"id"                 db:"id"`
-       RetiringUserID  string    `json:"retiring_user_id"   
db:"retiring_user_id"`
-       SurvivingUserID string    `json:"surviving_user_id"  
db:"surviving_user_id"`
-       Reason          string    `json:"reason,omitempty"   db:"reason"`
-       MergedAt        time.Time `json:"merged_at"          db:"merged_at"`
-}
diff --git a/pkg/service/service.go b/pkg/service/service.go
index 6b0cdb555..0a1734132 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -52,8 +52,6 @@ type Service struct {
        membershipOverrides 
store.ComputeAllocationMembershipResourceOverrideStore
        usages              store.ComputeAllocationUsageStore
        userIdentities      store.UserIdentityStore
-       userDNs             store.UserDNStore
-       userMerges          store.UserMergeStore
 }
 
 // New constructs a Service backed by the supplied database handle.
@@ -78,8 +76,6 @@ func New(database *sqlx.DB, eventBus *events.Bus) *Service {
                membershipOverrides: 
store.NewComputeAllocationMembershipResourceOverrideStore(database),
                usages:              
store.NewComputeAllocationUsageStore(database),
                userIdentities:      store.NewUserIdentityStore(database),
-               userDNs:             store.NewUserDNStore(database),
-               userMerges:          store.NewUserMergeStore(database),
        }
 }
 
@@ -105,8 +101,6 @@ func NewWithStores(
        memberships store.ComputeAllocationMembershipStore,
        usages store.ComputeAllocationUsageStore,
        userIdentities store.UserIdentityStore,
-       userDNs store.UserDNStore,
-       userMerges store.UserMergeStore,
 ) *Service {
        return &Service{
                db:                  database,
@@ -127,8 +121,6 @@ func NewWithStores(
                memberships:         memberships,
                usages:              usages,
                userIdentities:      userIdentities,
-               userDNs:             userDNs,
-               userMerges:          userMerges,
        }
 }
 
diff --git a/pkg/service/user_dn.go b/pkg/service/user_dn.go
deleted file mode 100644
index 611b93f9d..000000000
--- a/pkg/service/user_dn.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package service
-
-import (
-       "context"
-       "database/sql"
-       "fmt"
-
-       "github.com/apache/airavata-custos/pkg/events"
-       "github.com/apache/airavata-custos/pkg/models"
-)
-
-// AddUserDN binds a DN to a user. If d.ID is empty, a new UUID is generated.
-// The referenced user must already exist; (user_id, dn) is unique.
-func (s *Service) AddUserDN(ctx context.Context, d *models.UserDN) 
(*models.UserDN, error) {
-       if d == nil {
-               return nil, fmt.Errorf("%w: user dn is nil", ErrInvalidInput)
-       }
-       if d.UserID == "" {
-               return nil, fmt.Errorf("%w: user dn user_id is required", 
ErrInvalidInput)
-       }
-       if d.DN == "" {
-               return nil, fmt.Errorf("%w: user dn dn is required", 
ErrInvalidInput)
-       }
-
-       if user, err := s.users.FindByID(ctx, d.UserID); err != nil {
-               return nil, fmt.Errorf("verify user: %w", err)
-       } else if user == nil {
-               return nil, fmt.Errorf("%w: user %q does not exist", 
ErrInvalidInput, d.UserID)
-       }
-
-       if existing, err := s.userDNs.FindByDN(ctx, d.DN); err != nil {
-               return nil, fmt.Errorf("lookup user dn: %w", err)
-       } else if existing != nil {
-               return nil, fmt.Errorf("%w: dn %q", ErrAlreadyExists, d.DN)
-       }
-
-       if d.ID == "" {
-               d.ID = newID()
-       }
-
-       if err := s.inTx(ctx, func(tx *sql.Tx) error {
-               return s.userDNs.Create(ctx, tx, d)
-       }); err != nil {
-               return nil, fmt.Errorf("add user dn: %w", err)
-       }
-
-       s.eventBus.Publish(events.UserDNCreateEvent, d)
-       return d, nil
-}
-
-// GetUserDN retrieves a DN binding by ID. Returns ErrNotFound when no row 
matches.
-func (s *Service) GetUserDN(ctx context.Context, id string) (*models.UserDN, 
error) {
-       d, err := s.userDNs.FindByID(ctx, id)
-       if err != nil {
-               return nil, fmt.Errorf("get user dn: %w", err)
-       }
-       if d == nil {
-               return nil, ErrNotFound
-       }
-       return d, nil
-}
-
-// GetUserDNByDN performs a reverse lookup from DN to binding.
-func (s *Service) GetUserDNByDN(ctx context.Context, dn string) 
(*models.UserDN, error) {
-       if dn == "" {
-               return nil, fmt.Errorf("%w: dn is required", ErrInvalidInput)
-       }
-       d, err := s.userDNs.FindByDN(ctx, dn)
-       if err != nil {
-               return nil, fmt.Errorf("get user dn by dn: %w", err)
-       }
-       if d == nil {
-               return nil, ErrNotFound
-       }
-       return d, nil
-}
-
-// ListUserDNs returns every DN bound to the given user.
-func (s *Service) ListUserDNs(ctx context.Context, userID string) 
([]models.UserDN, error) {
-       if userID == "" {
-               return nil, fmt.Errorf("%w: user_id is required", 
ErrInvalidInput)
-       }
-       out, err := s.userDNs.FindByUser(ctx, userID)
-       if err != nil {
-               return nil, fmt.Errorf("list user dns: %w", err)
-       }
-       return out, nil
-}
-
-// RemoveUserDN removes a DN binding by ID.
-func (s *Service) RemoveUserDN(ctx context.Context, id string) error {
-       if id == "" {
-               return fmt.Errorf("%w: user dn id is required", ErrInvalidInput)
-       }
-       d, err := s.userDNs.FindByID(ctx, id)
-       if err != nil {
-               return fmt.Errorf("lookup user dn: %w", err)
-       }
-       if d == nil {
-               return ErrNotFound
-       }
-       if err := s.inTx(ctx, func(tx *sql.Tx) error {
-               return s.userDNs.Delete(ctx, tx, id)
-       }); err != nil {
-               return fmt.Errorf("remove user dn: %w", err)
-       }
-
-       s.eventBus.Publish(events.UserDNDeleteEvent, d)
-       return nil
-}
diff --git a/pkg/service/user_merge.go b/pkg/service/user_merge.go
index 3d8860e02..8a07b2ee1 100644
--- a/pkg/service/user_merge.go
+++ b/pkg/service/user_merge.go
@@ -28,13 +28,11 @@ import (
 
 // MergeUsers consolidates the retiring user into the surviving user. All
 // identity-forward state moves to the survivor; historical truth stays in
-// place. The retiring user is flipped to status=MERGED and a row is written
-// to user_merges with the surviving user and the given reason. All work
-// happens in a single transaction.
+// place. The retiring user is flipped to status=MERGED. All work happens in a
+// single transaction.
 //
 // Moved to survivor (duplicates on the retiring user are dropped first):
 //   - user_identities
-//   - user_dns
 //   - compute_cluster_users
 //   - projects.project_pi_id
 //   - compute_allocation_memberships
@@ -42,7 +40,11 @@ import (
 // Left in place (who actually did the thing):
 //   - compute_allocation_change_requests (requester / approver)
 //   - compute_allocation_usages
-func (s *Service) MergeUsers(ctx context.Context, survivingID, retiringID, 
reason string) (*models.User, error) {
+//
+// Not idempotent — a retiring user already in status=MERGED is rejected with
+// ErrAlreadyExists. Callers that need replay safety should fetch the retiring
+// user by ID and skip the call when its status is already MERGED.
+func (s *Service) MergeUsers(ctx context.Context, survivingID, retiringID 
string) (*models.User, error) {
        if survivingID == "" || retiringID == "" {
                return nil, fmt.Errorf("%w: surviving and retiring user IDs are 
required", ErrInvalidInput)
        }
@@ -67,30 +69,14 @@ func (s *Service) MergeUsers(ctx context.Context, 
survivingID, retiringID, reaso
        if retiring == nil {
                return nil, fmt.Errorf("%w: retiring user %q does not exist", 
ErrInvalidInput, retiringID)
        }
-
-       // Idempotency: re-running the same merge is a no-op; merging the same
-       // retiring user into a different survivor is rejected.
        if retiring.Status == models.UserMerged {
-               prior, err := s.userMerges.FindByRetiringUser(ctx, retiringID)
-               if err != nil {
-                       return nil, fmt.Errorf("lookup prior merge: %w", err)
-               }
-               if prior != nil {
-                       if prior.SurvivingUserID == survivingID {
-                               return survivor, nil
-                       }
-                       return nil, fmt.Errorf("%w: user %q already merged into 
%q",
-                               ErrAlreadyExists, retiringID, 
prior.SurvivingUserID)
-               }
+               return nil, fmt.Errorf("%w: retiring user %q is already 
merged", ErrAlreadyExists, retiringID)
        }
 
        if err := s.inTx(ctx, func(tx *sql.Tx) error {
                if err := s.userIdentities.ReassignUser(ctx, tx, retiringID, 
survivingID); err != nil {
                        return fmt.Errorf("reassign user identities: %w", err)
                }
-               if err := s.userDNs.ReassignUser(ctx, tx, retiringID, 
survivingID); err != nil {
-                       return fmt.Errorf("reassign user dns: %w", err)
-               }
                if err := s.clusterUsers.ReassignUser(ctx, tx, retiringID, 
survivingID); err != nil {
                        return fmt.Errorf("reassign compute cluster users: %w", 
err)
                }
@@ -100,10 +86,7 @@ func (s *Service) MergeUsers(ctx context.Context, 
survivingID, retiringID, reaso
                if err := s.memberships.ReassignUser(ctx, tx, retiringID, 
survivingID); err != nil {
                        return fmt.Errorf("reassign memberships: %w", err)
                }
-               if err := s.users.UpdateStatus(ctx, tx, retiringID, 
models.UserMerged); err != nil {
-                       return fmt.Errorf("mark retiring user merged: %w", err)
-               }
-               return s.userMerges.Record(ctx, tx, retiringID, survivingID, 
reason)
+               return s.users.UpdateStatus(ctx, tx, retiringID, 
models.UserMerged)
        }); err != nil {
                return nil, fmt.Errorf("merge users: %w", err)
        }
@@ -113,32 +96,3 @@ func (s *Service) MergeUsers(ctx context.Context, 
survivingID, retiringID, reaso
        s.eventBus.Publish(events.UserUpdateEvent, survivor)
        return survivor, nil
 }
-
-// GetUserMergeByRetiringUser returns the merge record for a retiring user, or
-// ErrNotFound if the user has not been merged.
-func (s *Service) GetUserMergeByRetiringUser(ctx context.Context, 
retiringUserID string) (*models.UserMerge, error) {
-       if retiringUserID == "" {
-               return nil, fmt.Errorf("%w: retiring_user_id is required", 
ErrInvalidInput)
-       }
-       m, err := s.userMerges.FindByRetiringUser(ctx, retiringUserID)
-       if err != nil {
-               return nil, fmt.Errorf("get user merge: %w", err)
-       }
-       if m == nil {
-               return nil, ErrNotFound
-       }
-       return m, nil
-}
-
-// ListUserMergesBySurvivingUser returns every merge record absorbed by the
-// given surviving user, oldest first.
-func (s *Service) ListUserMergesBySurvivingUser(ctx context.Context, 
survivingUserID string) ([]models.UserMerge, error) {
-       if survivingUserID == "" {
-               return nil, fmt.Errorf("%w: surviving_user_id is required", 
ErrInvalidInput)
-       }
-       out, err := s.userMerges.FindBySurvivingUser(ctx, survivingUserID)
-       if err != nil {
-               return nil, fmt.Errorf("list user merges by surviving user: 
%w", err)
-       }
-       return out, nil
-}

Reply via email to