mirror of
https://github.com/hexolan/panels.git
synced 2026-03-26 20:41:15 +00:00
155 lines
5.8 KiB
Go
155 lines
5.8 KiB
Go
// Copyright 2023 Declan Teevan
|
|
//
|
|
// Licensed 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 postgres
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/doug-martin/goqu/v9"
|
|
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
|
|
"github.com/jackc/pgerrcode"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/hexolan/panels/panel-service/internal"
|
|
)
|
|
|
|
type panelDatabaseRepo struct {
|
|
db *pgxpool.Pool
|
|
}
|
|
|
|
func NewPanelRepository(db *pgxpool.Pool) internal.PanelRepository {
|
|
return panelDatabaseRepo{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
func (r panelDatabaseRepo) transformToPatchData(data internal.PanelUpdate) goqu.Record {
|
|
// Ensure updated_at field is changed
|
|
patchData := goqu.Record{"updated_at": goqu.L("timezone('utc', now())")}
|
|
|
|
// Marshal the data to remove omitted keys
|
|
marshalled, _ := json.Marshal(data)
|
|
_ = json.Unmarshal(marshalled, &patchData)
|
|
return patchData
|
|
}
|
|
|
|
func (r panelDatabaseRepo) GetPanelIdFromName(ctx context.Context, name string) (*int64, error) {
|
|
var id int64
|
|
err := r.db.QueryRow(ctx, "SELECT id FROM panels WHERE LOWER(name)=LOWER($1)", name).Scan(&id)
|
|
if err != nil {
|
|
if err == pgx.ErrNoRows {
|
|
return nil, internal.WrapServiceError(err, internal.NotFoundErrorCode, "panel not found")
|
|
} else if strings.Contains(err.Error(), "failed to connect to") {
|
|
return nil, internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database")
|
|
}
|
|
log.Error().Err(err).Msg("unaccounted error whilst getting panel ID from name")
|
|
return nil, internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to get panel")
|
|
}
|
|
|
|
return &id, nil
|
|
}
|
|
|
|
func (r panelDatabaseRepo) CreatePanel(ctx context.Context, data internal.PanelCreate) (*internal.Panel, error) {
|
|
var id int64
|
|
err := r.db.QueryRow(ctx, "INSERT INTO panels (name, description) VALUES ($1, $2) RETURNING id", data.Name, data.Description).Scan(&id)
|
|
if err != nil {
|
|
var pgErr *pgconn.PgError
|
|
if errors.As(err, &pgErr) {
|
|
if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) {
|
|
return nil, internal.WrapServiceError(err, internal.ConflictErrorCode, "panel name not unique")
|
|
}
|
|
} else if strings.Contains(err.Error(), "failed to connect to") {
|
|
return nil, internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database")
|
|
}
|
|
log.Error().Err(err).Msg("unaccounted error whilst creating panel")
|
|
return nil, internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to create panel")
|
|
}
|
|
|
|
return r.GetPanel(ctx, id)
|
|
}
|
|
|
|
func (r panelDatabaseRepo) GetPanel(ctx context.Context, id int64) (*internal.Panel, error) {
|
|
var panel internal.Panel
|
|
row := r.db.QueryRow(ctx, "SELECT id, name, description, created_at, updated_at FROM panels WHERE id=$1", id)
|
|
err := row.Scan(&panel.Id, &panel.Name, &panel.Description, &panel.CreatedAt, &panel.UpdatedAt)
|
|
if err != nil {
|
|
if err == pgx.ErrNoRows {
|
|
return nil, internal.WrapServiceError(err, internal.NotFoundErrorCode, "panel not found")
|
|
} else if strings.Contains(err.Error(), "failed to connect to") {
|
|
return nil, internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database")
|
|
}
|
|
log.Error().Err(err).Msg("unaccounted error whilst getting panel")
|
|
return nil, internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to get panel")
|
|
}
|
|
|
|
return &panel, nil
|
|
}
|
|
|
|
func (r panelDatabaseRepo) UpdatePanel(ctx context.Context, id int64, data internal.PanelUpdate) (*internal.Panel, error) {
|
|
patchData := r.transformToPatchData(data)
|
|
|
|
// Build a statement to update the panel
|
|
statement, args, _ := goqu.Dialect("postgres").Update("panels").Prepared(true).Set(patchData).Where(goqu.C("id").Eq(id)).ToSQL()
|
|
|
|
// Execute the query
|
|
result, err := r.db.Exec(ctx, statement, args...)
|
|
if err != nil {
|
|
var pgErr *pgconn.PgError
|
|
if errors.As(err, &pgErr) {
|
|
if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) {
|
|
return nil, internal.WrapServiceError(err, internal.ConflictErrorCode, "panel name not unique")
|
|
}
|
|
} else if strings.Contains(err.Error(), "failed to connect to") {
|
|
return nil, internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database")
|
|
}
|
|
log.Error().Err(err).Msg("unaccounted error whilst updating panel")
|
|
return nil, internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to update panel")
|
|
}
|
|
|
|
// Check if any rows were affected from the query
|
|
rows_affected := result.RowsAffected()
|
|
if rows_affected != 1 {
|
|
return nil, internal.NewServiceError(internal.NotFoundErrorCode, "panel not found")
|
|
}
|
|
|
|
return r.GetPanel(ctx, id)
|
|
}
|
|
|
|
func (r panelDatabaseRepo) DeletePanel(ctx context.Context, id int64) error {
|
|
// Attempt to delete the panel
|
|
result, err := r.db.Exec(ctx, "DELETE FROM panels WHERE id=$1", id)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "failed to connect to") {
|
|
return internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database")
|
|
}
|
|
log.Error().Err(err).Msg("unaccounted error whilst deleting panel")
|
|
return internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to delete panel")
|
|
}
|
|
|
|
// Check if any rows were affected
|
|
rows_affected := result.RowsAffected()
|
|
if rows_affected != 1 {
|
|
return internal.NewServiceError(internal.NotFoundErrorCode, "panel not found")
|
|
}
|
|
|
|
return nil
|
|
}
|