Skip to content
Open
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
18 changes: 18 additions & 0 deletions internal/db/migrations/000006_researcher_profiles.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- 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 researcher_profiles;
29 changes: 29 additions & 0 deletions internal/db/migrations/000006_researcher_profiles.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- 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 researcher_profiles
(
user_id VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL,
research_domain VARCHAR(255) NULL,
department VARCHAR(255) NULL,
institution VARCHAR(255) NULL,
created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (user_id),
CONSTRAINT fk_researcher_profiles_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
74 changes: 74 additions & 0 deletions internal/store/researcher_profile_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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 mysqlResearcherProfileStore struct {
db *sqlx.DB
}

// NewResearcherProfileStore returns a MySQL-backed ResearcherProfileStore.
func NewResearcherProfileStore(db *sqlx.DB) ResearcherProfileStore {
return &mysqlResearcherProfileStore{db: db}
}

const researcherProfileColumns = `user_id, display_name, COALESCE(research_domain, '') AS research_domain, COALESCE(department, '') AS department, COALESCE(institution, '') AS institution, created_at, updated_at`

func (s *mysqlResearcherProfileStore) FindByUserID(ctx context.Context, userID string) (*models.ResearcherProfile, error) {
var p models.ResearcherProfile
err := s.db.GetContext(ctx, &p,
`SELECT `+researcherProfileColumns+` FROM researcher_profiles WHERE user_id = ?`, userID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &p, nil
}

func (s *mysqlResearcherProfileStore) Create(ctx context.Context, tx *sql.Tx, p *models.ResearcherProfile) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO researcher_profiles (user_id, display_name, research_domain, department, institution)
VALUES (?, ?, ?, ?, ?)`,
p.UserID, p.DisplayName, nullableString(p.ResearchDomain), nullableString(p.Department), nullableString(p.Institution))
return err
}

func (s *mysqlResearcherProfileStore) Update(ctx context.Context, tx *sql.Tx, p *models.ResearcherProfile) error {
_, err := tx.ExecContext(ctx,
`UPDATE researcher_profiles
SET display_name = ?, research_domain = ?, department = ?, institution = ?
WHERE user_id = ?`,
p.DisplayName, nullableString(p.ResearchDomain), nullableString(p.Department), nullableString(p.Institution), p.UserID)
return err
}

func (s *mysqlResearcherProfileStore) Delete(ctx context.Context, tx *sql.Tx, userID string) error {
_, err := tx.ExecContext(ctx, `DELETE FROM researcher_profiles WHERE user_id = ?`, userID)
return err
}
13 changes: 13 additions & 0 deletions internal/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ type UserStore interface {
Delete(ctx context.Context, tx *sql.Tx, id string) error
}

// ResearcherProfileStore defines persistence operations for researcher profile
// metadata attached to a Custos user.
type ResearcherProfileStore interface {
// FindByUserID returns the researcher profile for the given user ID, or nil if not found.
FindByUserID(ctx context.Context, userID string) (*models.ResearcherProfile, error)
// Create inserts a researcher profile within the provided transaction.
Create(ctx context.Context, tx *sql.Tx, p *models.ResearcherProfile) error
// Update replaces mutable fields of an existing researcher profile within the provided transaction.
Update(ctx context.Context, tx *sql.Tx, p *models.ResearcherProfile) error
// Delete removes a researcher profile by user ID within the provided transaction.
Delete(ctx context.Context, tx *sql.Tx, userID string) error
}

// OrganizationStore defines persistence operations for organizations.
type OrganizationStore interface {
// FindByID returns the organization with the given ID, or nil if not found.
Expand Down
33 changes: 33 additions & 0 deletions pkg/models/profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 models

import "time"

// ResearcherProfile stores researcher-specific metadata for a Custos user.
// The stable identity remains models.User; external identities remain
// models.UserIdentity.
type ResearcherProfile struct {
UserID string `json:"user_id" db:"user_id"`
DisplayName string `json:"display_name" db:"display_name"`
ResearchDomain string `json:"research_domain,omitempty" db:"research_domain"`
Department string `json:"department,omitempty" db:"department"`
Institution string `json:"institution,omitempty" db:"institution"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}