Skip to content
Draft
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
49 changes: 49 additions & 0 deletions docs/db_repository_update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# DB Repository Update Instructions

To fully implement the SSH key change history feature, the database schema needs to be updated in the [ssh-sync-db](https://github.com/therealpaulgg/ssh-sync-db) repository.

## Changes Required

Add the following SQL to the `init.sql` file in the ssh-sync-db repository:

```sql
-- Create enum type for change types
CREATE TYPE change_type AS ENUM ('created', 'updated', 'deleted');

-- Create table for tracking SSH key changes
CREATE TABLE IF NOT EXISTS ssh_key_changes (
id UUID DEFAULT uuid_generate_v4() NOT NULL,
ssh_key_id UUID NOT NULL,
user_id UUID NOT NULL,
change_type change_type NOT NULL,
filename VARCHAR(255) NOT NULL,
previous_data BYTEA,
new_data BYTEA,
change_time TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Add indexes for efficient lookups
CREATE INDEX IF NOT EXISTS idx_ssh_key_changes_ssh_key_id ON ssh_key_changes(ssh_key_id);
CREATE INDEX IF NOT EXISTS idx_ssh_key_changes_user_id ON ssh_key_changes(user_id);
CREATE INDEX IF NOT EXISTS idx_ssh_key_changes_change_time ON ssh_key_changes(change_time);

-- Add index for the most common query pattern: finding the latest changes per key for a user
CREATE INDEX IF NOT EXISTS idx_ssh_key_changes_user_time ON ssh_key_changes(user_id, change_time DESC);
```

## Implementation Notes

1. The schema matches what's defined in the `docs/sql/ssh_key_changes.sql` file in this repository
2. The schema uses the existing `uuid_generate_v4()` function that's already being used in the database
3. The table includes a foreign key reference to the users table with cascade deletion to ensure data integrity
4. Indexes have been added to optimize the most common query patterns

## Testing

After updating the init.sql file in the ssh-sync-db repository, you can test that the schema works correctly by:

1. Running `docker-compose up --build` in the ssh-sync-db repository
2. Connecting to the database with `psql -h localhost -p 5432 -U sshsync -d sshsync`
3. Verifying the table exists with `\d ssh_key_changes`
25 changes: 25 additions & 0 deletions docs/sql/ssh_key_changes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- Schema for SSH Key Change tracking

-- Create enum type for change types
CREATE TYPE change_type AS ENUM ('created', 'updated', 'deleted');

-- Create table for tracking SSH key changes
CREATE TABLE IF NOT EXISTS ssh_key_changes (
id UUID PRIMARY KEY,
ssh_key_id UUID NOT NULL,
user_id UUID NOT NULL,
change_type change_type NOT NULL,
filename VARCHAR(255) NOT NULL,
previous_data BYTEA,
new_data BYTEA,
change_time TIMESTAMP WITH TIME ZONE NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Add indexes for efficient lookups
CREATE INDEX IF NOT EXISTS idx_ssh_key_changes_ssh_key_id ON ssh_key_changes(ssh_key_id);
CREATE INDEX IF NOT EXISTS idx_ssh_key_changes_user_id ON ssh_key_changes(user_id);
CREATE INDEX IF NOT EXISTS idx_ssh_key_changes_change_time ON ssh_key_changes(change_time);

-- Add index for the most common query pattern: finding the latest changes per key for a user
CREATE INDEX IF NOT EXISTS idx_ssh_key_changes_user_time ON ssh_key_changes(user_id, change_time DESC);
36 changes: 36 additions & 0 deletions docs/ssh_key_change_history.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# SSH Key Change History

This feature adds support for tracking changes to SSH keys in the database, allowing for better conflict resolution during syncing.

## Database Schema

The feature introduces a new table `ssh_key_changes` that tracks all changes (creation, updates, and deletions) to SSH keys.
See the SQL schema in `docs/sql/ssh_key_changes.sql`.

**Note**: This schema needs to be added to the init.sql file in the separate [ssh-sync-db](https://github.com/therealpaulgg/ssh-sync-db) repository. See `docs/db_repository_update.md` for detailed instructions.

## API Usage

### Recording Changes

Changes are automatically recorded when using the new repository methods:

- `SshKeyRepo.CreateSshKeyWithChange`: Create a new SSH key and record it as a creation event
- `SshKeyRepo.UpsertSshKeyWithChange`: Create or update an SSH key and record the appropriate event
- `SshKeyRepo.UpsertSshKeyWithChangeTx`: Same as above but within a transaction
- `UserRepo.DeleteUserKeyTx`: Now records a deletion event before deleting the key

### Retrieving Change History

Use the `SshKeyChangeRepository` to access change history:

- `GetKeyChanges`: Retrieve the full change history for a specific SSH key
- `GetLatestKeyChangesForUser`: Get the most recent change for each of a user's SSH keys since a specified time

## Conflict Resolution

This change history enables better conflict resolution during syncing:

1. When a key is deleted on the server, clients can detect this change and delete it locally
2. When both server and client have made changes to the same key, the timestamps can be used to determine which change is more recent
3. In case of conflicts, the application can choose to keep the most recent change or prompt the user for resolution
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module github.com/therealpaulgg/ssh-sync-server

go 1.23
go 1.23.0

toolchain go1.24.1

require (
Expand Down
12 changes: 11 additions & 1 deletion internal/setup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ func SetupServices(i *do.Injector) {
dataAccessor := do.MustInvoke[database.DataAccessor](i)
return &query.QueryServiceTxImpl[models.SshConfig]{DataAccessor: dataAccessor}, nil
})
do.Provide(i, func(i *do.Injector) (query.QueryService[models.SshKeyChange], error) {
dataAccessor := do.MustInvoke[database.DataAccessor](i)
return &query.QueryServiceImpl[models.SshKeyChange]{DataAccessor: dataAccessor}, nil
})
do.Provide(i, func(i *do.Injector) (query.QueryServiceTx[models.SshKeyChange], error) {
dataAccessor := do.MustInvoke[database.DataAccessor](i)
return &query.QueryServiceTxImpl[models.SshKeyChange]{DataAccessor: dataAccessor}, nil
})
do.Provide(i, func(i *do.Injector) (repository.UserRepository, error) {
return &repository.UserRepo{Injector: i}, nil
})
Expand All @@ -58,5 +66,7 @@ func SetupServices(i *do.Injector) {
do.Provide(i, func(i *do.Injector) (repository.SshConfigRepository, error) {
return &repository.SshConfigRepo{Injector: i}, nil
})

do.Provide(i, func(i *do.Injector) (repository.SshKeyChangeRepository, error) {
return &repository.SshKeyChangeRepo{Injector: i}, nil
})
}
31 changes: 31 additions & 0 deletions pkg/database/models/ssh_key_change.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package models

import (
"time"

"github.com/google/uuid"
)

// ChangeType represents the type of change made to an SSH key
type ChangeType string

const (
// Created indicates a new SSH key was created
Created ChangeType = "created"
// Updated indicates an existing SSH key was updated
Updated ChangeType = "updated"
// Deleted indicates an SSH key was deleted
Deleted ChangeType = "deleted"
)

// SshKeyChange represents a change to an SSH key in the database
type SshKeyChange struct {
ID uuid.UUID `json:"id" db:"id"`
SshKeyID uuid.UUID `json:"ssh_key_id" db:"ssh_key_id"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
ChangeType ChangeType `json:"change_type" db:"change_type"`
Filename string `json:"filename" db:"filename"`
PreviousData []byte `json:"previous_data,omitempty" db:"previous_data"`
NewData []byte `json:"new_data,omitempty" db:"new_data"`
ChangeTime time.Time `json:"change_time" db:"change_time"`
}
36 changes: 36 additions & 0 deletions pkg/database/query/transaction_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package query

import (
"github.com/jackc/pgx/v5"
)

// TransactionMock implements the TransactionService interface for testing
type TransactionMock struct {
StartTxFunc func(pgx.TxOptions) (pgx.Tx, error)
CommitFunc func(pgx.Tx) error
RollbackFunc func(pgx.Tx) error
}

// StartTx implements TransactionService
func (m *TransactionMock) StartTx(opts pgx.TxOptions) (pgx.Tx, error) {
if m.StartTxFunc != nil {
return m.StartTxFunc(opts)
}
return nil, nil
}

// Commit implements TransactionService
func (m *TransactionMock) Commit(tx pgx.Tx) error {
if m.CommitFunc != nil {
return m.CommitFunc(tx)
}
return nil
}

// Rollback implements TransactionService
func (m *TransactionMock) Rollback(tx pgx.Tx) error {
if m.RollbackFunc != nil {
return m.RollbackFunc(tx)
}
return nil
}
3 changes: 0 additions & 3 deletions pkg/database/repository/machine_test.go

This file was deleted.

3 changes: 0 additions & 3 deletions pkg/database/repository/ssh_config_test.go

This file was deleted.

Loading