This commit is contained in:
2025-11-12 09:41:52 +03:00
commit 2a8566712a
44 changed files with 2602 additions and 0 deletions

202
internal/storage/issues.go Normal file
View File

@@ -0,0 +1,202 @@
package storage
import (
"context"
"errors"
"fmt"
"log"
"madsky.ru/go-tracker/internal/database"
"madsky.ru/go-tracker/internal/model/issue"
"madsky.ru/go-tracker/internal/model/project"
"madsky.ru/go-tracker/internal/model/status"
)
type IssueRepository interface {
Find(ctx context.Context) ([]*issue.Issue, error)
UpdatePositions(ctx context.Context, position int, statusId uint32, id uint32) (*uint32, error)
FindOne(ctx context.Context, id uint64) (*issue.Issue, error)
Create(ctx context.Context, dto *issue.CreateIssueDTO) (*uint64, error)
Update(ctx context.Context, id uint64, issue *issue.Issue) error
Remove(ctx context.Context, id uint64) (uint64, error)
}
type IssueStore struct {
client database.Client
}
func (is *IssueStore) Find(ctx context.Context) ([]*issue.Issue, error) {
q := `select
i."id",
i."name",
i."description",
i."position",
i."created",
i."project_id",
i."status_id",
p."id" as project_id,
p."name" as project_name,
p."description" as project_description,
p."key" as project_key,
s."id" as status_id,
s."name" as status_name,
s."description" as status_description,
s."position" as position
from issues i
join projects p on p.id = project_id
join statuses s on s.id = status_id`
rows, err := is.client.Query(ctx, q)
if err != nil {
log.Println("rows", err)
return nil, err
}
issues := make([]*issue.Issue, 0)
for rows.Next() {
var n issue.Issue
var p project.Project
var s status.Status
err = rows.Scan(
&n.ID,
&n.Name,
&n.Description,
&n.Position,
&n.Created,
&n.ProjectID,
&n.StatusID,
&p.ID,
&p.Name,
&p.Description,
&p.Key,
&s.ID,
&s.Name,
&s.Description,
&s.Position,
)
if err != nil {
log.Println("scan", err)
return nil, err
}
n.Project = p
n.Status = s
issues = append(issues, &n)
}
if err = rows.Err(); err != nil {
log.Println("rows err", err)
return nil, err
}
return issues, nil
}
func (is *IssueStore) UpdatePositions(ctx context.Context, position int, statusId uint32, id uint32) (*uint32, error) {
q := `update issues set "position" = $1, "status_id"=$2 where "id"=$3 returning id`
var resultId uint32
if err := is.client.QueryRow(ctx, q, position, statusId, id).Scan(&resultId); err != nil {
fmt.Println(fmt.Sprintf("error %v", err))
return nil, err
}
return &resultId, nil
}
func (is *IssueStore) FindOne(ctx context.Context, id uint64) (*issue.Issue, error) {
q := `select
i."id",
i."name",
i."description",
i."position",
i."created",
i."project_id",
i."status_id",
p."id" as project_id,
p."name" as project_name,
p."description" as project_description,
p."key" as project_key,
s."id" as status_id,
s."name" as status_name,
s."description" as status_description,
s."position" as position
from issues i
join projects p on p.id = project_id
join statuses s on s.id = status_id
where i."id" = $1`
var n issue.Issue
var p project.Project
var s status.Status
if err := is.client.QueryRow(ctx, q, id).Scan(
&n.ID,
&n.Name,
&n.Description,
&n.Position,
&n.Created,
&n.ProjectID,
&n.StatusID,
&p.ID,
&p.Name,
&p.Description,
&p.Key,
&s.ID,
&s.Name,
&s.Description,
&s.Position,
); err != nil {
fmt.Println(err)
return &issue.Issue{}, err
}
n.Project = p
n.Status = s
return &n, nil
}
func (is *IssueStore) Create(ctx context.Context, dto *issue.CreateIssueDTO) (*uint64, error) {
q := `insert into issues (name, description, project_id, status_id, position)
values ($1, $2, $3, $4, $5)
returning id`
var position uint32 = 0
var id uint64
if dto.Position != nil {
position = *dto.Position
}
if err := is.client.QueryRow(ctx, q, dto.Name, dto.Description, dto.ProjectID, dto.StatusID, position).Scan(&id); err != nil {
fmt.Println(fmt.Sprintf("error %v", err))
return nil, err
}
return &id, nil
}
func (is *IssueStore) Update(ctx context.Context, id uint64, issue *issue.Issue) error {
//TODO implement me
fmt.Println("update", id, issue, ctx)
panic("implement me")
}
func (is *IssueStore) Remove(ctx context.Context, id uint64) (uint64, error) {
q := "delete from issues where id=$1"
tag, err := is.client.Exec(ctx, q, id)
if err != nil {
log.Println("exec error", err)
return 0, err
}
rowsAffected := tag.RowsAffected()
if rowsAffected == 0 {
return 0, errors.New("project not found")
}
return uint64(rowsAffected), nil
}

