Add Projects #13

Open
allanger wants to merge 12 commits from add-projects into main
5 changed files with 202 additions and 17 deletions
Showing only changes of commit 9c558d601b - Show all commits

View File

@@ -61,6 +61,22 @@ tasks:
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
cmd: go run main.go migrate --migrations-path=file://migrations
force-migration:
desc: Force migrate to a desired version
vars:
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
cmd: "{{ .MIGRATE }} -path=./migrations -database={{ .SOFTPLAYER_DB_CONNECTION_STRING }} force {{ .CLI_ARGS }}"
deps:
- migrate
drop-migrations:
desc: Drop migrations
vars:
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
cmd: "{{ .MIGRATE }} -path=./migrations -database={{ .SOFTPLAYER_DB_CONNECTION_STRING }} drop"
deps:
- migrate
run-server-dev:
desc: Run the local dev server
deps:
@@ -113,6 +129,8 @@ tasks:
desc: Add a new database migration
silent: true
cmd: "{{.MIGRATE}} create -dir migrations -ext sql {{.CLI_ARGS}}"
deps:
- migrate
# Install required tools
localbin:
@@ -132,6 +150,7 @@ tasks:
TARGET: "{{.MIGRATE}}"
PACKAGE: github.com/golang-migrate/migrate/v4/cmd/migrate
VERSION: latest
TAGS: "postgres"
go-install-tool:
internal: true
@@ -150,7 +169,13 @@ tasks:
echo "Downloading $PACKAGE"
rm -f "$TARGET"
GOBIN="{{.LOCALBIN}}" go install "$PACKAGE"
TAGS="{{.TAGS}}"
if [ -n "$TAGS" ]; then
echo "Using build tags: $TAGS"
GOBIN="{{.LOCALBIN}}" go install -tags "$TAGS" "$PACKAGE"
else
GOBIN="{{.LOCALBIN}}" go install "$PACKAGE"
fi
mv "{{.LOCALBIN}}/$(basename "$TARGET")" "$VERSIONED"
ln -sf "$(realpath "$VERSIONED")" "$TARGET"

View File

