Release: v0.1.0-alpha
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s
This commit is contained in:
@@ -0,0 +1,776 @@
|
||||
// Keywarden - Centralized SSH Key Management and Deployment
|
||||
// Copyright (C) 2026 Patrick Asmus (scriptos)
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
package servers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.techniverse.net/scriptos/keywarden/internal/database"
|
||||
"git.techniverse.net/scriptos/keywarden/internal/models"
|
||||
)
|
||||
|
||||
// Service handles server management
|
||||
type Service struct {
|
||||
db *database.DB
|
||||
}
|
||||
|
||||
// NewService creates a new server service
|
||||
func NewService(db *database.DB) *Service {
|
||||
return &Service{db: db}
|
||||
}
|
||||
|
||||
// Create adds a new server
|
||||
func (s *Service) Create(userID int64, name, hostname string, port int, username, description string) (*models.Server, error) {
|
||||
if port == 0 {
|
||||
port = 22
|
||||
}
|
||||
|
||||
result, err := s.db.Exec(
|
||||
`INSERT INTO servers (user_id, name, hostname, port, username, description) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
userID, name, hostname, port, username, description,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create server: %w", err)
|
||||
}
|
||||
|
||||
id, _ := result.LastInsertId()
|
||||
return &models.Server{
|
||||
ID: id,
|
||||
UserID: userID,
|
||||
Name: name,
|
||||
Hostname: hostname,
|
||||
Port: port,
|
||||
Username: username,
|
||||
Description: description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetByUser returns all servers for a user
|
||||
func (s *Service) GetByUser(userID int64) ([]models.Server, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT id, user_id, name, hostname, port, username, description, created_at, updated_at
|
||||
FROM servers WHERE user_id = ? ORDER BY name ASC`, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query servers: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var servers []models.Server
|
||||
for rows.Next() {
|
||||
var srv models.Server
|
||||
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan server: %w", err)
|
||||
}
|
||||
servers = append(servers, srv)
|
||||
}
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// GetByID returns a specific server
|
||||
func (s *Service) GetByID(serverID, userID int64) (*models.Server, error) {
|
||||
srv := &models.Server{}
|
||||
err := s.db.QueryRow(
|
||||
`SELECT id, user_id, name, hostname, port, username, description, created_at, updated_at
|
||||
FROM servers WHERE id = ? AND user_id = ?`, serverID, userID,
|
||||
).Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server not found: %w", err)
|
||||
}
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Update modifies a server
|
||||
func (s *Service) Update(serverID, userID int64, name, hostname string, port int, username, description string) error {
|
||||
result, err := s.db.Exec(
|
||||
`UPDATE servers SET name=?, hostname=?, port=?, username=?, description=?, updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=? AND user_id=?`,
|
||||
name, hostname, port, username, description, serverID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update server: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("server not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a server
|
||||
func (s *Service) Delete(serverID, userID int64) error {
|
||||
result, err := s.db.Exec(`DELETE FROM servers WHERE id = ? AND user_id = ?`, serverID, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete server: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("server not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Server Groups ---
|
||||
|
||||
// CreateGroup creates a new server group
|
||||
func (s *Service) CreateGroup(userID int64, name, description string) (*models.ServerGroup, error) {
|
||||
result, err := s.db.Exec(
|
||||
`INSERT INTO server_groups (user_id, name, description) VALUES (?, ?, ?)`,
|
||||
userID, name, description,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create server group: %w", err)
|
||||
}
|
||||
id, _ := result.LastInsertId()
|
||||
return &models.ServerGroup{
|
||||
ID: id,
|
||||
UserID: userID,
|
||||
Name: name,
|
||||
Description: description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetGroupsByUser returns all server groups for a user with server counts
|
||||
func (s *Service) GetGroupsByUser(userID int64) ([]models.ServerGroupWithCount, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT sg.id, sg.user_id, sg.name, sg.description, sg.created_at, sg.updated_at,
|
||||
COUNT(sgm.server_id) as server_count
|
||||
FROM server_groups sg
|
||||
LEFT JOIN server_group_members sgm ON sg.id = sgm.group_id
|
||||
WHERE sg.user_id = ?
|
||||
GROUP BY sg.id
|
||||
ORDER BY sg.name ASC`, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query server groups: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var groups []models.ServerGroupWithCount
|
||||
for rows.Next() {
|
||||
var g models.ServerGroupWithCount
|
||||
if err := rows.Scan(&g.ID, &g.UserID, &g.Name, &g.Description, &g.CreatedAt, &g.UpdatedAt, &g.ServerCount); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan server group: %w", err)
|
||||
}
|
||||
groups = append(groups, g)
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// GetGroupByID returns a specific server group
|
||||
func (s *Service) GetGroupByID(groupID, userID int64) (*models.ServerGroup, error) {
|
||||
g := &models.ServerGroup{}
|
||||
err := s.db.QueryRow(
|
||||
`SELECT id, user_id, name, description, created_at, updated_at
|
||||
FROM server_groups WHERE id = ? AND user_id = ?`, groupID, userID,
|
||||
).Scan(&g.ID, &g.UserID, &g.Name, &g.Description, &g.CreatedAt, &g.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server group not found: %w", err)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// UpdateGroup modifies a server group
|
||||
func (s *Service) UpdateGroup(groupID, userID int64, name, description string) error {
|
||||
result, err := s.db.Exec(
|
||||
`UPDATE server_groups SET name=?, description=?, updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=? AND user_id=?`,
|
||||
name, description, groupID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update server group: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("server group not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGroup removes a server group
|
||||
func (s *Service) DeleteGroup(groupID, userID int64) error {
|
||||
result, err := s.db.Exec(`DELETE FROM server_groups WHERE id = ? AND user_id = ?`, groupID, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete server group: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("server group not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddServerToGroup adds a server to a group
|
||||
func (s *Service) AddServerToGroup(groupID, serverID, userID int64) error {
|
||||
// Verify group belongs to user
|
||||
_, err := s.GetGroupByID(groupID, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("group not found: %w", err)
|
||||
}
|
||||
// Verify server belongs to user
|
||||
_, err = s.GetByID(serverID, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server not found: %w", err)
|
||||
}
|
||||
_, err = s.db.Exec(
|
||||
`INSERT OR IGNORE INTO server_group_members (group_id, server_id) VALUES (?, ?)`,
|
||||
groupID, serverID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add server to group: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveServerFromGroup removes a server from a group
|
||||
func (s *Service) RemoveServerFromGroup(groupID, serverID, userID int64) error {
|
||||
// Verify group belongs to user
|
||||
_, err := s.GetGroupByID(groupID, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("group not found: %w", err)
|
||||
}
|
||||
_, err = s.db.Exec(
|
||||
`DELETE FROM server_group_members WHERE group_id = ? AND server_id = ?`,
|
||||
groupID, serverID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove server from group: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddServerToGroupGlobal adds a server to a group without user_id check
|
||||
func (s *Service) AddServerToGroupGlobal(groupID, serverID int64) error {
|
||||
_, err := s.db.Exec(
|
||||
`INSERT OR IGNORE INTO server_group_members (group_id, server_id) VALUES (?, ?)`,
|
||||
groupID, serverID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add server to group: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveServerFromGroupGlobal removes a server from a group without user_id check
|
||||
func (s *Service) RemoveServerFromGroupGlobal(groupID, serverID int64) error {
|
||||
_, err := s.db.Exec(
|
||||
`DELETE FROM server_group_members WHERE group_id = ? AND server_id = ?`,
|
||||
groupID, serverID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove server from group: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGroupMembers returns all servers in a group
|
||||
func (s *Service) GetGroupMembers(groupID, userID int64) ([]models.Server, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT s.id, s.user_id, s.name, s.hostname, s.port, s.username, s.description, s.created_at, s.updated_at
|
||||
FROM servers s
|
||||
JOIN server_group_members sgm ON s.id = sgm.server_id
|
||||
WHERE sgm.group_id = ? AND s.user_id = ?
|
||||
ORDER BY s.name ASC`, groupID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query group members: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var servers []models.Server
|
||||
for rows.Next() {
|
||||
var srv models.Server
|
||||
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan server: %w", err)
|
||||
}
|
||||
servers = append(servers, srv)
|
||||
}
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// GetGroupIDsForServer returns all group IDs that a server belongs to
|
||||
func (s *Service) GetGroupIDsForServer(serverID, userID int64) ([]int64, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT sgm.group_id FROM server_group_members sgm
|
||||
JOIN server_groups sg ON sg.id = sgm.group_id
|
||||
WHERE sgm.server_id = ? AND sg.user_id = ?`, serverID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query server groups: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var ids []int64
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// SetServerGroups replaces all group memberships for a server
|
||||
func (s *Service) SetServerGroups(serverID, userID int64, groupIDs []int64) error {
|
||||
// Verify server belongs to user
|
||||
_, err := s.GetByID(serverID, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server not found: %w", err)
|
||||
}
|
||||
|
||||
// Remove all current group memberships for this server
|
||||
_, err = s.db.Exec(`DELETE FROM server_group_members WHERE server_id = ?`, serverID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clear group memberships: %w", err)
|
||||
}
|
||||
|
||||
// Add new memberships
|
||||
for _, gid := range groupIDs {
|
||||
// Verify group belongs to user
|
||||
_, err := s.GetGroupByID(gid, userID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
_, err = s.db.Exec(
|
||||
`INSERT OR IGNORE INTO server_group_members (group_id, server_id) VALUES (?, ?)`,
|
||||
gid, serverID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add server to group: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGroupMemberIDs returns server IDs in a group
|
||||
func (s *Service) GetGroupMemberIDs(groupID, userID int64) ([]int64, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT sgm.server_id FROM server_group_members sgm
|
||||
JOIN servers s ON s.id = sgm.server_id
|
||||
WHERE sgm.group_id = ? AND s.user_id = ?`, groupID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query group member IDs: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var ids []int64
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// --- Global queries (admin/owner) ---
|
||||
|
||||
// GetAllServers returns all servers regardless of owner
|
||||
func (s *Service) GetAllServers() ([]models.Server, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT id, user_id, name, hostname, port, username, description, created_at, updated_at
|
||||
FROM servers ORDER BY name ASC`,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query all servers: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var servers []models.Server
|
||||
for rows.Next() {
|
||||
var srv models.Server
|
||||
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan server: %w", err)
|
||||
}
|
||||
servers = append(servers, srv)
|
||||
}
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// GetAllGroups returns all server groups regardless of owner
|
||||
func (s *Service) GetAllGroups() ([]models.ServerGroupWithCount, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT sg.id, sg.user_id, sg.name, sg.description, sg.created_at, sg.updated_at,
|
||||
COUNT(sgm.server_id) as server_count
|
||||
FROM server_groups sg
|
||||
LEFT JOIN server_group_members sgm ON sg.id = sgm.group_id
|
||||
GROUP BY sg.id
|
||||
ORDER BY sg.name ASC`,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query all server groups: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var groups []models.ServerGroupWithCount
|
||||
for rows.Next() {
|
||||
var g models.ServerGroupWithCount
|
||||
if err := rows.Scan(&g.ID, &g.UserID, &g.Name, &g.Description, &g.CreatedAt, &g.UpdatedAt, &g.ServerCount); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan server group: %w", err)
|
||||
}
|
||||
groups = append(groups, g)
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// --- Global access functions (admin/owner) ---
|
||||
|
||||
// GetByIDGlobal returns a server without user_id check (admin/owner access)
|
||||
func (s *Service) GetByIDGlobal(serverID int64) (*models.Server, error) {
|
||||
srv := &models.Server{}
|
||||
err := s.db.QueryRow(
|
||||
`SELECT id, user_id, name, hostname, port, username, description, created_at, updated_at
|
||||
FROM servers WHERE id = ?`, serverID,
|
||||
).Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server not found: %w", err)
|
||||
}
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// UpdateGlobal modifies a server without user_id check (admin/owner access)
|
||||
func (s *Service) UpdateGlobal(serverID int64, name, hostname string, port int, username, description string) error {
|
||||
result, err := s.db.Exec(
|
||||
`UPDATE servers SET name=?, hostname=?, port=?, username=?, description=?, updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=?`,
|
||||
name, hostname, port, username, description, serverID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update server: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("server not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGlobal removes a server without user_id check (admin/owner access)
|
||||
func (s *Service) DeleteGlobal(serverID int64) error {
|
||||
result, err := s.db.Exec(`DELETE FROM servers WHERE id = ?`, serverID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete server: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("server not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGroupByIDGlobal returns a server group without user_id check
|
||||
func (s *Service) GetGroupByIDGlobal(groupID int64) (*models.ServerGroup, error) {
|
||||
g := &models.ServerGroup{}
|
||||
err := s.db.QueryRow(
|
||||
`SELECT id, user_id, name, description, created_at, updated_at
|
||||
FROM server_groups WHERE id = ?`, groupID,
|
||||
).Scan(&g.ID, &g.UserID, &g.Name, &g.Description, &g.CreatedAt, &g.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("server group not found: %w", err)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// UpdateGroupGlobal modifies a server group without user_id check
|
||||
func (s *Service) UpdateGroupGlobal(groupID int64, name, description string) error {
|
||||
result, err := s.db.Exec(
|
||||
`UPDATE server_groups SET name=?, description=?, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
|
||||
name, description, groupID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update server group: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("server group not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGroupGlobal removes a server group without user_id check
|
||||
func (s *Service) DeleteGroupGlobal(groupID int64) error {
|
||||
result, err := s.db.Exec(`DELETE FROM server_groups WHERE id = ?`, groupID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete server group: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("server group not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGroupMembersGlobal returns all servers in a group without user_id check
|
||||
func (s *Service) GetGroupMembersGlobal(groupID int64) ([]models.Server, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT s.id, s.user_id, s.name, s.hostname, s.port, s.username, s.description, s.created_at, s.updated_at
|
||||
FROM servers s
|
||||
JOIN server_group_members sgm ON s.id = sgm.server_id
|
||||
WHERE sgm.group_id = ?
|
||||
ORDER BY s.name ASC`, groupID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query group members: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var servers []models.Server
|
||||
for rows.Next() {
|
||||
var srv models.Server
|
||||
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan server: %w", err)
|
||||
}
|
||||
servers = append(servers, srv)
|
||||
}
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// GetGroupIDsForServerGlobal returns all group IDs that a server belongs to (without user_id check)
|
||||
func (s *Service) GetGroupIDsForServerGlobal(serverID int64) ([]int64, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT sgm.group_id FROM server_group_members sgm WHERE sgm.server_id = ?`, serverID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query server groups: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var ids []int64
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// SetServerGroupsGlobal replaces all group memberships for a server (without user_id check)
|
||||
func (s *Service) SetServerGroupsGlobal(serverID int64, groupIDs []int64) error {
|
||||
_, err := s.db.Exec(`DELETE FROM server_group_members WHERE server_id = ?`, serverID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clear group memberships: %w", err)
|
||||
}
|
||||
|
||||
for _, gid := range groupIDs {
|
||||
_, err = s.db.Exec(
|
||||
`INSERT OR IGNORE INTO server_group_members (group_id, server_id) VALUES (?, ?)`,
|
||||
gid, serverID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add server to group: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServersByAssignedUser returns hosts that a user has access to via assignments
|
||||
func (s *Service) GetServersByAssignedUser(userID int64) ([]models.Server, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT DISTINCT s.id, s.user_id, s.name, s.hostname, s.port, s.username, s.description, s.created_at, s.updated_at
|
||||
FROM servers s
|
||||
WHERE s.id IN (
|
||||
SELECT a.server_id FROM access_assignments a WHERE a.user_id = ? AND a.server_id > 0
|
||||
UNION
|
||||
SELECT sgm.server_id FROM access_assignments a
|
||||
JOIN server_group_members sgm ON a.group_id = sgm.group_id
|
||||
WHERE a.user_id = ? AND a.group_id > 0
|
||||
)
|
||||
ORDER BY s.name ASC`, userID, userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query assigned servers: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var servers []models.Server
|
||||
for rows.Next() {
|
||||
var srv models.Server
|
||||
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan server: %w", err)
|
||||
}
|
||||
servers = append(servers, srv)
|
||||
}
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// UpdateAssignmentStatus updates the sync status of an assignment
|
||||
func (s *Service) UpdateAssignmentStatus(id int64, status, message string) error {
|
||||
_, err := s.db.Exec(
|
||||
`UPDATE access_assignments SET status=?, last_sync_at=CURRENT_TIMESTAMP, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
|
||||
status, id,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// --- Access Assignments ---
|
||||
|
||||
// CreateAssignment creates a new access assignment
|
||||
func (s *Service) CreateAssignment(userID, sshKeyID, serverID, groupID int64, systemUser, desiredState string, sudo, createUser bool) (*models.AccessAssignment, error) {
|
||||
sudoInt := 0
|
||||
if sudo {
|
||||
sudoInt = 1
|
||||
}
|
||||
createUserInt := 0
|
||||
if createUser {
|
||||
createUserInt = 1
|
||||
}
|
||||
if desiredState == "" {
|
||||
desiredState = "present"
|
||||
}
|
||||
result, err := s.db.Exec(
|
||||
`INSERT INTO access_assignments (user_id, ssh_key_id, server_id, group_id, system_user, desired_state, sudo, create_user)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
userID, sshKeyID, serverID, groupID, systemUser, desiredState, sudoInt, createUserInt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create assignment: %w", err)
|
||||
}
|
||||
id, _ := result.LastInsertId()
|
||||
return &models.AccessAssignment{
|
||||
ID: id,
|
||||
UserID: userID,
|
||||
SSHKeyID: sshKeyID,
|
||||
ServerID: serverID,
|
||||
GroupID: groupID,
|
||||
SystemUser: systemUser,
|
||||
DesiredState: desiredState,
|
||||
Sudo: sudo,
|
||||
CreateUser: createUser,
|
||||
Status: "pending",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAllAssignments returns all access assignments with resolved display names
|
||||
func (s *Service) GetAllAssignments() ([]models.AccessAssignmentDisplay, error) {
|
||||
rows, err := s.db.Query(
|
||||
`SELECT a.id, a.user_id, a.ssh_key_id, a.server_id, a.group_id,
|
||||
a.system_user, a.desired_state, a.sudo, a.create_user,
|
||||
a.status, a.initial_password, a.last_sync_at, a.created_at, a.updated_at,
|
||||
u.username,
|
||||
COALESCE(k.name, '(deleted)'),
|
||||
CASE
|
||||
WHEN a.server_id > 0 THEN COALESCE(s.name, '(deleted)')
|
||||
WHEN a.group_id > 0 THEN COALESCE(sg.name, '(deleted)')
|
||||
ELSE '(none)'
|
||||
END,
|
||||
CASE
|
||||
WHEN a.server_id > 0 THEN 'host'
|
||||
WHEN a.group_id > 0 THEN 'group'
|
||||
ELSE 'none'
|
||||
END
|
||||
FROM access_assignments a
|
||||
LEFT JOIN users u ON a.user_id = u.id
|
||||
LEFT JOIN ssh_keys k ON a.ssh_key_id = k.id
|
||||
LEFT JOIN servers s ON a.server_id = s.id AND a.server_id > 0
|
||||
LEFT JOIN server_groups sg ON a.group_id = sg.id AND a.group_id > 0
|
||||
ORDER BY a.created_at DESC`,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query assignments: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var assignments []models.AccessAssignmentDisplay
|
||||
for rows.Next() {
|
||||
var a models.AccessAssignmentDisplay
|
||||
var sudo, createUser int
|
||||
if err := rows.Scan(&a.ID, &a.UserID, &a.SSHKeyID, &a.ServerID, &a.GroupID,
|
||||
&a.SystemUser, &a.DesiredState, &sudo, &createUser,
|
||||
&a.Status, &a.InitialPassword, &a.LastSyncAt, &a.CreatedAt, &a.UpdatedAt,
|
||||
&a.Username, &a.KeyName, &a.TargetName, &a.TargetType); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan assignment: %w", err)
|
||||
}
|
||||
a.Sudo = sudo == 1
|
||||
a.CreateUser = createUser == 1
|
||||
assignments = append(assignments, a)
|
||||
}
|
||||
return assignments, nil
|
||||
}
|
||||
|
||||
// GetAssignmentsByUser returns access assignments for a specific user
|
||||
func (s *Service) GetAssignmentsByUser(userID int64) ([]models.AccessAssignmentDisplay, error) {
|
||||
all, err := s.GetAllAssignments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var filtered []models.AccessAssignmentDisplay
|
||||
for _, a := range all {
|
||||
if a.UserID == userID {
|
||||
filtered = append(filtered, a)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// GetAssignmentByID returns a single access assignment
|
||||
func (s *Service) GetAssignmentByID(id int64) (*models.AccessAssignment, error) {
|
||||
a := &models.AccessAssignment{}
|
||||
var sudo, createUser int
|
||||
err := s.db.QueryRow(
|
||||
`SELECT id, user_id, ssh_key_id, server_id, group_id, system_user, desired_state,
|
||||
sudo, create_user, initial_password, status, last_sync_at, created_at, updated_at
|
||||
FROM access_assignments WHERE id = ?`, id,
|
||||
).Scan(&a.ID, &a.UserID, &a.SSHKeyID, &a.ServerID, &a.GroupID, &a.SystemUser, &a.DesiredState,
|
||||
&sudo, &createUser, &a.InitialPassword, &a.Status, &a.LastSyncAt, &a.CreatedAt, &a.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assignment not found: %w", err)
|
||||
}
|
||||
a.Sudo = sudo == 1
|
||||
a.CreateUser = createUser == 1
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// UpdateAssignment updates an existing access assignment
|
||||
func (s *Service) UpdateAssignment(id, userID, sshKeyID, serverID, groupID int64, systemUser, desiredState string, sudo, createUser bool) error {
|
||||
sudoInt := 0
|
||||
if sudo {
|
||||
sudoInt = 1
|
||||
}
|
||||
createUserInt := 0
|
||||
if createUser {
|
||||
createUserInt = 1
|
||||
}
|
||||
result, err := s.db.Exec(
|
||||
`UPDATE access_assignments SET user_id=?, ssh_key_id=?, server_id=?, group_id=?,
|
||||
system_user=?, desired_state=?, sudo=?, create_user=?, status='pending', updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=?`,
|
||||
userID, sshKeyID, serverID, groupID, systemUser, desiredState, sudoInt, createUserInt, id,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update assignment: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("assignment not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAssignment removes an access assignment
|
||||
func (s *Service) DeleteAssignment(id int64) error {
|
||||
result, err := s.db.Exec(`DELETE FROM access_assignments WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete assignment: %w", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return fmt.Errorf("assignment not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAssignmentInitialPassword stores the encrypted initial password for an assignment
|
||||
func (s *Service) UpdateAssignmentInitialPassword(id int64, encryptedPassword string) error {
|
||||
_, err := s.db.Exec(
|
||||
`UPDATE access_assignments SET initial_password=?, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
|
||||
encryptedPassword, id,
|
||||
)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user