View File

@@ -0,0 +1,169 @@
package storage
import (
"context"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"log"
"madsky.ru/go-tracker/internal/database"
"madsky.ru/go-tracker/internal/model/project"
"strings"
)
type ProjectRepository interface {
Find(ctx context.Context, userId uint32, isAdmin bool, filter *project.FilterDTO) ([]*project.Project, error)
FindOne(ctx context.Context, projectId uint64, userId uint32, isAdmin bool) (*project.Project, error)
Create(ctx context.Context, dto *project.CreateProjectDTO) (*project.Project, error)
Update(ctx context.Context, id uint64, issue *project.UpdateProjectDTO) (*project.Project, error)
Remove(ctx context.Context, id uint64) (uint64, error)
}
type ProjectStore struct {
client database.Client
}
func (ps *ProjectStore) Find(ctx context.Context, userId uint32, isAdmin bool, filter *project.FilterDTO) ([]*project.Project, error) {
query := `select p.id, p.name, p.description, p.key from projects p `
var args []interface{}
if isAdmin == false {
query += `left join user_to_project up on up.project_id = p.id where up.user_id = $1`
args = append(args, userId)
}
rows, err := ps.client.Query(ctx, query, args...)
if err != nil {
log.Println("ProjectStore rows", err)
return nil, err
}
defer rows.Close()
// another variant
//projects, err := pgx.CollectRows(rows, pgx.RowToStructByName[project.Project])
projects := make([]*project.Project, 0)
for rows.Next() {
var p project.Project
err = rows.Scan(&p.ID, &p.Name, &p.Description, &p.Key)
if err != nil {
log.Println("scan", err)
return nil, err
}
projects = append(projects, &p)
}
if err = rows.Err(); err != nil {
log.Println("rows err", err)
return nil, err
}
return projects, nil
}
func (ps *ProjectStore) FindOne(ctx context.Context, projectId uint64, userId uint32, isAdmin bool) (*project.Project, error) {
args := []interface{}{projectId}
query := `select p.id, p.name, p.description, p.key from projects p
left join user_to_project up on up.project_id = p.id
where p.id = $1 `
if isAdmin == false {
query += "and up.user_id = $2"
args = append(args, userId)
}
var p project.Project
rows, _ := ps.client.Query(ctx, query, args...)
defer rows.Close()
p, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[project.Project])
if err != nil {
fmt.Println("CollectOneRow FindById project", err)
return nil, err
}
return &p, nil
}
func (ps *ProjectStore) Create(ctx context.Context, dto *project.CreateProjectDTO) (*project.Project, error) {
//q := "insert into projects (name, description, key) values ($1, $2, $3) returning id, name, description, key"
query := `insert into projects (name, description, key)
values (@projName, @projDescription, @projKey)
returning id, name, description, key`
key := dto.Key
if dto.Key == "" {
key = trimString(dto.Name)
}
args := pgx.NamedArgs{
"projName": dto.Name,
"projDescription": dto.Description,
"projKey": key,
}
//rows, _ := ps.client.Query(ctx, q, dto.Name, dto.Description, key)
rows, _ := ps.client.Query(ctx, query, args)
defer rows.Close()
p, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[project.Project])
if err != nil {
fmt.Println("CollectOneRow Create Project", err)
//return nil, fmt.Errorf("unable to insert row: %w", err)
return nil, fmt.Errorf("KEY must be 4 characters long")
}
return &p, nil
}
func (ps *ProjectStore) Update(ctx context.Context, id uint64, dto *project.UpdateProjectDTO) (*project.Project, error) {
q := `update projects
set name = $1, description = $2
where id = $3
returning id, name, description, key`
var p project.Project
if err := ps.client.QueryRow(ctx, q, dto.Name, dto.Description, id).Scan(&p.ID, &p.Name, &p.Description, &p.Key); err != nil {
fmt.Println(fmt.Sprintf("error %v", err))
return nil, err
}
return &p, nil
}
func (ps *ProjectStore) Remove(ctx context.Context, id uint64) (uint64, error) {
q := `delete from projects where id=$1`
tag, err := ps.client.Exec(ctx, q, id)
if err != nil {
log.Println("exec error", err)
return 0, err
}
rowsAffected := tag.RowsAffected()
if rowsAffected == 0 {
return 0, errors.New("project not found")
}
return uint64(rowsAffected), nil
}
func trimString(str string) string {
runeStr := []rune(str)
var res string
if len(runeStr) > 4 {
res = string(runeStr[:3])
} else {
res = string(runeStr)
}
return strings.ToUpper(res)
}