@@ -10,8 +10,9 @@ import (
)
var (
ErrAlreadyExists = errors.New("entry already exists")
ErrNotFound = errors.New("entry not found")
ErrAlreadyExists = errors.New("entry already exists")
ErrCheckNotPassed = errors.New("sql checks not passed")
ErrNotFound = errors.New("entry not found")
)
type AccountData struct {

View File

@@ -3,7 +3,11 @@ package repository
import (
"context"
"database/sql"
"errors"
"time"
"github.com/jackc/pgerrcode"
"github.com/jackc/pgx/v5/pgconn"
)
type ProjectData struct {
@@ -13,7 +17,7 @@ type ProjectData struct {
Description string
CreatedBy string
CreatedAt time.Time
ArchivedAt sql.NullTime
ClosedAt sql.NullTime
Blocked bool
UpdatedAt time.Time
UpdatedBy string
@@ -23,18 +27,67 @@ type ProjectData struct {
func CreateProject(ctx context.Context, db *sql.DB, data *ProjectData) error {
query := `
INSERT INTO projects
(uuid, name, slug, )
(uuid, name, slug, description, owner_user_id, billing_account_id, created_by, created_at, updated_by, updated_at)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`
if _, err := db.ExecContext(ctx, query,
data.UUID, data.Name, data.Slug, data.Description, data.CreatedBy,
data.CreatedBy, data.CreatedBy, data.CreatedAt, data.CreatedBy, data.CreatedAt); err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
switch pgErr.Code {
case pgerrcode.UniqueViolation:
return ErrAlreadyExists
case pgerrcode.CheckViolation:
return ErrCheckNotPassed
}
return err
}
return err
}
return nil
}
// GetProjectByID returns a project from the database
func GetProjectByID(ctx context.Context, db *sql.DB, projectID string) (data *ProjectData, err error) {
return nil, nil
func GetProjectByID(ctx context.Context, db *sql.DB, projectID string) (*ProjectData, error) {
data := &ProjectData{}
query := `
SELECT uuid, name, slug, description, owner_user_id, closed_at, created_at
FROM projects
WHERE uuid=$1`
if err := db.QueryRowContext(ctx, query, projectID).Scan(
&data.UUID,
&data.Name,
&data.Slug,
&data.Description,
&data.CreatedBy,
&data.ClosedAt,
&data.CreatedAt,
); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
return data, nil
}
// UpdateProject change editable project data
func UpdateProject(ctx context.Context, db *sql.DB, data *ProjectData) error {
query := `
UPDATE projects
SET
name = $2,
description = $3,
updated_at = $4,
updated_by = $5
WHERE uuid = $1;`
if _, err := db.Query(query, data.UUID, data.Name, data.Description, data.UpdatedAt, data.UpdatedBy); err != nil {
return err
}
return nil
}

View File

@@ -7,20 +7,127 @@ import (
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/repository"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func TestProjectsRepository_Success(t *testing.T) {
func TestIntegrationProjectsCreate_Success(t *testing.T) {
createdBy := uuid.NewString()
_ = &repository.ProjectData{
project := &repository.ProjectData{
UUID: uuid.NewString(),
Name: "test-1",
Slug: "test_1",
Slug: uuid.NewString(),
Description: "Test Project Number 1",
CreatedBy: createdBy,
CreatedAt: time.Now(),
ArchivedAt: sql.NullTime{Time: time.Now()},
ClosedAt: sql.NullTime{Time: time.Now()},
Blocked: false,
UpdatedAt: time.Now(),
UpdatedBy: createdBy,
}
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
}
func TestIntegrationProjectsCreate_CheckFailed(t *testing.T) {
createdBy := uuid.NewString()
project := &repository.ProjectData{
UUID: uuid.NewString(),
Name: "test-2",
Slug: "test_2",
Description: "Test Project Number 1",
CreatedBy: createdBy,
CreatedAt: time.Now(),
ClosedAt: sql.NullTime{Time: time.Now()},
Blocked: false,
UpdatedAt: time.Now(),
UpdatedBy: createdBy,
}
assert.Error(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project), repository.ErrCheckNotPassed)
}
func TestIntegrationProjectsCreate_AlreadyExistsFail(t *testing.T) {
createdBy := uuid.NewString()
project := &repository.ProjectData{
UUID: uuid.NewString(),
Name: "test-3",
Slug: uuid.NewString(),
Description: "Test Project Number 1",
CreatedBy: createdBy,
CreatedAt: time.Now(),
ClosedAt: sql.NullTime{Time: time.Now()},
Blocked: false,
UpdatedAt: time.Now(),
UpdatedBy: createdBy,
}
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
assert.Error(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project), repository.ErrAlreadyExists)
}
func TestIntegrationProjectsCreateAndGet_Success(t *testing.T) {
createdBy := uuid.NewString()
project := &repository.ProjectData{
UUID: uuid.NewString(),
Name: "test-4",
Slug: uuid.NewString(),
Description: "Test Project Number 1",
CreatedBy: createdBy,
CreatedAt: time.Now(),
Blocked: false,
UpdatedAt: time.Now(),
UpdatedBy: createdBy,
}
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
data, err := repository.GetProjectByID(t.Context(), newTestDBConnection(t.Context()), project.UUID)
assert.NoError(t, err)
assert.Equal(t, project.Name, data.Name)
assert.Equal(t, project.Slug, data.Slug)
assert.Equal(t, project.Description, data.Description)
assert.Equal(t, project.CreatedBy, data.CreatedBy)
assert.Equal(t, project.ClosedAt.Time.Truncate(time.Second), data.ClosedAt.Time.Truncate(time.Second))
assert.Equal(t, project.CreatedAt.Truncate(time.Second), data.CreatedAt.Truncate(time.Second))
}
func TestIntegrationProjectsCreateAndGet_NotFound(t *testing.T) {
data, err := repository.GetProjectByID(t.Context(), newTestDBConnection(t.Context()), uuid.NewString())
assert.ErrorIs(t, err, repository.ErrNotFound)
assert.Nil(t, data)
}
func TestIntegrationProjectsCreateUpdateAndGet_Success(t *testing.T) {
createdBy := uuid.NewString()
project := &repository.ProjectData{
UUID: uuid.NewString(),
Name: "test-5",
Slug: uuid.NewString(),
Description: "Test Project Number 1",
CreatedBy: createdBy,
CreatedAt: time.Now(),
Blocked: false,
UpdatedAt: time.Now(),
UpdatedBy: createdBy,
}
assert.NoError(t, repository.CreateProject(t.Context(), newTestDBConnection(t.Context()), project))
data, err := repository.GetProjectByID(t.Context(), newTestDBConnection(t.Context()), project.UUID)
assert.NoError(t, err)
assert.Equal(t, project.Name, data.Name)
assert.Equal(t, project.Slug, data.Slug)
assert.Equal(t, project.Description, data.Description)
assert.Equal(t, project.CreatedBy, data.CreatedBy)
assert.Equal(t, project.ClosedAt.Time.Truncate(time.Second), data.ClosedAt.Time.Truncate(time.Second))
assert.Equal(t, project.CreatedAt.Truncate(time.Second), data.CreatedAt.Truncate(time.Second))
project.UpdatedBy = uuid.NewString()
project.UpdatedAt = time.Now()
project.Description = "Updated description"
project.Name = "update name"
assert.NoError(t, repository.UpdateProject(t.Context(), newTestDBConnection(t.Context()), project))
data, err = repository.GetProjectByID(t.Context(), newTestDBConnection(t.Context()), project.UUID)
assert.NoError(t, err)
assert.Equal(t, project.Name, data.Name)
assert.Equal(t, project.Slug, data.Slug)
assert.Equal(t, project.Description, data.Description)
}

View File

@@ -1,5 +1,5 @@
CREATE TABLE projects (
id UUID PRIMARY KEY,
uuid UUID PRIMARY KEY,
name VARCHAR(120) NOT NULL,
slug VARCHAR(120) NOT NULL UNIQUE
CHECK (
@@ -7,11 +7,10 @@ CREATE TABLE projects (
),
description TEXT,
owner_user_id UUID NOT NULL,
archived_at TIMESTAMP NULL,
closed_at TIMESTAMP NULL,
closed_at TIMESTAMPTZ NULL,
billing_account_id UUID NULL,
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT now(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_by UUID NOT NULL,
updated_by UUID NOT NULL,
updated_by UUID NOT NULL
);