Skip to content

Commit

Permalink
Merge pull request #1064 from goplus/dev
Browse files Browse the repository at this point in the history
Release v1.5.2
  • Loading branch information
nighca authored Nov 1, 2024
2 parents 02856dc + 2200594 commit cc14a1d
Showing 169 changed files with 1,901 additions and 2,191 deletions.
5 changes: 5 additions & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
@@ -516,6 +516,7 @@ paths:
required:
- projectFullName
- name
- description
properties:
projectFullName:
$ref: "#/components/schemas/ProjectRelease/properties/projectFullName"
@@ -1065,6 +1066,10 @@ components:
$ref: "#/components/schemas/ProjectReleaseFullName"
nullable: true
description: Full name of the project release from which the project is remixed.
latestRelease:
$ref: "#/components/schemas/ProjectRelease"
nullable: true
description: Latest release of the project.
name:
type: string
examples:
4 changes: 1 addition & 3 deletions spx-backend/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module github.com/goplus/builder/spx-backend

go 1.21

toolchain go1.21.3
go 1.21.0

require (
github.com/go-sql-driver/mysql v1.8.1
19 changes: 17 additions & 2 deletions spx-backend/internal/controller/asset_test.go
Original file line number Diff line number Diff line change
@@ -258,17 +258,28 @@ func TestControllerCreateAsset(t *testing.T) {
dbMock.ExpectBegin()

dbMockStmt := ctrl.db.Session(&gorm.Session{DryRun: true, SkipDefaultTransaction: true}).
Create(&model.Asset{}).
Create(&model.Asset{
OwnerID: mAuthedUser.ID,
DisplayName: params.DisplayName,
Type: model.ParseAssetType(params.Type),
Category: params.Category,
FilesHash: params.FilesHash,
Visibility: model.ParseVisibility(params.Visibility),
}).
Statement
dbMockArgs := modeltest.ToDriverValueSlice(dbMockStmt.Vars...)
dbMockArgs[0] = sqlmock.AnyArg() // CreatedAt
dbMockArgs[1] = sqlmock.AnyArg() // UpdatedAt
dbMock.ExpectExec(regexp.QuoteMeta(dbMockStmt.SQL.String())).
WithArgs(dbMockArgs...).
WillReturnResult(sqlmock.NewResult(1, 1))

dbMock.ExpectCommit()

dbMockStmt = ctrl.db.Session(&gorm.Session{DryRun: true}).
First(&model.Asset{Model: model.Model{ID: 1}}).
Statement
dbMockArgs := modeltest.ToDriverValueSlice(dbMockStmt.Vars...)
dbMockArgs = modeltest.ToDriverValueSlice(dbMockStmt.Vars...)
dbMock.ExpectQuery(regexp.QuoteMeta(dbMockStmt.SQL.String())).
WithArgs(dbMockArgs...).
WillReturnRows(sqlmock.NewRows(assetDBColumns).AddRows(generateAssetDBRows(model.Asset{
@@ -417,6 +428,7 @@ func TestControllerListAssets(t *testing.T) {
dbMockStmt = ctrl.db.Session(&gorm.Session{DryRun: true}).
Where(ctrl.db.Where("asset.owner_id = ?", mAuthedUser.ID).Or("asset.visibility = ?", model.VisibilityPublic)).
Order("asset.created_at asc, asset.id").
Offset(params.Pagination.Offset()).
Limit(params.Pagination.Size).
Find(&[]model.Asset{}).
Statement
@@ -841,7 +853,10 @@ func TestControllerDeleteAsset(t *testing.T) {
dbMockStmt = ctrl.db.Session(&gorm.Session{DryRun: true, SkipDefaultTransaction: true}).
Delete(&model.Asset{Model: mAsset.Model}).
Statement
dbMockArgs = modeltest.ToDriverValueSlice(dbMockStmt.Vars...)
dbMockArgs[0] = sqlmock.AnyArg() // DeletedAt
dbMock.ExpectExec(regexp.QuoteMeta(dbMockStmt.SQL.String())).
WithArgs(dbMockArgs...).
WillReturnResult(sqlmock.NewResult(0, 1))

dbMock.ExpectCommit()
73 changes: 39 additions & 34 deletions spx-backend/internal/controller/project.go
Original file line number Diff line number Diff line change
@@ -19,19 +19,20 @@ import (
type ProjectDTO struct {
ModelDTO

Owner string `json:"owner"`
RemixedFrom string `json:"remixedFrom,omitempty"`
Name string `json:"name"`
Version int `json:"version"`
Files model.FileCollection `json:"files"`
Visibility string `json:"visibility"`
Description string `json:"description"`
Instructions string `json:"instructions"`
Thumbnail string `json:"thumbnail"`
ViewCount int64 `json:"viewCount"`
LikeCount int64 `json:"likeCount"`
ReleaseCount int64 `json:"releaseCount"`
RemixCount int64 `json:"remixCount"`
Owner string `json:"owner"`
RemixedFrom string `json:"remixedFrom,omitempty"`
LatestRelease *ProjectReleaseDTO `json:"latestRelease,omitempty"`
Name string `json:"name"`
Version int `json:"version"`
Files model.FileCollection `json:"files"`
Visibility string `json:"visibility"`
Description string `json:"description"`
Instructions string `json:"instructions"`
Thumbnail string `json:"thumbnail"`
ViewCount int64 `json:"viewCount"`
LikeCount int64 `json:"likeCount"`
ReleaseCount int64 `json:"releaseCount"`
RemixCount int64 `json:"remixCount"`
}

// toProjectDTO converts the model project to its DTO.
@@ -45,21 +46,27 @@ func toProjectDTO(mProject model.Project) ProjectDTO {
mProject.RemixedFromRelease.Name,
)
}
var latestRelease *ProjectReleaseDTO
if mProject.LatestRelease != nil {
lr := toProjectReleaseDTO(*mProject.LatestRelease)
latestRelease = &lr
}
return ProjectDTO{
ModelDTO: toModelDTO(mProject.Model),
Owner: mProject.Owner.Username,
RemixedFrom: remixedFrom,
Name: mProject.Name,
Version: mProject.Version,
Files: mProject.Files,
Visibility: mProject.Visibility.String(),
Description: mProject.Description,
Instructions: mProject.Instructions,
Thumbnail: mProject.Thumbnail,
ViewCount: mProject.ViewCount,
LikeCount: mProject.LikeCount,
ReleaseCount: mProject.ReleaseCount,
RemixCount: mProject.RemixCount,
ModelDTO: toModelDTO(mProject.Model),
Owner: mProject.Owner.Username,
RemixedFrom: remixedFrom,
LatestRelease: latestRelease,
Name: mProject.Name,
Version: mProject.Version,
Files: mProject.Files,
Visibility: mProject.Visibility.String(),
Description: mProject.Description,
Instructions: mProject.Instructions,
Thumbnail: mProject.Thumbnail,
ViewCount: mProject.ViewCount,
LikeCount: mProject.LikeCount,
ReleaseCount: mProject.ReleaseCount,
RemixCount: mProject.RemixCount,
}
}

@@ -77,6 +84,7 @@ func (ctrl *Controller) ensureProject(ctx context.Context, owner, name string, o
if err := ctrl.db.WithContext(ctx).
Preload("Owner").
Preload("RemixedFromRelease.Project.Owner").
Preload("LatestRelease.Project.Owner").
Joins("JOIN user ON user.id = project.owner_id").
Where("user.username = ?", owner).
Where("project.name = ?", name).
@@ -147,8 +155,6 @@ func (ctrl *Controller) CreateProject(ctx context.Context, params *CreateProject

var mRemixSourceProject model.Project
if err := ctrl.db.WithContext(ctx).
Preload("Owner").
Preload("RemixedFromRelease.Project.Owner").
Joins("JOIN user ON user.id = project.owner_id").
Where("user.username = ?", ownerUsername).
Where("project.name = ?", projectName).
@@ -233,6 +239,7 @@ func (ctrl *Controller) CreateProject(ctx context.Context, params *CreateProject
if err := ctrl.db.WithContext(ctx).
Preload("Owner").
Preload("RemixedFromRelease.Project.Owner").
Preload("LatestRelease.Project.Owner").
First(&mProject).
Error; err != nil {
return nil, fmt.Errorf("failed to get project: %w", err)
@@ -370,10 +377,7 @@ func (ctrl *Controller) ListProjects(ctx context.Context, params *ListProjectsPa
}
}

query := ctrl.db.WithContext(ctx).
Model(&model.Project{}).
Preload("Owner").
Preload("RemixedFromRelease.Project.Owner")
query := ctrl.db.WithContext(ctx).Model(&model.Project{})
if params.Owner != nil {
query = query.Joins("JOIN user ON user.id = project.owner_id").Where("user.username = ?", *params.Owner)
}
@@ -473,6 +477,7 @@ func (ctrl *Controller) ListProjects(ctx context.Context, params *ListProjectsPa
if err := query.
Preload("Owner").
Preload("RemixedFromRelease.Project.Owner").
Preload("LatestRelease.Project.Owner").
Offset(params.Pagination.Offset()).
Limit(params.Pagination.Size).
Find(&mProjects).
@@ -559,7 +564,7 @@ func (ctrl *Controller) UpdateProject(ctx context.Context, owner, name string, p
return nil
}

if queryResult := tx.Model(mProject).Omit("Owner", "RemixedFromRelease").Updates(updates); queryResult.Error != nil {
if queryResult := tx.Model(mProject).Omit("Owner", "RemixedFromRelease", "LatestRelease").Updates(updates); queryResult.Error != nil {
return queryResult.Error
} else if queryResult.RowsAffected == 0 {
return nil
20 changes: 14 additions & 6 deletions spx-backend/internal/controller/project_release.go
Original file line number Diff line number Diff line change
@@ -2,11 +2,13 @@ package controller

import (
"context"
"database/sql"
"fmt"
"regexp"

"github.com/goplus/builder/spx-backend/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)

// ProjectReleaseDTO is the DTO for project releases.
@@ -62,6 +64,9 @@ func (p *CreateProjectReleaseParams) Validate() (ok bool, msg string) {
if !projectReleaseNameRE.MatchString(p.Name) {
return false, "invalid projectReleaseName"
}
if p.Description == "" {
return false, "missing description"
}
return true, ""
}

@@ -76,9 +81,6 @@ func (ctrl *Controller) CreateProjectRelease(ctx context.Context, params *Create
return nil, err
}

if params.Description == "" {
params.Description = mProject.Description
}
if params.Thumbnail == "" {
params.Thumbnail = mProject.Thumbnail
}
@@ -90,14 +92,21 @@ func (ctrl *Controller) CreateProjectRelease(ctx context.Context, params *Create
Thumbnail: params.Thumbnail,
}
if err := ctrl.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(mProject).Error; err != nil {
return err
}

if err := tx.Create(&mProjectRelease).Error; err != nil {
return err
}

if err := tx.
Model(&mProject).
Omit("Owner", "RemixedFromRelease").
UpdateColumn("release_count", gorm.Expr("release_count + 1")).
Omit("Owner", "RemixedFromRelease", "LatestRelease").
Updates(map[string]any{
"latest_release_id": sql.NullInt64{Int64: mProjectRelease.ID, Valid: true},
"release_count": gorm.Expr("release_count + 1"),
}).
Error; err != nil {
return err
}
@@ -183,7 +192,6 @@ func (p *ListProjectReleasesParams) Validate() (ok bool, msg string) {
func (ctrl *Controller) ListProjectReleases(ctx context.Context, params *ListProjectReleasesParams) (*ByPage[ProjectReleaseDTO], error) {
query := ctrl.db.WithContext(ctx).
Model(&model.ProjectRelease{}).
Preload("Project.Owner").
Joins("JOIN project ON project.id = project_release.project_id").
Where("project.visibility = ?", model.VisibilityPublic)
if params.ProjectFullName != nil {
42 changes: 40 additions & 2 deletions spx-backend/internal/controller/project_release_test.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package controller

import (
"context"
"database/sql"
"fmt"
"regexp"
"testing"
@@ -12,6 +13,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)

func TestCreateProjectReleaseParams(t *testing.T) {
@@ -31,6 +33,7 @@ func TestCreateProjectReleaseParams(t *testing.T) {
params := &CreateProjectReleaseParams{
ProjectFullName: "invalid/project/name",
Name: "v1.0.0",
Description: "First release",
}
ok, msg := params.Validate()
assert.False(t, ok)
@@ -41,11 +44,22 @@ func TestCreateProjectReleaseParams(t *testing.T) {
params := &CreateProjectReleaseParams{
ProjectFullName: "user/project",
Name: "invalid-version",
Description: "First release",
}
ok, msg := params.Validate()
assert.False(t, ok)
assert.Equal(t, "invalid projectReleaseName", msg)
})

t.Run("MissingDescription", func(t *testing.T) {
params := &CreateProjectReleaseParams{
ProjectFullName: "user/project",
Name: "v1.0.0",
}
ok, msg := params.Validate()
assert.False(t, ok)
assert.Equal(t, "missing description", msg)
})
}

func TestControllerCreateProjectRelease(t *testing.T) {
@@ -110,17 +124,41 @@ func TestControllerCreateProjectRelease(t *testing.T) {

dbMock.ExpectBegin()

dbMockStmt = ctrl.db.Session(&gorm.Session{DryRun: true}).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&mProject).
Statement
dbMockArgs = modeltest.ToDriverValueSlice(dbMockStmt.Vars...)
dbMock.ExpectQuery(regexp.QuoteMeta(dbMockStmt.SQL.String())).
WithArgs(dbMockArgs...).
WillReturnRows(sqlmock.NewRows(projectDBColumns).AddRows(generateProjectDBRows(mProject)...))

dbMockStmt = ctrl.db.Session(&gorm.Session{DryRun: true, SkipDefaultTransaction: true}).
Create(&model.ProjectRelease{}).
Create(&model.ProjectRelease{
ProjectID: mProject.ID,
Name: params.Name,
Description: params.Description,
Thumbnail: params.Thumbnail,
}).
Statement
dbMockArgs = modeltest.ToDriverValueSlice(dbMockStmt.Vars...)
dbMockArgs[0] = sqlmock.AnyArg() // CreatedAt
dbMockArgs[1] = sqlmock.AnyArg() // UpdatedAt
dbMock.ExpectExec(regexp.QuoteMeta(dbMockStmt.SQL.String())).
WithArgs(dbMockArgs...).
WillReturnResult(sqlmock.NewResult(1, 1))

dbMockStmt = ctrl.db.Session(&gorm.Session{DryRun: true, SkipDefaultTransaction: true}).
Model(&model.Project{Model: mProject.Model}).
UpdateColumn("release_count", gorm.Expr("release_count + 1")).
Updates(map[string]any{
"latest_release_id": sql.NullInt64{Int64: 1, Valid: true},
"release_count": gorm.Expr("release_count + 1"),
"updated_at": sqlmock.AnyArg(),
}).
Statement
dbMockArgs = modeltest.ToDriverValueSlice(dbMockStmt.Vars...)
dbMock.ExpectExec(regexp.QuoteMeta(dbMockStmt.SQL.String())).
WithArgs(dbMockArgs...).
WillReturnResult(sqlmock.NewResult(0, 1))

dbMock.ExpectCommit()
Loading

0 comments on commit cc14a1d

Please sign in to comment.