View File

@@ -0,0 +1,46 @@
package storage
import (
"context"
"log"
"madsky.ru/go-tracker/internal/database"
"madsky.ru/go-tracker/internal/model/setting"
)
type ServerSettingsRepository interface {
Find(ctx context.Context) ([]*setting.Setting, error)
}
type ServerSettingsStore struct {
client database.Client
}
func (store *ServerSettingsStore) Find(ctx context.Context) ([]*setting.Setting, error) {
query := "select id, name, value from public.server_settings"
rows, err := store.client.Query(ctx, query)
if err != nil {
log.Println("ServerSettingsStore query err", err)
return nil, err
}
res := make([]*setting.Setting, 0)
for rows.Next() {
var r setting.Setting
err := rows.Scan(&r.ID, &r.Name, &r.Value)
if err != nil {
log.Println("ServerSettingsStore scan err", err)
return nil, err
}
res = append(res, &r)
}
if err := rows.Err(); err != nil {
log.Println("ServerSettingsStore rows err", err)
return nil, err
}
return res, nil
}

View File

@@ -0,0 +1,99 @@
package storage
import (
"context"
"errors"
"fmt"
"log"
"madsky.ru/go-tracker/internal/database"
"madsky.ru/go-tracker/internal/model/status"
)
type StatusRepository interface {
Find(ctx context.Context) ([]*status.Status, error)
FindOne(ctx context.Context, id uint64) (*status.Status, error)
Create(ctx context.Context, dto *status.CreateStatusDTO) (*status.Status, error)
Update(ctx context.Context, id uint64, issue *status.Status) error
Remove(ctx context.Context, id uint64) (uint64, error)
}
type StatusStore struct {
client database.Client
}
func (r *StatusStore) Find(ctx context.Context) ([]*status.Status, error) {
query := "select id, name, description, position from statuses"
rows, err := r.client.Query(ctx, query)
if err != nil {
log.Println("rows", err)
return nil, err
}
statuses := make([]*status.Status, 0)
for rows.Next() {
var n status.Status
err = rows.Scan(&n.ID, &n.Name, &n.Description, &n.Position)
if err != nil {
log.Println("scan", err)
return nil, err
}
statuses = append(statuses, &n)
}
if err = rows.Err(); err != nil {
log.Println("rows err", err)
return nil, err
}
return statuses, nil
}
func (r *StatusStore) FindOne(ctx context.Context, id uint64) (*status.Status, error) {
query := "select id, name, description, position from statuses where id = $1"
var s status.Status
if err := r.client.QueryRow(ctx, query, id).Scan(&s.ID, &s.Name, &s.Description, &s.Position); err != nil {
fmt.Println(err)
return nil, err
}
return &s, nil
}
func (r *StatusStore) Create(ctx context.Context, dto *status.CreateStatusDTO) (*status.Status, error) {
q := "insert into statuses (name, description) values ($1, $2) returning id, name, description, position"
var s status.Status
if err := r.client.QueryRow(ctx, q, dto.Name, dto.Description).Scan(&s.ID, &s.Name, &s.Description, &s.Position); err != nil {
fmt.Println(fmt.Sprintf("error %v", err))
return nil, err
}
return &s, nil
}
func (r *StatusStore) Update(ctx context.Context, id uint64, issue *status.Status) error {
//TODO implement me
panic("implement me")
}
func (r *StatusStore) Remove(ctx context.Context, id uint64) (uint64, error) {
q := "delete from statuses where id=$1"
tag, err := r.client.Exec(ctx, q, id)
if err != nil {
log.Println("exec error", err)
return 0, err
}
rowsAffected := tag.RowsAffected()
if rowsAffected == 0 {
return 0, errors.New("status not found")
}
return uint64(rowsAffected), nil
}

View File

