package db import ( "context" "fmt" "net/http" "strings" "github.com/H1K0/Kiraku/internal/models" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" ) func PersonsGet(ctx context.Context, user_id, filter, sort string, limit, offset int) (persons models.Persons, statusCode int, err error) { ok, _ := UserAuth(ctx, user_id) if !ok { err = fmt.Errorf("unauthorized") statusCode = http.StatusUnauthorized return } queryGet := "SELECT id, name, COALESCE(sort_name, '') FROM persons WHERE POSITION($1 IN LOWER(name))>0 OR POSITION($1 IN LOWER(sort_name))>0" if sort != "" { sort_options := strings.Split(sort, ",") queryGet += " ORDER BY " for i, sort_option := range sort_options { sort_order := sort_option[:1] sort_field := sort_option[1:] switch sort_order { case "+": sort_order = "ASC" case "-": sort_order = "DESC" default: err = fmt.Errorf("invalid sorting order mark: %q", sort) statusCode = http.StatusBadRequest return } switch sort_field { case "name": case "sortName": sort_field = "COALESCE(sort_name, name)" case "birthdate": case "deathdate": default: err = fmt.Errorf("invalid sorting field: %q", sort_field) statusCode = http.StatusBadRequest return } if i > 0 { queryGet += ", " } queryGet += fmt.Sprintf("%s %s NULLS LAST", sort_field, sort_order) } } queryCount := queryGet if limit >= 0 { queryGet += fmt.Sprintf(" LIMIT %d", limit) } if offset > 0 { queryGet += fmt.Sprintf(" OFFSET %d", offset) } filter = strings.ToLower(filter) statusCode, err = transaction(ctx, func(tx pgx.Tx) (statusCode int, err error) { rows, err := tx.Query(ctx, queryGet, filter) if err != nil { statusCode = http.StatusInternalServerError return } count := 0 for rows.Next() { var person models.PersonBrief err = rows.Scan(&person.ID, &person.Name, &person.SortName) if err != nil { statusCode = http.StatusInternalServerError return } persons.Persons = append(persons.Persons, person) count++ } err = rows.Err() if err != nil { statusCode = http.StatusInternalServerError return } persons.Pagination.Limit = limit persons.Pagination.Offset = offset persons.Pagination.Count = count queryCount = fmt.Sprintf("SELECT COUNT(*) FROM (%s) tmp", queryCount) row := tx.QueryRow(ctx, queryCount, filter) err = row.Scan(&persons.Pagination.Total) if err != nil { statusCode = http.StatusInternalServerError } return }) if err != nil { return } statusCode = http.StatusOK return } func PersonGet(ctx context.Context, user_id, person_id string) (person models.Person, statusCode int, err error) { ok, _ := UserAuth(ctx, user_id) if !ok { err = fmt.Errorf("unauthorized") statusCode = http.StatusUnauthorized return } row := connPool.QueryRow(ctx, "SELECT id, name, COALESCE(sort_name, ''), COALESCE(TO_CHAR(birthdate, 'YYYY-MM-DD'), ''), COALESCE(TO_CHAR(deathdate, 'YYYY-MM-DD'), ''), COALESCE(info, '') FROM persons WHERE id=$1", person_id) err = row.Scan(&person.ID, &person.Name, &person.SortName, &person.Birthdate, &person.Deathdate, &person.Deathdate) if err != nil { if err == pgx.ErrNoRows { err = fmt.Errorf("not found") statusCode = http.StatusNotFound return } pgErr := err.(*pgconn.PgError) if pgErr.Code == "22P02" || pgErr.Code == "22007" { err = fmt.Errorf("%s", pgErr.Message) statusCode = http.StatusBadRequest } else { statusCode = http.StatusInternalServerError } return } statusCode = http.StatusOK return } func PersonAdd(ctx context.Context, user_id, name, sortName, birthdate, deathdate, info string) (person models.Person, statusCode int, err error) { ok, editor := UserAuth(ctx, user_id) if !ok { err = fmt.Errorf("unauthorized") statusCode = http.StatusUnauthorized return } if !editor { err = fmt.Errorf("not allowed") statusCode = http.StatusForbidden return } row := connPool.QueryRow( ctx, "INSERT INTO persons (name, sort_name, birthdate, deathdate, info) "+ "VALUES ($1, NULLIF($2, ''), NULLIF($3, '')::date, NULLIF($4, '')::date, NULLIF($5, '')) "+ "RETURNING id, name, COALESCE(sort_name, ''), COALESCE(TO_CHAR(birthdate, 'YYYY-MM-DD'), ''), COALESCE(TO_CHAR(deathdate, 'YYYY-MM-DD'), ''), COALESCE(info, '')", name, sortName, birthdate, deathdate, info, ) err = row.Scan(&person.ID, &person.Name, &person.SortName, &person.Birthdate, &person.Deathdate, &person.Info) if err != nil { pgErr := err.(*pgconn.PgError) if pgErr.Code == "22P02" || pgErr.Code == "22007" { err = fmt.Errorf("%s", pgErr.Message) statusCode = http.StatusBadRequest } else if pgErr.Code == "23505" { err = fmt.Errorf("a person with this name already exists") statusCode = http.StatusConflict } else { statusCode = http.StatusInternalServerError } return } statusCode = http.StatusOK return } func PersonUpdate(ctx context.Context, user_id, person_id string, values map[string]string) (person models.Person, statusCode int, err error) { ok, editor := UserAuth(ctx, user_id) if !ok { err = fmt.Errorf("unauthorized") statusCode = http.StatusUnauthorized return } if !editor { err = fmt.Errorf("not allowed") statusCode = http.StatusForbidden return } statusCode, err = transaction(ctx, func(tx pgx.Tx) (statusCode int, err error) { for _, field := range []string{"name", "sortName", "birthdate", "deathdate", "info"} { value, ok := values[field] if !ok { continue } if field == "sortName" { field = "sort_name" } var query string if field == "birthdate" || field == "deathdate" { query = fmt.Sprintf("UPDATE persons SET %s=NULLIF($2, '')::date WHERE id=$1", field) } else { query = fmt.Sprintf("UPDATE persons SET %s=NULLIF($2, '') WHERE id=$1", field) } var commandTag pgconn.CommandTag commandTag, err = tx.Exec(ctx, query, person_id, value) if err != nil { pgErr := err.(*pgconn.PgError) if pgErr.Code == "22P02" || pgErr.Code == "22007" { err = fmt.Errorf("%s", pgErr.Message) statusCode = http.StatusBadRequest } else if pgErr.Code == "23505" { err = fmt.Errorf("a person with this name already exists") statusCode = http.StatusConflict } else { statusCode = http.StatusInternalServerError } return } if commandTag.RowsAffected() == 0 { err = fmt.Errorf("not found") statusCode = http.StatusNotFound return } } row := tx.QueryRow(ctx, "SELECT id, name, COALESCE(sort_name, ''), COALESCE(TO_CHAR(birthdate, 'YYYY-MM-DD'), ''), COALESCE(TO_CHAR(deathdate, 'YYYY-MM-DD'), ''), COALESCE(info, '') FROM persons WHERE id=$1", person_id) err = row.Scan(&person.ID, &person.Name, &person.SortName, &person.Birthdate, &person.Deathdate, &person.Info) if err != nil { statusCode = http.StatusInternalServerError } return }) if err != nil { return } statusCode = http.StatusOK return } func PersonDelete(ctx context.Context, user_id, person_id string) (statusCode int, err error) { ok, editor := UserAuth(ctx, user_id) if !ok { err = fmt.Errorf("unauthorized") statusCode = http.StatusUnauthorized return } if !editor { err = fmt.Errorf("not allowed") statusCode = http.StatusForbidden return } commandTag, err := connPool.Exec(ctx, "DELETE FROM persons WHERE id=$1", person_id) if err != nil { pgErr := err.(*pgconn.PgError) if pgErr.Code == "22P02" { err = fmt.Errorf("%s", pgErr.Message) statusCode = http.StatusBadRequest } else { statusCode = http.StatusInternalServerError } return } if commandTag.RowsAffected() == 0 { err = fmt.Errorf("not found") statusCode = http.StatusNotFound return } statusCode = http.StatusNoContent return }