Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add software and software logs endpoints #69

Merged
merged 6 commits into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ linters-settings:
- .WithMessage(
- .WithMessagef(
- .WithStack(
funlen:
# Increase the number of lines, considering funlen counts comments as well
# (https://github.com/ultraware/funlen/issues/12)
#
# default: 60
lines: 80

linters:
enable-all: true
Expand Down
6 changes: 5 additions & 1 deletion internal/common/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ type CodeHosting struct {
URL string `json:"url" validate:"required"`
}

type Software struct {
URLs []string `json:"urls" validate:"required,gt=1"`
}

type Log struct {
Message string `json:"message" validate:"required"`
Message string `json:"message" validate:"required,gt=1"`
}
8 changes: 7 additions & 1 deletion internal/database/postgres_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ func (d *PostgresDB) Init(dsn string) (*gorm.DB, error) {
return nil, fmt.Errorf("can't open database: %w", err)
}

if err = database.AutoMigrate(&models.Publisher{}, &models.CodeHosting{}, &models.Log{}); err != nil {
if err = database.AutoMigrate(
&models.Publisher{},
&models.CodeHosting{},
&models.Log{},
&models.Software{},
&models.SoftwareURL{},
); err != nil {
return nil, fmt.Errorf("can't migrate database: %w", err)
}

Expand Down
8 changes: 7 additions & 1 deletion internal/database/sqlite_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ func (d *SQLiteDB) Init(dsn string) (*gorm.DB, error) {
return nil, fmt.Errorf("can't open database: %w", err)
}

if err = database.AutoMigrate(&models.Publisher{}, &models.CodeHosting{}, &models.Log{}); err != nil {
if err = database.AutoMigrate(
&models.Publisher{},
&models.CodeHosting{},
&models.Log{},
&models.Software{},
&models.SoftwareURL{},
); err != nil {
return nil, fmt.Errorf("can't migrate database: %w", err)
}

Expand Down
3 changes: 1 addition & 2 deletions internal/handlers/general/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ type PaginationLinks paginator.Cursor

func NewPaginator(ctx *fiber.Ctx) *paginator.Paginator {
paginator := paginator.New(&paginator.Config{
Rules: []paginator.Rule{{Key: "ID"}, {Key: "CreatedAt"}},
Keys: []string{},
Keys: []string{"ID", "CreatedAt"},
Limit: DefaultLimitCount,
Order: paginator.ASC,
})
Expand Down
103 changes: 96 additions & 7 deletions internal/handlers/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"github.com/italia/developers-italia-api/internal/common"
"github.com/italia/developers-italia-api/internal/handlers/general"
"github.com/italia/developers-italia-api/internal/models"
Expand All @@ -16,6 +17,9 @@ type LogInterface interface {
PostLog(ctx *fiber.Ctx) error
PatchLog(ctx *fiber.Ctx) error
DeleteLog(ctx *fiber.Ctx) error

GetSoftwareLogs(ctx *fiber.Ctx) error
PostSoftwareLog(ctx *fiber.Ctx) error
}

type Log struct {
Expand Down Expand Up @@ -67,7 +71,7 @@ func (p *Log) GetLogs(ctx *fiber.Ctx) error {
func (p *Log) GetLog(ctx *fiber.Ctx) error {
log := models.Log{}

if err := p.db.First(&log, ctx.Params("id")).Error; err != nil {
if err := p.db.First(&log, "id = ?", ctx.Params("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.Error(fiber.StatusNotFound, "can't get Log", "Log was not found")
}
Expand All @@ -90,7 +94,7 @@ func (p *Log) PostLog(ctx *fiber.Ctx) error {
return common.Error(fiber.StatusUnprocessableEntity, "can't create Log", "invalid format")
}

log := models.Log{Message: logReq.Message}
log := models.Log{ID: utils.UUIDv4(), Message: logReq.Message}

if err := p.db.Create(&log).Error; err != nil {
return common.Error(fiber.StatusInternalServerError, "can't create Log", "db error")
Expand All @@ -113,7 +117,7 @@ func (p *Log) PatchLog(ctx *fiber.Ctx) error {

log := models.Log{}

if err := p.db.First(&log, ctx.Params("id")).Error; err != nil {
if err := p.db.First(&log, "id = ?", ctx.Params("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.Error(fiber.StatusNotFound, "can't update Log", "Log was not found")
}
Expand All @@ -134,13 +138,98 @@ func (p *Log) PatchLog(ctx *fiber.Ctx) error {
func (p *Log) DeleteLog(ctx *fiber.Ctx) error {
var log models.Log

if err := p.db.Delete(&log, ctx.Params("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.Error(fiber.StatusNotFound, "can't delete Log", "Log was not found")
}
result := p.db.Delete(&log, "id = ?", ctx.Params("id"))

if result.Error != nil {
return common.Error(fiber.StatusInternalServerError, "can't delete Log", "db error")
}

if result.RowsAffected == 0 {
return common.Error(fiber.StatusNotFound, "can't delete Log", "Log was not found")
}

return ctx.SendStatus(fiber.StatusNoContent)
}

// GetSoftwareLogs gets the logs associated to a Software with the given ID and returns any error encountered.
func (p *Log) GetSoftwareLogs(ctx *fiber.Ctx) error {
var logs []models.Log

software := models.Software{}

if err := p.db.First(&software, "id = ?", ctx.Params("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.Error(fiber.StatusNotFound, "can't get Software", "Software was not found")
}

return common.Error(
fiber.StatusInternalServerError,
"can't get Software",
fiber.ErrInternalServerError.Message,
)
}

stmt := p.db.
Where(map[string]interface{}{"entity_type": models.Software{}.TableName()}).
bfabio marked this conversation as resolved.
Show resolved Hide resolved
Where("entity_id = ?", software.ID)

paginator := general.NewPaginator(ctx)

result, cursor, err := paginator.Paginate(stmt, &logs)
if err != nil {
return common.Error(
fiber.StatusBadRequest,
"can't get Software",
"wrong cursor format in page[after] or page[before]",
)
}

if result.Error != nil {
return common.Error(
fiber.StatusInternalServerError,
"can't get Software",
fiber.ErrInternalServerError.Message,
)
}

return ctx.JSON(fiber.Map{"data": &logs, "links": general.PaginationLinks(cursor)})
}

// PostSoftwareLog creates a new log associated to a Software with the given ID and returns any error encountered.
func (p *Log) PostSoftwareLog(ctx *fiber.Ctx) error {
logReq := new(common.Log)

if err := ctx.BodyParser(&logReq); err != nil {
return common.Error(fiber.StatusBadRequest, "can't create Log", "invalid json")
}

if err := common.ValidateStruct(*logReq); err != nil {
return common.Error(fiber.StatusUnprocessableEntity, "can't create Log", "invalid format")
bfabio marked this conversation as resolved.
Show resolved Hide resolved
}

software := models.Software{}
if err := p.db.First(&software, "id = ?", ctx.Params("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.Error(fiber.StatusNotFound, "can't get Software", "Software was not found")
}

return common.Error(
fiber.StatusInternalServerError,
"can't get Software",
fiber.ErrInternalServerError.Message,
)
}

log := models.Log{
ID: utils.UUIDv4(),
Message: logReq.Message,
EntityID: software.ID,
EntityType: models.Software{}.TableName(),
}

if err := p.db.Create(&log).Error; err != nil {
return common.Error(fiber.StatusInternalServerError, "can't create Log", "db error")
}

return ctx.JSON(&log)
}
155 changes: 155 additions & 0 deletions internal/handlers/software.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package handlers

import (
"errors"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
"github.com/italia/developers-italia-api/internal/common"
"github.com/italia/developers-italia-api/internal/handlers/general"
"github.com/italia/developers-italia-api/internal/models"
"gorm.io/gorm"
)

type SoftwareInterface interface {
GetAllSoftware(ctx *fiber.Ctx) error
GetSoftware(ctx *fiber.Ctx) error
PostSoftware(ctx *fiber.Ctx) error
PatchSoftware(ctx *fiber.Ctx) error
DeleteSoftware(ctx *fiber.Ctx) error
}

type Software struct {
db *gorm.DB
}

func NewSoftware(db *gorm.DB) *Software {
return &Software{db: db}
}

// GetAllSoftware gets the list of all software and returns any error encountered.
func (p *Software) GetAllSoftware(ctx *fiber.Ctx) error {
var software []models.Software

stmt := p.db.Begin().Preload("URLs")

paginator := general.NewPaginator(ctx)

result, cursor, err := paginator.Paginate(stmt, &software)
if err != nil {
return common.Error(
fiber.StatusBadRequest,
"can't get Software",
"wrong cursor format in page[after] or page[before]",
)
}

if result.Error != nil {
return common.Error(
fiber.StatusInternalServerError,
"can't get Software",
fiber.ErrInternalServerError.Message,
)
}

return ctx.JSON(fiber.Map{"data": &software, "links": general.PaginationLinks(cursor)})
}

// GetSoftware gets the software with the given ID and returns any error encountered.
func (p *Software) GetSoftware(ctx *fiber.Ctx) error {
software := models.Software{}

if err := p.db.First(&software, ctx.Params("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.Error(fiber.StatusNotFound, "can't get Software", "Software was not found")
}

return common.Error(
fiber.StatusInternalServerError,
"can't get Software",
fiber.ErrInternalServerError.Message,
)
}

return ctx.JSON(&software)
}

// PostSoftware creates a new software.
func (p *Software) PostSoftware(ctx *fiber.Ctx) error {
softwareReq := new(common.Software)

if err := ctx.BodyParser(&softwareReq); err != nil {
return common.Error(fiber.StatusBadRequest, "can't create Software", "invalid json")
}

if err := common.ValidateStruct(*softwareReq); err != nil {
return common.Error(fiber.StatusUnprocessableEntity, "can't create Software", "invalid format", err)
}

softwareURLs := []models.SoftwareURL{}
for _, u := range softwareReq.URLs {
softwareURLs = append(softwareURLs, models.SoftwareURL{ID: utils.UUIDv4(), URL: u})
}

software := models.Software{
ID: utils.UUIDv4(),
URLs: softwareURLs,
}

if err := p.db.Create(&software).Error; err != nil {
return common.Error(fiber.StatusInternalServerError, "can't create Software", err.Error())
}

return ctx.JSON(&software)
}

// PatchSoftware updates the software with the given ID.
func (p *Software) PatchSoftware(ctx *fiber.Ctx) error {
softwareReq := new(common.Software)

if err := ctx.BodyParser(softwareReq); err != nil {
return common.Error(fiber.StatusBadRequest, "can't update Software", "invalid json")
}

if err := common.ValidateStruct(*softwareReq); err != nil {
return common.Error(fiber.StatusUnprocessableEntity, "can't update Software", "invalid format", err)
}

software := models.Software{}

if err := p.db.First(&software, ctx.Params("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.Error(fiber.StatusNotFound, "can't update Software", "Software was not found")
}

return common.Error(fiber.StatusInternalServerError, "can't update Software", "internal server error")
}

softwareURLs := []models.SoftwareURL{}
for _, u := range softwareReq.URLs {
softwareURLs = append(softwareURLs, models.SoftwareURL{ID: utils.UUIDv4(), URL: u})
}

software.URLs = softwareURLs

if err := p.db.Updates(&software).Error; err != nil {
return common.Error(fiber.StatusInternalServerError, "can't update Software", "db error")
}

return ctx.JSON(&software)
}

// DeleteSoftware deletes the software with the given ID.
func (p *Software) DeleteSoftware(ctx *fiber.Ctx) error {
var software models.Software

if err := p.db.Delete(&software, ctx.Params("id")).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.Error(fiber.StatusNotFound, "can't delete Software", "Software was not found")
}

return common.Error(fiber.StatusInternalServerError, "can't delete Software", "db error")
}

return ctx.SendStatus(fiber.StatusNoContent)
}
Loading