@@ -0,0 +1,25 @@
package storage
import (
"github.com/jackc/pgx/v5/pgxpool"
)
type Storage struct {
Projects ProjectRepository
ServerSettings ServerSettingsRepository
Issues IssueRepository
Status StatusRepository
User UserRepository
UserToProject UserToProjectRepository
}
func NewStorage(client *pgxpool.Pool) *Storage {
return &Storage{
Projects: &ProjectStore{client: client},
ServerSettings: &ServerSettingsStore{client: client},
Issues: &IssueStore{client: client},
Status: &StatusStore{client: client},
User: &UserStore{client: client},
UserToProject: &UserToProjectStore{client: client},
}
}

View File

@@ -0,0 +1,32 @@
package storage
import (
"context"
"fmt"
"github.com/jackc/pgx/v5"
"madsky.ru/go-tracker/internal/database"
)
type UserToProjectRepository interface {
Create(ctx context.Context, userId uint32, projectId uint32) error
}
type UserToProjectStore struct {
client database.Client
}
func (up *UserToProjectStore) Create(ctx context.Context, userId uint32, projectId uint32) error {
query := `insert into user_to_project (user_id, project_id) values (@userId, @projectId)`
args := pgx.NamedArgs{
"userId": userId,
"projectId": projectId,
}
_, err := up.client.Exec(ctx, query, args)
if err != nil {
return fmt.Errorf("unable to insert row: %w", err)
}
return nil
}

108
internal/storage/user.go Normal file
View File

@@ -0,0 +1,108 @@
package storage
import (
"context"
"fmt"
"github.com/jackc/pgx/v5"
"madsky.ru/go-tracker/internal/database"
"madsky.ru/go-tracker/internal/model/user"
)
type UserRepository interface {
FindByEmail(ctx context.Context, email string) (*user.User, error)
FindById(ctx context.Context, id uint32) (*user.User, error)
Create(ctx context.Context, user *user.User) (*user.User, error)
IsEmpty(ctx context.Context) (bool, error)
Update(ctx context.Context, id uint32, dto *user.UpdateUserDTO) (*user.User, error)
//Find(ctx context.Context) ([]*user.User, error)
//FindOne(ctx context.Context, id uint64) (*user.User, error)
//Create(ctx context.Context, dto *user.CreateUserDTO) (*user.User, error)
//Remove(ctx context.Context, id uint64) (uint64, error)
}
type UserStore struct {
client database.Client
}
func (us *UserStore) FindById(ctx context.Context, id uint32) (*user.User, error) {
query := `select id, email, name, password_hash, role
from users
where id = $1`
rows, _ := us.client.Query(ctx, query, id)
defer rows.Close()
u, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[user.User])
if err != nil {
fmt.Println("CollectOneRow FindById User", err)
return nil, err
}
return &u, nil
}
func (us *UserStore) FindByEmail(ctx context.Context, email string) (*user.User, error) {
query := `select id, email, name, password_hash, role
from users
where email = $1`
rows, _ := us.client.Query(ctx, query, email)
defer rows.Close()
u, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[user.User])
if err != nil {
fmt.Println("CollectOneRow FindByEmail User", err)
return nil, err
}
return &u, nil
}
func (us *UserStore) IsEmpty(ctx context.Context) (bool, error) {
query := "select count(id) from users limit 1"
rows, _ := us.client.Query(ctx, query)
defer rows.Close()
count, err := pgx.CollectOneRow(rows, pgx.RowTo[int])
if err != nil {
fmt.Println("CollectOneRow IsEmpty User", err)
return false, err
}
return count == 0, nil
}
func (us *UserStore) Create(ctx context.Context, dto *user.User) (*user.User, error) {
query := `insert into users (email, name, password_hash, role)
values ($1, $2, $3, $4)
returning id, name, email, password_hash, role`
rows, _ := us.client.Query(ctx, query, dto.Email, dto.Name, dto.PasswordHash, dto.Role)
defer rows.Close()
u, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[user.User])
if err != nil {
fmt.Println("CollectOneRow Create User", err)
return nil, err
}
return &u, nil
}
func (us *UserStore) Update(ctx context.Context, id uint32, dto *user.UpdateUserDTO) (*user.User, error) {
query := `update users
set name = $1
where id = $2
returning id, email, name, password_hash, role`
rows, _ := us.client.Query(ctx, query, dto.Name, id)
defer rows.Close()
u, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[user.User])
if err != nil {
fmt.Println("CollectOneRow Update User", err)
return nil, err
}
return &u, nil
}