diff options
Diffstat (limited to '')
43 files changed, 3521 insertions, 0 deletions
diff --git a/services/f3/driver/asset.go b/services/f3/driver/asset.go new file mode 100644 index 0000000..6759cc6 --- /dev/null +++ b/services/f3/driver/asset.go @@ -0,0 +1,171 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "os" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/services/attachment" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" + "github.com/google/uuid" +) + +var _ f3_tree.ForgeDriverInterface = &issue{} + +type asset struct { + common + + forgejoAsset *repo_model.Attachment + sha string + contentType string + downloadFunc f3.DownloadFuncType +} + +func (o *asset) SetNative(asset any) { + o.forgejoAsset = asset.(*repo_model.Attachment) +} + +func (o *asset) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoAsset.ID) +} + +func (o *asset) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *asset) ToFormat() f3.Interface { + if o.forgejoAsset == nil { + return o.NewFormat() + } + + return &f3.ReleaseAsset{ + Common: f3.NewCommon(o.GetNativeID()), + Name: o.forgejoAsset.Name, + ContentType: o.contentType, + Size: o.forgejoAsset.Size, + DownloadCount: o.forgejoAsset.DownloadCount, + Created: o.forgejoAsset.CreatedUnix.AsTime(), + SHA256: o.sha, + DownloadURL: o.forgejoAsset.DownloadURL(), + DownloadFunc: o.downloadFunc, + } +} + +func (o *asset) FromFormat(content f3.Interface) { + asset := content.(*f3.ReleaseAsset) + o.forgejoAsset = &repo_model.Attachment{ + ID: f3_util.ParseInt(asset.GetID()), + Name: asset.Name, + Size: asset.Size, + DownloadCount: asset.DownloadCount, + CreatedUnix: timeutil.TimeStamp(asset.Created.Unix()), + CustomDownloadURL: asset.DownloadURL, + } + o.contentType = asset.ContentType + o.sha = asset.SHA256 + o.downloadFunc = asset.DownloadFunc +} + +func (o *asset) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + id := node.GetID().Int64() + + asset, err := repo_model.GetAttachmentByID(ctx, id) + if repo_model.IsErrAttachmentNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("asset %v %w", id, err)) + } + + o.forgejoAsset = asset + + path := o.forgejoAsset.RelativePath() + + { + f, err := storage.Attachments.Open(path) + if err != nil { + panic(err) + } + hasher := sha256.New() + if _, err := io.Copy(hasher, f); err != nil { + panic(fmt.Errorf("io.Copy to hasher: %v", err)) + } + o.sha = hex.EncodeToString(hasher.Sum(nil)) + } + + o.downloadFunc = func() io.ReadCloser { + o.Trace("download %s from copy stored in temporary file %s", o.forgejoAsset.DownloadURL, path) + f, err := os.Open(path) + if err != nil { + panic(err) + } + return f + } + return true +} + +func (o *asset) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoAsset.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoAsset.ID).Cols("name").Update(o.forgejoAsset); err != nil { + panic(fmt.Errorf("UpdateAssetCols: %v %v", o.forgejoAsset, err)) + } +} + +func (o *asset) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + uploader, err := user_model.GetAdminUser(ctx) + if err != nil { + panic(fmt.Errorf("GetAdminUser %w", err)) + } + + o.forgejoAsset.UploaderID = uploader.ID + o.forgejoAsset.RepoID = f3_tree.GetProjectID(o.GetNode()) + o.forgejoAsset.ReleaseID = f3_tree.GetReleaseID(o.GetNode()) + o.forgejoAsset.UUID = uuid.New().String() + + download := o.downloadFunc() + defer download.Close() + + _, err = attachment.NewAttachment(ctx, o.forgejoAsset, download, o.forgejoAsset.Size) + if err != nil { + panic(err) + } + + o.Trace("asset created %d", o.forgejoAsset.ID) + return generic.NewNodeID(o.forgejoAsset.ID) +} + +func (o *asset) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + if err := repo_model.DeleteAttachment(ctx, o.forgejoAsset, true); err != nil { + panic(err) + } +} + +func newAsset() generic.NodeDriverInterface { + return &asset{} +} diff --git a/services/f3/driver/assets.go b/services/f3/driver/assets.go new file mode 100644 index 0000000..88a3979 --- /dev/null +++ b/services/f3/driver/assets.go @@ -0,0 +1,42 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + repo_model "code.gitea.io/gitea/models/repo" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type assets struct { + container +} + +func (o *assets) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + if page > 1 { + return generic.NewChildrenSlice(0) + } + + releaseID := f3_tree.GetReleaseID(o.GetNode()) + + release, err := repo_model.GetReleaseByID(ctx, releaseID) + if err != nil { + panic(fmt.Errorf("GetReleaseByID %v %w", releaseID, err)) + } + + if err := release.LoadAttributes(ctx); err != nil { + panic(fmt.Errorf("error while listing assets: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(release.Attachments...)...) +} + +func newAssets() generic.NodeDriverInterface { + return &assets{} +} diff --git a/services/f3/driver/comment.go b/services/f3/driver/comment.go new file mode 100644 index 0000000..0c10fd7 --- /dev/null +++ b/services/f3/driver/comment.go @@ -0,0 +1,122 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/timeutil" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &comment{} + +type comment struct { + common + + forgejoComment *issues_model.Comment +} + +func (o *comment) SetNative(comment any) { + o.forgejoComment = comment.(*issues_model.Comment) +} + +func (o *comment) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoComment.ID) +} + +func (o *comment) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *comment) ToFormat() f3.Interface { + if o.forgejoComment == nil { + return o.NewFormat() + } + return &f3.Comment{ + Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoComment.ID)), + PosterID: f3_tree.NewUserReference(o.forgejoComment.Poster.ID), + Content: o.forgejoComment.Content, + Created: o.forgejoComment.CreatedUnix.AsTime(), + Updated: o.forgejoComment.UpdatedUnix.AsTime(), + } +} + +func (o *comment) FromFormat(content f3.Interface) { + comment := content.(*f3.Comment) + + o.forgejoComment = &issues_model.Comment{ + ID: f3_util.ParseInt(comment.GetID()), + PosterID: comment.PosterID.GetIDAsInt(), + Poster: &user_model.User{ + ID: comment.PosterID.GetIDAsInt(), + }, + Content: comment.Content, + CreatedUnix: timeutil.TimeStamp(comment.Created.Unix()), + UpdatedUnix: timeutil.TimeStamp(comment.Updated.Unix()), + } +} + +func (o *comment) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + id := node.GetID().Int64() + + comment, err := issues_model.GetCommentByID(ctx, id) + if issues_model.IsErrCommentNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("comment %v %w", id, err)) + } + if err := comment.LoadPoster(ctx); err != nil { + panic(fmt.Errorf("LoadPoster %v %w", *comment, err)) + } + o.forgejoComment = comment + return true +} + +func (o *comment) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoComment.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoComment.ID).Cols("content").Update(o.forgejoComment); err != nil { + panic(fmt.Errorf("UpdateCommentCols: %v %v", o.forgejoComment, err)) + } +} + +func (o *comment) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + sess := db.GetEngine(ctx) + + if _, err := sess.NoAutoTime().Insert(o.forgejoComment); err != nil { + panic(err) + } + o.Trace("comment created %d", o.forgejoComment.ID) + return generic.NewNodeID(o.forgejoComment.ID) +} + +func (o *comment) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + if err := issues_model.DeleteComment(ctx, o.forgejoComment); err != nil { + panic(err) + } +} + +func newComment() generic.NodeDriverInterface { + return &comment{} +} diff --git a/services/f3/driver/comments.go b/services/f3/driver/comments.go new file mode 100644 index 0000000..eb79b74 --- /dev/null +++ b/services/f3/driver/comments.go @@ -0,0 +1,49 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type comments struct { + container +} + +func (o *comments) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + project := f3_tree.GetProjectID(o.GetNode()) + commentable := f3_tree.GetCommentableID(o.GetNode()) + + issue, err := issues_model.GetIssueByIndex(ctx, project, commentable) + if err != nil { + panic(fmt.Errorf("GetIssueByIndex %v %w", commentable, err)) + } + + sess := db.GetEngine(ctx). + Table("comment"). + Where("`issue_id` = ? AND `type` = ?", issue.ID, issues_model.CommentTypeComment) + if page != 0 { + sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize}) + } + forgejoComments := make([]*issues_model.Comment, 0, pageSize) + if err := sess.Find(&forgejoComments); err != nil { + panic(fmt.Errorf("error while listing comments: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoComments...)...) +} + +func newComments() generic.NodeDriverInterface { + return &comments{} +} diff --git a/services/f3/driver/common.go b/services/f3/driver/common.go new file mode 100644 index 0000000..104f91c --- /dev/null +++ b/services/f3/driver/common.go @@ -0,0 +1,48 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type common struct { + generic.NullDriver +} + +func (o *common) GetHelper() any { + panic("not implemented") +} + +func (o *common) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + return generic.NewChildrenSlice(0) +} + +func (o *common) GetNativeID() string { + return "" +} + +func (o *common) SetNative(native any) { +} + +func (o *common) getTree() generic.TreeInterface { + return o.GetNode().GetTree() +} + +func (o *common) getPageSize() int { + return o.getTreeDriver().GetPageSize() +} + +func (o *common) getKind() generic.Kind { + return o.GetNode().GetKind() +} + +func (o *common) getTreeDriver() *treeDriver { + return o.GetTreeDriver().(*treeDriver) +} + +func (o *common) IsNull() bool { return false } diff --git a/services/f3/driver/container.go b/services/f3/driver/container.go new file mode 100644 index 0000000..1530444 --- /dev/null +++ b/services/f3/driver/container.go @@ -0,0 +1,43 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type container struct { + common +} + +func (o *container) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *container) ToFormat() f3.Interface { + return o.NewFormat() +} + +func (o *container) FromFormat(content f3.Interface) { +} + +func (o *container) Get(context.Context) bool { return true } + +func (o *container) Put(ctx context.Context) generic.NodeID { + return o.upsert(ctx) +} + +func (o *container) Patch(ctx context.Context) { + o.upsert(ctx) +} + +func (o *container) upsert(context.Context) generic.NodeID { + return generic.NewNodeID(o.getKind()) +} diff --git a/services/f3/driver/forge.go b/services/f3/driver/forge.go new file mode 100644 index 0000000..a4bcf61 --- /dev/null +++ b/services/f3/driver/forge.go @@ -0,0 +1,64 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + user_model "code.gitea.io/gitea/models/user" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + "code.forgejo.org/f3/gof3/v3/util" +) + +type forge struct { + generic.NullDriver + + ownersKind map[string]generic.Kind +} + +func newForge() generic.NodeDriverInterface { + return &forge{ + ownersKind: make(map[string]generic.Kind), + } +} + +func (o *forge) getOwnersKind(ctx context.Context, id string) generic.Kind { + kind, ok := o.ownersKind[id] + if !ok { + user, err := user_model.GetUserByID(ctx, util.ParseInt(id)) + if err != nil { + panic(fmt.Errorf("user_repo.GetUserByID: %w", err)) + } + kind = f3_tree.KindUsers + if user.IsOrganization() { + kind = f3_tree.KindOrganization + } + o.ownersKind[id] = kind + } + return kind +} + +func (o *forge) getOwnersPath(ctx context.Context, id string) f3_tree.Path { + return f3_tree.NewPathFromString("/").SetForge().SetOwners(o.getOwnersKind(ctx, id)) +} + +func (o *forge) Equals(context.Context, generic.NodeInterface) bool { return true } +func (o *forge) Get(context.Context) bool { return true } +func (o *forge) Put(context.Context) generic.NodeID { return generic.NewNodeID("forge") } +func (o *forge) Patch(context.Context) {} +func (o *forge) Delete(context.Context) {} +func (o *forge) NewFormat() f3.Interface { return &f3.Forge{} } +func (o *forge) FromFormat(f3.Interface) {} + +func (o *forge) ToFormat() f3.Interface { + return &f3.Forge{ + Common: f3.NewCommon("forge"), + URL: o.String(), + } +} diff --git a/services/f3/driver/issue.go b/services/f3/driver/issue.go new file mode 100644 index 0000000..7f1614d --- /dev/null +++ b/services/f3/driver/issue.go @@ -0,0 +1,238 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/timeutil" + issue_service "code.gitea.io/gitea/services/issue" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &issue{} + +type issue struct { + common + + forgejoIssue *issues_model.Issue +} + +func (o *issue) SetNative(issue any) { + o.forgejoIssue = issue.(*issues_model.Issue) +} + +func (o *issue) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoIssue.Index) +} + +func (o *issue) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *issue) ToFormat() f3.Interface { + if o.forgejoIssue == nil { + return o.NewFormat() + } + + var milestone *f3.Reference + if o.forgejoIssue.Milestone != nil { + milestone = f3_tree.NewIssueMilestoneReference(o.forgejoIssue.Milestone.ID) + } + + assignees := make([]*f3.Reference, 0, len(o.forgejoIssue.Assignees)) + for _, assignee := range o.forgejoIssue.Assignees { + assignees = append(assignees, f3_tree.NewUserReference(assignee.ID)) + } + + labels := make([]*f3.Reference, 0, len(o.forgejoIssue.Labels)) + for _, label := range o.forgejoIssue.Labels { + labels = append(labels, f3_tree.NewIssueLabelReference(label.ID)) + } + + return &f3.Issue{ + Title: o.forgejoIssue.Title, + Common: f3.NewCommon(o.GetNativeID()), + PosterID: f3_tree.NewUserReference(o.forgejoIssue.Poster.ID), + Assignees: assignees, + Labels: labels, + Content: o.forgejoIssue.Content, + Milestone: milestone, + State: string(o.forgejoIssue.State()), + Created: o.forgejoIssue.CreatedUnix.AsTime(), + Updated: o.forgejoIssue.UpdatedUnix.AsTime(), + Closed: o.forgejoIssue.ClosedUnix.AsTimePtr(), + IsLocked: o.forgejoIssue.IsLocked, + } +} + +func (o *issue) FromFormat(content f3.Interface) { + issue := content.(*f3.Issue) + var milestone *issues_model.Milestone + if issue.Milestone != nil { + milestone = &issues_model.Milestone{ + ID: issue.Milestone.GetIDAsInt(), + } + } + o.forgejoIssue = &issues_model.Issue{ + Title: issue.Title, + Index: f3_util.ParseInt(issue.GetID()), + PosterID: issue.PosterID.GetIDAsInt(), + Poster: &user_model.User{ + ID: issue.PosterID.GetIDAsInt(), + }, + Content: issue.Content, + Milestone: milestone, + IsClosed: issue.State == f3.IssueStateClosed, + CreatedUnix: timeutil.TimeStamp(issue.Created.Unix()), + UpdatedUnix: timeutil.TimeStamp(issue.Updated.Unix()), + IsLocked: issue.IsLocked, + } + + assignees := make([]*user_model.User, 0, len(issue.Assignees)) + for _, assignee := range issue.Assignees { + assignees = append(assignees, &user_model.User{ID: assignee.GetIDAsInt()}) + } + o.forgejoIssue.Assignees = assignees + + labels := make([]*issues_model.Label, 0, len(issue.Labels)) + for _, label := range issue.Labels { + labels = append(labels, &issues_model.Label{ID: label.GetIDAsInt()}) + } + o.forgejoIssue.Labels = labels + + if issue.Closed != nil { + o.forgejoIssue.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix()) + } +} + +func (o *issue) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + id := node.GetID().Int64() + + issue, err := issues_model.GetIssueByIndex(ctx, project, id) + if issues_model.IsErrIssueNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("issue %v %w", id, err)) + } + if err := issue.LoadAttributes(ctx); err != nil { + panic(err) + } + + o.forgejoIssue = issue + return true +} + +func (o *issue) Patch(ctx context.Context) { + node := o.GetNode() + project := f3_tree.GetProjectID(o.GetNode()) + id := node.GetID().Int64() + o.Trace("repo_id = %d, index = %d", project, id) + if _, err := db.GetEngine(ctx).Where("`repo_id` = ? AND `index` = ?", project, id).Cols("name", "content", "is_closed").Update(o.forgejoIssue); err != nil { + panic(fmt.Errorf("%v %v", o.forgejoIssue, err)) + } +} + +func (o *issue) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + o.forgejoIssue.RepoID = f3_tree.GetProjectID(o.GetNode()) + makeLabels := func(issueID int64) []issues_model.IssueLabel { + labels := make([]issues_model.IssueLabel, 0, len(o.forgejoIssue.Labels)) + for _, label := range o.forgejoIssue.Labels { + o.Trace("%d with label %d", issueID, label.ID) + labels = append(labels, issues_model.IssueLabel{ + IssueID: issueID, + LabelID: label.ID, + }) + } + return labels + } + + idx, err := db.GetNextResourceIndex(ctx, "issue_index", o.forgejoIssue.RepoID) + if err != nil { + panic(fmt.Errorf("generate issue index failed: %w", err)) + } + o.forgejoIssue.Index = idx + + sess := db.GetEngine(ctx) + + if _, err = sess.NoAutoTime().Insert(o.forgejoIssue); err != nil { + panic(err) + } + + labels := makeLabels(o.forgejoIssue.ID) + if len(labels) > 0 { + if _, err := sess.Insert(labels); err != nil { + panic(err) + } + } + + makeAssignees := func(issueID int64) []issues_model.IssueAssignees { + assignees := make([]issues_model.IssueAssignees, 0, len(o.forgejoIssue.Assignees)) + for _, assignee := range o.forgejoIssue.Assignees { + o.Trace("%d with assignee %d", issueID, assignee.ID) + assignees = append(assignees, issues_model.IssueAssignees{ + IssueID: issueID, + AssigneeID: assignee.ID, + }) + } + return assignees + } + + assignees := makeAssignees(o.forgejoIssue.ID) + if len(assignees) > 0 { + if _, err := sess.Insert(assignees); err != nil { + panic(err) + } + } + + o.Trace("issue created %d/%d", o.forgejoIssue.ID, o.forgejoIssue.Index) + return generic.NewNodeID(o.forgejoIssue.Index) +} + +func (o *issue) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + owner := f3_tree.GetOwnerName(o.GetNode()) + project := f3_tree.GetProjectName(o.GetNode()) + repoPath := repo_model.RepoPath(owner, project) + gitRepo, err := git.OpenRepository(ctx, repoPath) + if err != nil { + panic(err) + } + defer gitRepo.Close() + + doer, err := user_model.GetAdminUser(ctx) + if err != nil { + panic(fmt.Errorf("GetAdminUser %w", err)) + } + + if err := issue_service.DeleteIssue(ctx, doer, gitRepo, o.forgejoIssue); err != nil { + panic(err) + } +} + +func newIssue() generic.NodeDriverInterface { + return &issue{} +} diff --git a/services/f3/driver/issues.go b/services/f3/driver/issues.go new file mode 100644 index 0000000..3a5a64e --- /dev/null +++ b/services/f3/driver/issues.go @@ -0,0 +1,40 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type issues struct { + container +} + +func (o *issues) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + project := f3_tree.GetProjectID(o.GetNode()) + + forgejoIssues, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{ + Paginator: &db.ListOptions{Page: page, PageSize: pageSize}, + RepoIDs: []int64{project}, + }) + if err != nil { + panic(fmt.Errorf("error while listing issues: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoIssues...)...) +} + +func newIssues() generic.NodeDriverInterface { + return &issues{} +} diff --git a/services/f3/driver/label.go b/services/f3/driver/label.go new file mode 100644 index 0000000..6d1fcaa --- /dev/null +++ b/services/f3/driver/label.go @@ -0,0 +1,113 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &label{} + +type label struct { + common + + forgejoLabel *issues_model.Label +} + +func (o *label) SetNative(label any) { + o.forgejoLabel = label.(*issues_model.Label) +} + +func (o *label) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoLabel.ID) +} + +func (o *label) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *label) ToFormat() f3.Interface { + if o.forgejoLabel == nil { + return o.NewFormat() + } + return &f3.Label{ + Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoLabel.ID)), + Name: o.forgejoLabel.Name, + Color: o.forgejoLabel.Color, + Description: o.forgejoLabel.Description, + } +} + +func (o *label) FromFormat(content f3.Interface) { + label := content.(*f3.Label) + o.forgejoLabel = &issues_model.Label{ + ID: f3_util.ParseInt(label.GetID()), + Name: label.Name, + Description: label.Description, + Color: label.Color, + } +} + +func (o *label) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + id := node.GetID().Int64() + + label, err := issues_model.GetLabelInRepoByID(ctx, project, id) + if issues_model.IsErrRepoLabelNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("label %v %w", id, err)) + } + o.forgejoLabel = label + return true +} + +func (o *label) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoLabel.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoLabel.ID).Cols("name", "description", "color").Update(o.forgejoLabel); err != nil { + panic(fmt.Errorf("UpdateLabelCols: %v %v", o.forgejoLabel, err)) + } +} + +func (o *label) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + o.forgejoLabel.RepoID = f3_tree.GetProjectID(o.GetNode()) + if err := issues_model.NewLabel(ctx, o.forgejoLabel); err != nil { + panic(err) + } + o.Trace("label created %d", o.forgejoLabel.ID) + return generic.NewNodeID(o.forgejoLabel.ID) +} + +func (o *label) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + + if err := issues_model.DeleteLabel(ctx, project, o.forgejoLabel.ID); err != nil { + panic(err) + } +} + +func newLabel() generic.NodeDriverInterface { + return &label{} +} diff --git a/services/f3/driver/labels.go b/services/f3/driver/labels.go new file mode 100644 index 0000000..03f986b --- /dev/null +++ b/services/f3/driver/labels.go @@ -0,0 +1,37 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type labels struct { + container +} + +func (o *labels) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + project := f3_tree.GetProjectID(o.GetNode()) + + forgejoLabels, err := issues_model.GetLabelsByRepoID(ctx, project, "", db.ListOptions{Page: page, PageSize: pageSize}) + if err != nil { + panic(fmt.Errorf("error while listing labels: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoLabels...)...) +} + +func newLabels() generic.NodeDriverInterface { + return &labels{} +} diff --git a/services/f3/driver/main.go b/services/f3/driver/main.go new file mode 100644 index 0000000..825d456 --- /dev/null +++ b/services/f3/driver/main.go @@ -0,0 +1,17 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + driver_options "code.gitea.io/gitea/services/f3/driver/options" + + "code.forgejo.org/f3/gof3/v3/options" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" +) + +func init() { + f3_tree.RegisterForgeFactory(driver_options.Name, newTreeDriver) + options.RegisterFactory(driver_options.Name, newOptions) +} diff --git a/services/f3/driver/main_test.go b/services/f3/driver/main_test.go new file mode 100644 index 0000000..8505b69 --- /dev/null +++ b/services/f3/driver/main_test.go @@ -0,0 +1,30 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" + driver_options "code.gitea.io/gitea/services/f3/driver/options" + + _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" + _ "code.gitea.io/gitea/models/perm/access" + _ "code.gitea.io/gitea/services/f3/driver/tests" + + tests_f3 "code.forgejo.org/f3/gof3/v3/tree/tests/f3" + "github.com/stretchr/testify/require" +) + +func TestF3(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + tests_f3.ForgeCompliance(t, driver_options.Name) +} + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} diff --git a/services/f3/driver/milestone.go b/services/f3/driver/milestone.go new file mode 100644 index 0000000..222407f --- /dev/null +++ b/services/f3/driver/milestone.go @@ -0,0 +1,150 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + "time" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &milestone{} + +type milestone struct { + common + + forgejoMilestone *issues_model.Milestone +} + +func (o *milestone) SetNative(milestone any) { + o.forgejoMilestone = milestone.(*issues_model.Milestone) +} + +func (o *milestone) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoMilestone.ID) +} + +func (o *milestone) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *milestone) ToFormat() f3.Interface { + if o.forgejoMilestone == nil { + return o.NewFormat() + } + return &f3.Milestone{ + Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoMilestone.ID)), + Title: o.forgejoMilestone.Name, + Description: o.forgejoMilestone.Content, + Created: o.forgejoMilestone.CreatedUnix.AsTime(), + Updated: o.forgejoMilestone.UpdatedUnix.AsTimePtr(), + Deadline: o.forgejoMilestone.DeadlineUnix.AsTimePtr(), + State: string(o.forgejoMilestone.State()), + } +} + +func (o *milestone) FromFormat(content f3.Interface) { + milestone := content.(*f3.Milestone) + + var deadline timeutil.TimeStamp + if milestone.Deadline != nil { + deadline = timeutil.TimeStamp(milestone.Deadline.Unix()) + } + if deadline == 0 { + deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix()) + } + + var closed timeutil.TimeStamp + if milestone.Closed != nil { + closed = timeutil.TimeStamp(milestone.Closed.Unix()) + } + + if milestone.Created.IsZero() { + if milestone.Updated != nil { + milestone.Created = *milestone.Updated + } else if milestone.Deadline != nil { + milestone.Created = *milestone.Deadline + } else { + milestone.Created = time.Now() + } + } + if milestone.Updated == nil || milestone.Updated.IsZero() { + milestone.Updated = &milestone.Created + } + + o.forgejoMilestone = &issues_model.Milestone{ + ID: f3_util.ParseInt(milestone.GetID()), + Name: milestone.Title, + Content: milestone.Description, + IsClosed: milestone.State == f3.MilestoneStateClosed, + CreatedUnix: timeutil.TimeStamp(milestone.Created.Unix()), + UpdatedUnix: timeutil.TimeStamp(milestone.Updated.Unix()), + ClosedDateUnix: closed, + DeadlineUnix: deadline, + } +} + +func (o *milestone) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + id := node.GetID().Int64() + + milestone, err := issues_model.GetMilestoneByRepoID(ctx, project, id) + if issues_model.IsErrMilestoneNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("milestone %v %w", id, err)) + } + o.forgejoMilestone = milestone + return true +} + +func (o *milestone) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoMilestone.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoMilestone.ID).Cols("name", "description").Update(o.forgejoMilestone); err != nil { + panic(fmt.Errorf("UpdateMilestoneCols: %v %v", o.forgejoMilestone, err)) + } +} + +func (o *milestone) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + o.forgejoMilestone.RepoID = f3_tree.GetProjectID(o.GetNode()) + if err := issues_model.NewMilestone(ctx, o.forgejoMilestone); err != nil { + panic(err) + } + o.Trace("milestone created %d", o.forgejoMilestone.ID) + return generic.NewNodeID(o.forgejoMilestone.ID) +} + +func (o *milestone) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + + if err := issues_model.DeleteMilestoneByRepoID(ctx, project, o.forgejoMilestone.ID); err != nil { + panic(err) + } +} + +func newMilestone() generic.NodeDriverInterface { + return &milestone{} +} diff --git a/services/f3/driver/milestones.go b/services/f3/driver/milestones.go new file mode 100644 index 0000000..c816903 --- /dev/null +++ b/services/f3/driver/milestones.go @@ -0,0 +1,40 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type milestones struct { + container +} + +func (o *milestones) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + project := f3_tree.GetProjectID(o.GetNode()) + + forgejoMilestones, err := db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ + ListOptions: db.ListOptions{Page: page, PageSize: pageSize}, + RepoID: project, + }) + if err != nil { + panic(fmt.Errorf("error while listing milestones: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoMilestones...)...) +} + +func newMilestones() generic.NodeDriverInterface { + return &milestones{} +} diff --git a/services/f3/driver/options.go b/services/f3/driver/options.go new file mode 100644 index 0000000..abc5015 --- /dev/null +++ b/services/f3/driver/options.go @@ -0,0 +1,20 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "net/http" + + driver_options "code.gitea.io/gitea/services/f3/driver/options" + + "code.forgejo.org/f3/gof3/v3/options" +) + +func newOptions() options.Interface { + o := &driver_options.Options{} + o.SetName(driver_options.Name) + o.SetNewMigrationHTTPClient(func() *http.Client { return &http.Client{} }) + return o +} diff --git a/services/f3/driver/options/name.go b/services/f3/driver/options/name.go new file mode 100644 index 0000000..9922d11 --- /dev/null +++ b/services/f3/driver/options/name.go @@ -0,0 +1,7 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package options + +const Name = "internal_forgejo" diff --git a/services/f3/driver/options/options.go b/services/f3/driver/options/options.go new file mode 100644 index 0000000..ee9fdd6 --- /dev/null +++ b/services/f3/driver/options/options.go @@ -0,0 +1,31 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package options + +import ( + "net/http" + + "code.forgejo.org/f3/gof3/v3/options" + "code.forgejo.org/f3/gof3/v3/options/cli" + "code.forgejo.org/f3/gof3/v3/options/logger" +) + +type NewMigrationHTTPClientFun func() *http.Client + +type Options struct { + options.Options + logger.OptionsLogger + cli.OptionsCLI + + NewMigrationHTTPClient NewMigrationHTTPClientFun +} + +func (o *Options) GetNewMigrationHTTPClient() NewMigrationHTTPClientFun { + return o.NewMigrationHTTPClient +} + +func (o *Options) SetNewMigrationHTTPClient(fun NewMigrationHTTPClientFun) { + o.NewMigrationHTTPClient = fun +} diff --git a/services/f3/driver/organization.go b/services/f3/driver/organization.go new file mode 100644 index 0000000..76b2400 --- /dev/null +++ b/services/f3/driver/organization.go @@ -0,0 +1,111 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + org_model "code.gitea.io/gitea/models/organization" + user_model "code.gitea.io/gitea/models/user" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &organization{} + +type organization struct { + common + + forgejoOrganization *org_model.Organization +} + +func (o *organization) SetNative(organization any) { + o.forgejoOrganization = organization.(*org_model.Organization) +} + +func (o *organization) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoOrganization.ID) +} + +func (o *organization) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *organization) ToFormat() f3.Interface { + if o.forgejoOrganization == nil { + return o.NewFormat() + } + return &f3.Organization{ + Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoOrganization.ID)), + Name: o.forgejoOrganization.Name, + FullName: o.forgejoOrganization.FullName, + } +} + +func (o *organization) FromFormat(content f3.Interface) { + organization := content.(*f3.Organization) + o.forgejoOrganization = &org_model.Organization{ + ID: f3_util.ParseInt(organization.GetID()), + Name: organization.Name, + FullName: organization.FullName, + } +} + +func (o *organization) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + id := node.GetID().Int64() + organization, err := org_model.GetOrgByID(ctx, id) + if user_model.IsErrUserNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("organization %v %w", id, err)) + } + o.forgejoOrganization = organization + return true +} + +func (o *organization) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoOrganization.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoOrganization.ID).Cols("full_name").Update(o.forgejoOrganization); err != nil { + panic(fmt.Errorf("UpdateOrganizationCols: %v %v", o.forgejoOrganization, err)) + } +} + +func (o *organization) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + doer, err := user_model.GetAdminUser(ctx) + if err != nil { + panic(fmt.Errorf("GetAdminUser %w", err)) + } + err = org_model.CreateOrganization(ctx, o.forgejoOrganization, doer) + if err != nil { + panic(err) + } + + return generic.NewNodeID(o.forgejoOrganization.ID) +} + +func (o *organization) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + if err := org_model.DeleteOrganization(ctx, o.forgejoOrganization); err != nil { + panic(err) + } +} + +func newOrganization() generic.NodeDriverInterface { + return &organization{} +} diff --git a/services/f3/driver/organizations.go b/services/f3/driver/organizations.go new file mode 100644 index 0000000..98c4c14 --- /dev/null +++ b/services/f3/driver/organizations.go @@ -0,0 +1,50 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + org_model "code.gitea.io/gitea/models/organization" + user_model "code.gitea.io/gitea/models/user" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type organizations struct { + container +} + +func (o *organizations) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + sess := db.GetEngine(ctx) + if page != 0 { + sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: o.getPageSize()}) + } + sess = sess.Select("`user`.*"). + Where("`type`=?", user_model.UserTypeOrganization) + organizations := make([]*org_model.Organization, 0, o.getPageSize()) + + if err := sess.Find(&organizations); err != nil { + panic(fmt.Errorf("error while listing organizations: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(organizations...)...) +} + +func (o *organizations) GetIDFromName(ctx context.Context, name string) generic.NodeID { + organization, err := org_model.GetOrgByName(ctx, name) + if err != nil { + panic(fmt.Errorf("GetOrganizationByName: %v", err)) + } + + return generic.NewNodeID(organization.ID) +} + +func newOrganizations() generic.NodeDriverInterface { + return &organizations{} +} diff --git a/services/f3/driver/project.go b/services/f3/driver/project.go new file mode 100644 index 0000000..c2a2df3 --- /dev/null +++ b/services/f3/driver/project.go @@ -0,0 +1,188 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + "strings" + + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + repo_service "code.gitea.io/gitea/services/repository" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &project{} + +type project struct { + common + + forgejoProject *repo_model.Repository + forked *f3.Reference +} + +func (o *project) SetNative(project any) { + o.forgejoProject = project.(*repo_model.Repository) +} + +func (o *project) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoProject.ID) +} + +func (o *project) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *project) setForkedReference(ctx context.Context) { + if !o.forgejoProject.IsFork { + return + } + + if err := o.forgejoProject.GetBaseRepo(ctx); err != nil { + panic(fmt.Errorf("GetBaseRepo %v %w", o.forgejoProject, err)) + } + forkParent := o.forgejoProject.BaseRepo + if err := forkParent.LoadOwner(ctx); err != nil { + panic(fmt.Errorf("LoadOwner %v %w", forkParent, err)) + } + owners := "users" + if forkParent.Owner.IsOrganization() { + owners = "organizations" + } + + o.forked = f3_tree.NewProjectReference(owners, fmt.Sprintf("%d", forkParent.Owner.ID), fmt.Sprintf("%d", forkParent.ID)) +} + +func (o *project) ToFormat() f3.Interface { + if o.forgejoProject == nil { + return o.NewFormat() + } + return &f3.Project{ + Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoProject.ID)), + Name: o.forgejoProject.Name, + IsPrivate: o.forgejoProject.IsPrivate, + IsMirror: o.forgejoProject.IsMirror, + Description: o.forgejoProject.Description, + DefaultBranch: o.forgejoProject.DefaultBranch, + Forked: o.forked, + } +} + +func (o *project) FromFormat(content f3.Interface) { + project := content.(*f3.Project) + o.forgejoProject = &repo_model.Repository{ + ID: f3_util.ParseInt(project.GetID()), + Name: project.Name, + IsPrivate: project.IsPrivate, + IsMirror: project.IsMirror, + Description: project.Description, + DefaultBranch: project.DefaultBranch, + } + if project.Forked != nil { + o.forgejoProject.IsFork = true + o.forgejoProject.ForkID = project.Forked.GetIDAsInt() + } + o.forked = project.Forked +} + +func (o *project) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + id := node.GetID().Int64() + u, err := repo_model.GetRepositoryByID(ctx, id) + if repo_model.IsErrRepoNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("project %v %w", id, err)) + } + o.forgejoProject = u + o.setForkedReference(ctx) + return true +} + +func (o *project) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoProject.ID) + o.forgejoProject.LowerName = strings.ToLower(o.forgejoProject.Name) + if err := repo_model.UpdateRepositoryCols(ctx, o.forgejoProject, + "description", + "name", + "lower_name", + ); err != nil { + panic(fmt.Errorf("UpdateRepositoryCols: %v %v", o.forgejoProject, err)) + } +} + +func (o *project) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + ownerID := f3_tree.GetOwnerID(o.GetNode()) + owner, err := user_model.GetUserByID(ctx, ownerID) + if err != nil { + panic(fmt.Errorf("GetUserByID %v %w", ownerID, err)) + } + doer, err := user_model.GetAdminUser(ctx) + if err != nil { + panic(fmt.Errorf("GetAdminUser %w", err)) + } + + if o.forked == nil { + repo, err := repo_service.CreateRepositoryDirectly(ctx, doer, owner, repo_service.CreateRepoOptions{ + Name: o.forgejoProject.Name, + Description: o.forgejoProject.Description, + IsPrivate: o.forgejoProject.IsPrivate, + DefaultBranch: o.forgejoProject.DefaultBranch, + }) + if err != nil { + panic(err) + } + o.forgejoProject = repo + o.Trace("project created %d", o.forgejoProject.ID) + } else { + if err = o.forgejoProject.GetBaseRepo(ctx); err != nil { + panic(fmt.Errorf("GetBaseRepo %v %w", o.forgejoProject, err)) + } + if err = o.forgejoProject.BaseRepo.LoadOwner(ctx); err != nil { + panic(fmt.Errorf("LoadOwner %v %w", o.forgejoProject.BaseRepo, err)) + } + + repo, err := repo_service.ForkRepositoryIfNotExists(ctx, doer, owner, repo_service.ForkRepoOptions{ + BaseRepo: o.forgejoProject.BaseRepo, + Name: o.forgejoProject.Name, + Description: o.forgejoProject.Description, + }) + if err != nil { + panic(err) + } + o.forgejoProject = repo + o.Trace("project created %d", o.forgejoProject.ID) + } + return generic.NewNodeID(o.forgejoProject.ID) +} + +func (o *project) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + doer, err := user_model.GetAdminUser(ctx) + if err != nil { + panic(fmt.Errorf("GetAdminUser %w", err)) + } + + if err := repo_service.DeleteRepository(ctx, doer, o.forgejoProject, true); err != nil { + panic(err) + } +} + +func newProject() generic.NodeDriverInterface { + return &project{} +} diff --git a/services/f3/driver/projects.go b/services/f3/driver/projects.go new file mode 100644 index 0000000..a2dabc3 --- /dev/null +++ b/services/f3/driver/projects.go @@ -0,0 +1,55 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type projects struct { + container +} + +func (o *projects) GetIDFromName(ctx context.Context, name string) generic.NodeID { + owner := f3_tree.GetOwnerName(o.GetNode()) + forgejoProject, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner, name) + if repo_model.IsErrRepoNotExist(err) { + return generic.NilID + } + + if err != nil { + panic(fmt.Errorf("error GetRepositoryByOwnerAndName(%s, %s): %v", owner, name, err)) + } + + return generic.NewNodeID(forgejoProject.ID) +} + +func (o *projects) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + owner := f3_tree.GetOwner(o.GetNode()) + + forgejoProjects, _, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ + ListOptions: db.ListOptions{Page: page, PageSize: pageSize}, + OwnerID: owner.GetID().Int64(), + Private: true, + }) + if err != nil { + panic(fmt.Errorf("error while listing projects: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoProjects...)...) +} + +func newProjects() generic.NodeDriverInterface { + return &projects{} +} diff --git a/services/f3/driver/pullrequest.go b/services/f3/driver/pullrequest.go new file mode 100644 index 0000000..466b4bd --- /dev/null +++ b/services/f3/driver/pullrequest.go @@ -0,0 +1,320 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + "time" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/timeutil" + issue_service "code.gitea.io/gitea/services/issue" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &pullRequest{} + +type pullRequest struct { + common + + forgejoPullRequest *issues_model.Issue + headRepository *f3.Reference + baseRepository *f3.Reference + fetchFunc f3.PullRequestFetchFunc +} + +func (o *pullRequest) SetNative(pullRequest any) { + o.forgejoPullRequest = pullRequest.(*issues_model.Issue) +} + +func (o *pullRequest) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoPullRequest.Index) +} + +func (o *pullRequest) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *pullRequest) repositoryToReference(ctx context.Context, repository *repo_model.Repository) *f3.Reference { + if repository == nil { + panic("unexpected nil repository") + } + forge := o.getTree().GetRoot().GetChild(generic.NewNodeID(f3_tree.KindForge)).GetDriver().(*forge) + owners := forge.getOwnersPath(ctx, fmt.Sprintf("%d", repository.OwnerID)) + return f3_tree.NewRepositoryReference(owners.String(), repository.OwnerID, repository.ID) +} + +func (o *pullRequest) referenceToRepository(reference *f3.Reference) int64 { + var project int64 + if reference.Get() == "../../repository/vcs" { + project = f3_tree.GetProjectID(o.GetNode()) + } else { + p := f3_tree.ToPath(generic.PathAbsolute(o.GetNode().GetCurrentPath().String(), reference.Get())) + o.Trace("%v %v", o.GetNode().GetCurrentPath().String(), p) + _, project = p.OwnerAndProjectID() + } + return project +} + +func (o *pullRequest) ToFormat() f3.Interface { + if o.forgejoPullRequest == nil { + return o.NewFormat() + } + + var milestone *f3.Reference + if o.forgejoPullRequest.Milestone != nil { + milestone = f3_tree.NewIssueMilestoneReference(o.forgejoPullRequest.Milestone.ID) + } + + var mergedTime *time.Time + if o.forgejoPullRequest.PullRequest.HasMerged { + mergedTime = o.forgejoPullRequest.PullRequest.MergedUnix.AsTimePtr() + } + + var closedTime *time.Time + if o.forgejoPullRequest.IsClosed { + closedTime = o.forgejoPullRequest.ClosedUnix.AsTimePtr() + } + + makePullRequestBranch := func(repo *repo_model.Repository, branch string) f3.PullRequestBranch { + r, err := git.OpenRepository(context.Background(), repo.RepoPath()) + if err != nil { + panic(err) + } + defer r.Close() + + b, err := r.GetBranch(branch) + if err != nil { + panic(err) + } + + c, err := b.GetCommit() + if err != nil { + panic(err) + } + + return f3.PullRequestBranch{ + Ref: branch, + SHA: c.ID.String(), + } + } + if err := o.forgejoPullRequest.PullRequest.LoadHeadRepo(db.DefaultContext); err != nil { + panic(err) + } + head := makePullRequestBranch(o.forgejoPullRequest.PullRequest.HeadRepo, o.forgejoPullRequest.PullRequest.HeadBranch) + head.Repository = o.headRepository + if err := o.forgejoPullRequest.PullRequest.LoadBaseRepo(db.DefaultContext); err != nil { + panic(err) + } + base := makePullRequestBranch(o.forgejoPullRequest.PullRequest.BaseRepo, o.forgejoPullRequest.PullRequest.BaseBranch) + base.Repository = o.baseRepository + + return &f3.PullRequest{ + Common: f3.NewCommon(o.GetNativeID()), + PosterID: f3_tree.NewUserReference(o.forgejoPullRequest.Poster.ID), + Title: o.forgejoPullRequest.Title, + Content: o.forgejoPullRequest.Content, + Milestone: milestone, + State: string(o.forgejoPullRequest.State()), + IsLocked: o.forgejoPullRequest.IsLocked, + Created: o.forgejoPullRequest.CreatedUnix.AsTime(), + Updated: o.forgejoPullRequest.UpdatedUnix.AsTime(), + Closed: closedTime, + Merged: o.forgejoPullRequest.PullRequest.HasMerged, + MergedTime: mergedTime, + MergeCommitSHA: o.forgejoPullRequest.PullRequest.MergedCommitID, + Head: head, + Base: base, + FetchFunc: o.fetchFunc, + } +} + +func (o *pullRequest) FromFormat(content f3.Interface) { + pullRequest := content.(*f3.PullRequest) + var milestone *issues_model.Milestone + if pullRequest.Milestone != nil { + milestone = &issues_model.Milestone{ + ID: pullRequest.Milestone.GetIDAsInt(), + } + } + + o.headRepository = pullRequest.Head.Repository + o.baseRepository = pullRequest.Base.Repository + pr := issues_model.PullRequest{ + HeadBranch: pullRequest.Head.Ref, + HeadRepoID: o.referenceToRepository(o.headRepository), + BaseBranch: pullRequest.Base.Ref, + BaseRepoID: o.referenceToRepository(o.baseRepository), + + MergeBase: pullRequest.Base.SHA, + Index: f3_util.ParseInt(pullRequest.GetID()), + HasMerged: pullRequest.Merged, + } + + o.forgejoPullRequest = &issues_model.Issue{ + Index: f3_util.ParseInt(pullRequest.GetID()), + PosterID: pullRequest.PosterID.GetIDAsInt(), + Poster: &user_model.User{ + ID: pullRequest.PosterID.GetIDAsInt(), + }, + Title: pullRequest.Title, + Content: pullRequest.Content, + Milestone: milestone, + IsClosed: pullRequest.State == f3.PullRequestStateClosed, + CreatedUnix: timeutil.TimeStamp(pullRequest.Created.Unix()), + UpdatedUnix: timeutil.TimeStamp(pullRequest.Updated.Unix()), + IsLocked: pullRequest.IsLocked, + PullRequest: &pr, + IsPull: true, + } + + if pullRequest.Closed != nil { + o.forgejoPullRequest.ClosedUnix = timeutil.TimeStamp(pullRequest.Closed.Unix()) + } +} + +func (o *pullRequest) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + id := node.GetID().Int64() + + issue, err := issues_model.GetIssueByIndex(ctx, project, id) + if issues_model.IsErrIssueNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("issue %v %w", id, err)) + } + if err := issue.LoadAttributes(ctx); err != nil { + panic(err) + } + if err := issue.PullRequest.LoadHeadRepo(ctx); err != nil { + panic(err) + } + o.headRepository = o.repositoryToReference(ctx, issue.PullRequest.HeadRepo) + if err := issue.PullRequest.LoadBaseRepo(ctx); err != nil { + panic(err) + } + o.baseRepository = o.repositoryToReference(ctx, issue.PullRequest.BaseRepo) + + o.forgejoPullRequest = issue + o.Trace("ID = %s", o.forgejoPullRequest.ID) + return true +} + +func (o *pullRequest) Patch(ctx context.Context) { + node := o.GetNode() + project := f3_tree.GetProjectID(o.GetNode()) + id := node.GetID().Int64() + o.Trace("repo_id = %d, index = %d", project, id) + if _, err := db.GetEngine(ctx).Where("`repo_id` = ? AND `index` = ?", project, id).Cols("name", "content").Update(o.forgejoPullRequest); err != nil { + panic(fmt.Errorf("%v %v", o.forgejoPullRequest, err)) + } +} + +func (o *pullRequest) GetPullRequestPushRefs() []string { + return []string{ + fmt.Sprintf("refs/f3/%s/head", o.GetNativeID()), + fmt.Sprintf("refs/pull/%s/head", o.GetNativeID()), + } +} + +func (o *pullRequest) GetPullRequestRef() string { + return fmt.Sprintf("refs/pull/%s/head", o.GetNativeID()) +} + +func (o *pullRequest) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + o.forgejoPullRequest.RepoID = f3_tree.GetProjectID(o.GetNode()) + + ctx, committer, err := db.TxContext(ctx) + if err != nil { + panic(err) + } + defer committer.Close() + + idx, err := db.GetNextResourceIndex(ctx, "issue_index", o.forgejoPullRequest.RepoID) + if err != nil { + panic(fmt.Errorf("generate issue index failed: %w", err)) + } + o.forgejoPullRequest.Index = idx + + sess := db.GetEngine(ctx) + + if _, err = sess.NoAutoTime().Insert(o.forgejoPullRequest); err != nil { + panic(err) + } + + pr := o.forgejoPullRequest.PullRequest + pr.Index = o.forgejoPullRequest.Index + pr.IssueID = o.forgejoPullRequest.ID + pr.HeadRepoID = o.referenceToRepository(o.headRepository) + if pr.HeadRepoID == 0 { + panic(fmt.Errorf("HeadRepoID == 0 in %v", pr)) + } + pr.BaseRepoID = o.referenceToRepository(o.baseRepository) + if pr.BaseRepoID == 0 { + panic(fmt.Errorf("BaseRepoID == 0 in %v", pr)) + } + + if _, err = sess.NoAutoTime().Insert(pr); err != nil { + panic(err) + } + + if err = committer.Commit(); err != nil { + panic(fmt.Errorf("Commit: %w", err)) + } + + if err := pr.LoadBaseRepo(ctx); err != nil { + panic(err) + } + if err := pr.LoadHeadRepo(ctx); err != nil { + panic(err) + } + + o.Trace("pullRequest created %d/%d", o.forgejoPullRequest.ID, o.forgejoPullRequest.Index) + return generic.NewNodeID(o.forgejoPullRequest.Index) +} + +func (o *pullRequest) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + owner := f3_tree.GetOwnerName(o.GetNode()) + project := f3_tree.GetProjectName(o.GetNode()) + repoPath := repo_model.RepoPath(owner, project) + gitRepo, err := git.OpenRepository(ctx, repoPath) + if err != nil { + panic(err) + } + defer gitRepo.Close() + + doer, err := user_model.GetAdminUser(ctx) + if err != nil { + panic(fmt.Errorf("GetAdminUser %w", err)) + } + + if err := issue_service.DeleteIssue(ctx, doer, gitRepo, o.forgejoPullRequest); err != nil { + panic(err) + } +} + +func newPullRequest() generic.NodeDriverInterface { + return &pullRequest{} +} diff --git a/services/f3/driver/pullrequests.go b/services/f3/driver/pullrequests.go new file mode 100644 index 0000000..e7f2910 --- /dev/null +++ b/services/f3/driver/pullrequests.go @@ -0,0 +1,42 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/optional" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type pullRequests struct { + container +} + +func (o *pullRequests) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + project := f3_tree.GetProjectID(o.GetNode()) + + forgejoPullRequests, err := issues_model.Issues(ctx, &issues_model.IssuesOptions{ + Paginator: &db.ListOptions{Page: page, PageSize: pageSize}, + RepoIDs: []int64{project}, + IsPull: optional.Some(true), + }) + if err != nil { + panic(fmt.Errorf("error while listing pullRequests: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoPullRequests...)...) +} + +func newPullRequests() generic.NodeDriverInterface { + return &pullRequests{} +} diff --git a/services/f3/driver/reaction.go b/services/f3/driver/reaction.go new file mode 100644 index 0000000..0dc486c --- /dev/null +++ b/services/f3/driver/reaction.go @@ -0,0 +1,133 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + user_model "code.gitea.io/gitea/models/user" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &reaction{} + +type reaction struct { + common + + forgejoReaction *issues_model.Reaction +} + +func (o *reaction) SetNative(reaction any) { + o.forgejoReaction = reaction.(*issues_model.Reaction) +} + +func (o *reaction) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoReaction.ID) +} + +func (o *reaction) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *reaction) ToFormat() f3.Interface { + if o.forgejoReaction == nil { + return o.NewFormat() + } + return &f3.Reaction{ + Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoReaction.ID)), + UserID: f3_tree.NewUserReference(o.forgejoReaction.User.ID), + Content: o.forgejoReaction.Type, + } +} + +func (o *reaction) FromFormat(content f3.Interface) { + reaction := content.(*f3.Reaction) + + o.forgejoReaction = &issues_model.Reaction{ + ID: f3_util.ParseInt(reaction.GetID()), + UserID: reaction.UserID.GetIDAsInt(), + User: &user_model.User{ + ID: reaction.UserID.GetIDAsInt(), + }, + Type: reaction.Content, + } +} + +func (o *reaction) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + id := node.GetID().Int64() + + if has, err := db.GetEngine(ctx).Where("ID = ?", id).Get(o.forgejoReaction); err != nil { + panic(fmt.Errorf("reaction %v %w", id, err)) + } else if !has { + return false + } + if _, err := o.forgejoReaction.LoadUser(ctx); err != nil { + panic(fmt.Errorf("LoadUser %v %w", *o.forgejoReaction, err)) + } + return true +} + +func (o *reaction) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoReaction.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoReaction.ID).Cols("type").Update(o.forgejoReaction); err != nil { + panic(fmt.Errorf("UpdateReactionCols: %v %v", o.forgejoReaction, err)) + } +} + +func (o *reaction) Put(ctx context.Context) generic.NodeID { + o.Error("%v", o.forgejoReaction.User) + + sess := db.GetEngine(ctx) + + reactionable := f3_tree.GetReactionable(o.GetNode()) + reactionableID := f3_tree.GetReactionableID(o.GetNode()) + + switch reactionable.GetKind() { + case f3_tree.KindIssue, f3_tree.KindPullRequest: + project := f3_tree.GetProjectID(o.GetNode()) + issue, err := issues_model.GetIssueByIndex(ctx, project, reactionableID) + if err != nil { + panic(fmt.Errorf("GetIssueByIndex %v %w", reactionableID, err)) + } + o.forgejoReaction.IssueID = issue.ID + case f3_tree.KindComment: + o.forgejoReaction.CommentID = reactionableID + default: + panic(fmt.Errorf("unexpected type %v", reactionable.GetKind())) + } + + o.Error("%v", o.forgejoReaction) + + if _, err := sess.Insert(o.forgejoReaction); err != nil { + panic(err) + } + o.Trace("reaction created %d", o.forgejoReaction.ID) + return generic.NewNodeID(o.forgejoReaction.ID) +} + +func (o *reaction) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + sess := db.GetEngine(ctx) + if _, err := sess.Delete(o.forgejoReaction); err != nil { + panic(err) + } +} + +func newReaction() generic.NodeDriverInterface { + return &reaction{} +} diff --git a/services/f3/driver/reactions.go b/services/f3/driver/reactions.go new file mode 100644 index 0000000..b7fd5e8 --- /dev/null +++ b/services/f3/driver/reactions.go @@ -0,0 +1,59 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + "xorm.io/builder" +) + +type reactions struct { + container +} + +func (o *reactions) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + reactionable := f3_tree.GetReactionable(o.GetNode()) + reactionableID := f3_tree.GetReactionableID(o.GetNode()) + + sess := db.GetEngine(ctx) + cond := builder.NewCond() + switch reactionable.GetKind() { + case f3_tree.KindIssue, f3_tree.KindPullRequest: + project := f3_tree.GetProjectID(o.GetNode()) + issue, err := issues_model.GetIssueByIndex(ctx, project, reactionableID) + if err != nil { + panic(fmt.Errorf("GetIssueByIndex %v %w", reactionableID, err)) + } + cond = cond.And(builder.Eq{"reaction.issue_id": issue.ID}) + case f3_tree.KindComment: + cond = cond.And(builder.Eq{"reaction.comment_id": reactionableID}) + default: + panic(fmt.Errorf("unexpected type %v", reactionable.GetKind())) + } + + sess = sess.Where(cond) + if page > 0 { + sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize}) + } + reactions := make([]*issues_model.Reaction, 0, 10) + if err := sess.Find(&reactions); err != nil { + panic(fmt.Errorf("error while listing reactions: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(reactions...)...) +} + +func newReactions() generic.NodeDriverInterface { + return &reactions{} +} diff --git a/services/f3/driver/release.go b/services/f3/driver/release.go new file mode 100644 index 0000000..e937f84 --- /dev/null +++ b/services/f3/driver/release.go @@ -0,0 +1,161 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + "strings" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/timeutil" + release_service "code.gitea.io/gitea/services/release" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &release{} + +type release struct { + common + + forgejoRelease *repo_model.Release +} + +func (o *release) SetNative(release any) { + o.forgejoRelease = release.(*repo_model.Release) +} + +func (o *release) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoRelease.ID) +} + +func (o *release) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *release) ToFormat() f3.Interface { + if o.forgejoRelease == nil { + return o.NewFormat() + } + return &f3.Release{ + Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoRelease.ID)), + TagName: o.forgejoRelease.TagName, + TargetCommitish: o.forgejoRelease.Target, + Name: o.forgejoRelease.Title, + Body: o.forgejoRelease.Note, + Draft: o.forgejoRelease.IsDraft, + Prerelease: o.forgejoRelease.IsPrerelease, + PublisherID: f3_tree.NewUserReference(o.forgejoRelease.Publisher.ID), + Created: o.forgejoRelease.CreatedUnix.AsTime(), + } +} + +func (o *release) FromFormat(content f3.Interface) { + release := content.(*f3.Release) + + o.forgejoRelease = &repo_model.Release{ + ID: f3_util.ParseInt(release.GetID()), + PublisherID: release.PublisherID.GetIDAsInt(), + Publisher: &user_model.User{ + ID: release.PublisherID.GetIDAsInt(), + }, + TagName: release.TagName, + LowerTagName: strings.ToLower(release.TagName), + Target: release.TargetCommitish, + Title: release.Name, + Note: release.Body, + IsDraft: release.Draft, + IsPrerelease: release.Prerelease, + IsTag: false, + CreatedUnix: timeutil.TimeStamp(release.Created.Unix()), + } +} + +func (o *release) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + id := node.GetID().Int64() + + release, err := repo_model.GetReleaseByID(ctx, id) + if repo_model.IsErrReleaseNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("release %v %w", id, err)) + } + + release.Publisher, err = user_model.GetUserByID(ctx, release.PublisherID) + if err != nil { + if user_model.IsErrUserNotExist(err) { + release.Publisher = user_model.NewGhostUser() + } else { + panic(err) + } + } + + o.forgejoRelease = release + return true +} + +func (o *release) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoRelease.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoRelease.ID).Cols("title", "note").Update(o.forgejoRelease); err != nil { + panic(fmt.Errorf("UpdateReleaseCols: %v %v", o.forgejoRelease, err)) + } +} + +func (o *release) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + o.forgejoRelease.RepoID = f3_tree.GetProjectID(o.GetNode()) + + owner := f3_tree.GetOwnerName(o.GetNode()) + project := f3_tree.GetProjectName(o.GetNode()) + repoPath := repo_model.RepoPath(owner, project) + gitRepo, err := git.OpenRepository(ctx, repoPath) + if err != nil { + panic(err) + } + defer gitRepo.Close() + if err := release_service.CreateRelease(gitRepo, o.forgejoRelease, "", nil); err != nil { + panic(err) + } + o.Trace("release created %d", o.forgejoRelease.ID) + return generic.NewNodeID(o.forgejoRelease.ID) +} + +func (o *release) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + repo, err := repo_model.GetRepositoryByID(ctx, project) + if err != nil { + panic(err) + } + + doer, err := user_model.GetAdminUser(ctx) + if err != nil { + panic(fmt.Errorf("GetAdminUser %w", err)) + } + + if err := release_service.DeleteReleaseByID(ctx, repo, o.forgejoRelease, doer, true); err != nil { + panic(err) + } +} + +func newRelease() generic.NodeDriverInterface { + return &release{} +} diff --git a/services/f3/driver/releases.go b/services/f3/driver/releases.go new file mode 100644 index 0000000..3b46bc7 --- /dev/null +++ b/services/f3/driver/releases.go @@ -0,0 +1,42 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type releases struct { + container +} + +func (o *releases) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + project := f3_tree.GetProjectID(o.GetNode()) + + forgejoReleases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{ + ListOptions: db.ListOptions{Page: page, PageSize: pageSize}, + IncludeDrafts: true, + IncludeTags: false, + RepoID: project, + }) + if err != nil { + panic(fmt.Errorf("error while listing releases: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoReleases...)...) +} + +func newReleases() generic.NodeDriverInterface { + return &releases{} +} diff --git a/services/f3/driver/repositories.go b/services/f3/driver/repositories.go new file mode 100644 index 0000000..03daf35 --- /dev/null +++ b/services/f3/driver/repositories.go @@ -0,0 +1,36 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type repositories struct { + container +} + +func (o *repositories) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + children := generic.NewChildrenSlice(0) + if page > 1 { + return children + } + + names := []string{f3.RepositoryNameDefault} + project := f3_tree.GetProject(o.GetNode()).ToFormat().(*f3.Project) + if project.HasWiki { + names = append(names, f3.RepositoryNameWiki) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(names...)...) +} + +func newRepositories() generic.NodeDriverInterface { + return &repositories{} +} diff --git a/services/f3/driver/repository.go b/services/f3/driver/repository.go new file mode 100644 index 0000000..da968b4 --- /dev/null +++ b/services/f3/driver/repository.go @@ -0,0 +1,101 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + + repo_model "code.gitea.io/gitea/models/repo" + + "code.forgejo.org/f3/gof3/v3/f3" + helpers_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/repository" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +var _ f3_tree.ForgeDriverInterface = &repository{} + +type repository struct { + common + + name string + h helpers_repository.Interface + + f *f3.Repository +} + +func (o *repository) SetNative(repository any) { + o.name = repository.(string) +} + +func (o *repository) GetNativeID() string { + return o.name +} + +func (o *repository) NewFormat() f3.Interface { + return &f3.Repository{} +} + +func (o *repository) ToFormat() f3.Interface { + return &f3.Repository{ + Common: f3.NewCommon(o.GetNativeID()), + Name: o.GetNativeID(), + FetchFunc: o.f.FetchFunc, + } +} + +func (o *repository) FromFormat(content f3.Interface) { + f := content.Clone().(*f3.Repository) + o.f = f + o.f.SetID(f.Name) + o.name = f.Name +} + +func (o *repository) Get(ctx context.Context) bool { + return o.h.Get(ctx) +} + +func (o *repository) Put(ctx context.Context) generic.NodeID { + return o.upsert(ctx) +} + +func (o *repository) Patch(ctx context.Context) { + o.upsert(ctx) +} + +func (o *repository) upsert(ctx context.Context) generic.NodeID { + o.Trace("%s", o.GetNativeID()) + o.h.Upsert(ctx, o.f) + return generic.NewNodeID(o.f.Name) +} + +func (o *repository) SetFetchFunc(fetchFunc func(ctx context.Context, destination string)) { + o.f.FetchFunc = fetchFunc +} + +func (o *repository) getURL() string { + owner := f3_tree.GetOwnerName(o.GetNode()) + repoName := f3_tree.GetProjectName(o.GetNode()) + if o.f.GetID() == f3.RepositoryNameWiki { + repoName += ".wiki" + } + return repo_model.RepoPath(owner, repoName) +} + +func (o *repository) GetRepositoryURL() string { + return o.getURL() +} + +func (o *repository) GetRepositoryPushURL() string { + return o.getURL() +} + +func newRepository(_ context.Context) generic.NodeDriverInterface { + r := &repository{ + f: &f3.Repository{}, + } + r.h = helpers_repository.NewHelper(r) + return r +} diff --git a/services/f3/driver/review.go b/services/f3/driver/review.go new file mode 100644 index 0000000..a3c074b --- /dev/null +++ b/services/f3/driver/review.go @@ -0,0 +1,179 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/timeutil" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &review{} + +type review struct { + common + + forgejoReview *issues_model.Review +} + +func (o *review) SetNative(review any) { + o.forgejoReview = review.(*issues_model.Review) +} + +func (o *review) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoReview.ID) +} + +func (o *review) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *review) ToFormat() f3.Interface { + if o.forgejoReview == nil { + return o.NewFormat() + } + + review := &f3.Review{ + Common: f3.NewCommon(o.GetNativeID()), + ReviewerID: f3_tree.NewUserReference(o.forgejoReview.ReviewerID), + Official: o.forgejoReview.Official, + CommitID: o.forgejoReview.CommitID, + Content: o.forgejoReview.Content, + CreatedAt: o.forgejoReview.CreatedUnix.AsTime(), + } + + switch o.forgejoReview.Type { + case issues_model.ReviewTypeApprove: + review.State = f3.ReviewStateApproved + case issues_model.ReviewTypeReject: + review.State = f3.ReviewStateChangesRequested + case issues_model.ReviewTypeComment: + review.State = f3.ReviewStateCommented + case issues_model.ReviewTypePending: + review.State = f3.ReviewStatePending + case issues_model.ReviewTypeRequest: + review.State = f3.ReviewStateRequestReview + default: + review.State = f3.ReviewStateUnknown + } + + if o.forgejoReview.Reviewer != nil { + review.ReviewerID = f3_tree.NewUserReference(o.forgejoReview.Reviewer.ID) + } + + return review +} + +func (o *review) FromFormat(content f3.Interface) { + review := content.(*f3.Review) + + o.forgejoReview = &issues_model.Review{ + ID: f3_util.ParseInt(review.GetID()), + ReviewerID: review.ReviewerID.GetIDAsInt(), + Reviewer: &user_model.User{ + ID: review.ReviewerID.GetIDAsInt(), + }, + Official: review.Official, + CommitID: review.CommitID, + Content: review.Content, + CreatedUnix: timeutil.TimeStamp(review.CreatedAt.Unix()), + } + + switch review.State { + case f3.ReviewStateApproved: + o.forgejoReview.Type = issues_model.ReviewTypeApprove + case f3.ReviewStateChangesRequested: + o.forgejoReview.Type = issues_model.ReviewTypeReject + case f3.ReviewStateCommented: + o.forgejoReview.Type = issues_model.ReviewTypeComment + case f3.ReviewStatePending: + o.forgejoReview.Type = issues_model.ReviewTypePending + case f3.ReviewStateRequestReview: + o.forgejoReview.Type = issues_model.ReviewTypeRequest + default: + o.forgejoReview.Type = issues_model.ReviewTypeUnknown + } +} + +func (o *review) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + id := node.GetID().Int64() + + review, err := issues_model.GetReviewByID(ctx, id) + if issues_model.IsErrReviewNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("review %v %w", id, err)) + } + if err := review.LoadReviewer(ctx); err != nil { + panic(fmt.Errorf("LoadReviewer %v %w", *review, err)) + } + o.forgejoReview = review + return true +} + +func (o *review) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoReview.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoReview.ID).Cols("content").Update(o.forgejoReview); err != nil { + panic(fmt.Errorf("UpdateReviewCols: %v %v", o.forgejoReview, err)) + } +} + +func (o *review) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + pullRequest := f3_tree.GetPullRequestID(o.GetNode()) + + issue, err := issues_model.GetIssueByIndex(ctx, project, pullRequest) + if err != nil { + panic(fmt.Errorf("GetIssueByIndex %v", err)) + } + o.forgejoReview.IssueID = issue.ID + + sess := db.GetEngine(ctx) + + if _, err := sess.NoAutoTime().Insert(o.forgejoReview); err != nil { + panic(err) + } + o.Trace("review created %d", o.forgejoReview.ID) + return generic.NewNodeID(o.forgejoReview.ID) +} + +func (o *review) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + project := f3_tree.GetProjectID(o.GetNode()) + pullRequest := f3_tree.GetPullRequestID(o.GetNode()) + + issue, err := issues_model.GetIssueByIndex(ctx, project, pullRequest) + if err != nil { + panic(fmt.Errorf("GetIssueByIndex %v", err)) + } + o.forgejoReview.IssueID = issue.ID + + if err := issues_model.DeleteReview(ctx, o.forgejoReview); err != nil { + panic(err) + } +} + +func newReview() generic.NodeDriverInterface { + return &review{} +} diff --git a/services/f3/driver/reviewcomment.go b/services/f3/driver/reviewcomment.go new file mode 100644 index 0000000..8e13d86 --- /dev/null +++ b/services/f3/driver/reviewcomment.go @@ -0,0 +1,142 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + "strings" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/timeutil" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &reviewComment{} + +type reviewComment struct { + common + + forgejoReviewComment *issues_model.Comment +} + +func (o *reviewComment) SetNative(reviewComment any) { + o.forgejoReviewComment = reviewComment.(*issues_model.Comment) +} + +func (o *reviewComment) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoReviewComment.ID) +} + +func (o *reviewComment) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func patch2diff(patch string) string { + split := strings.Split(patch, "\n@@") + if len(split) == 2 { + return "@@" + split[1] + } + return patch +} + +func (o *reviewComment) ToFormat() f3.Interface { + if o.forgejoReviewComment == nil { + return o.NewFormat() + } + + return &f3.ReviewComment{ + Common: f3.NewCommon(o.GetNativeID()), + PosterID: f3_tree.NewUserReference(o.forgejoReviewComment.Poster.ID), + Content: o.forgejoReviewComment.Content, + TreePath: o.forgejoReviewComment.TreePath, + DiffHunk: patch2diff(o.forgejoReviewComment.PatchQuoted), + Line: int(o.forgejoReviewComment.Line), + CommitID: o.forgejoReviewComment.CommitSHA, + CreatedAt: o.forgejoReviewComment.CreatedUnix.AsTime(), + UpdatedAt: o.forgejoReviewComment.UpdatedUnix.AsTime(), + } +} + +func (o *reviewComment) FromFormat(content f3.Interface) { + reviewComment := content.(*f3.ReviewComment) + o.forgejoReviewComment = &issues_model.Comment{ + ID: f3_util.ParseInt(reviewComment.GetID()), + PosterID: reviewComment.PosterID.GetIDAsInt(), + Poster: &user_model.User{ + ID: reviewComment.PosterID.GetIDAsInt(), + }, + TreePath: reviewComment.TreePath, + Content: reviewComment.Content, + // a hunk misses the patch header but it is never used so do not bother + // reconstructing it + Patch: reviewComment.DiffHunk, + PatchQuoted: reviewComment.DiffHunk, + Line: int64(reviewComment.Line), + CommitSHA: reviewComment.CommitID, + CreatedUnix: timeutil.TimeStamp(reviewComment.CreatedAt.Unix()), + UpdatedUnix: timeutil.TimeStamp(reviewComment.UpdatedAt.Unix()), + } +} + +func (o *reviewComment) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + id := node.GetID().Int64() + + reviewComment, err := issues_model.GetCommentByID(ctx, id) + if issues_model.IsErrCommentNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("reviewComment %v %w", id, err)) + } + if err := reviewComment.LoadPoster(ctx); err != nil { + panic(fmt.Errorf("LoadPoster %v %w", *reviewComment, err)) + } + o.forgejoReviewComment = reviewComment + return true +} + +func (o *reviewComment) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoReviewComment.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoReviewComment.ID).Cols("content").Update(o.forgejoReviewComment); err != nil { + panic(fmt.Errorf("UpdateReviewCommentCols: %v %v", o.forgejoReviewComment, err)) + } +} + +func (o *reviewComment) Put(ctx context.Context) generic.NodeID { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + sess := db.GetEngine(ctx) + + if _, err := sess.NoAutoTime().Insert(o.forgejoReviewComment); err != nil { + panic(err) + } + o.Trace("reviewComment created %d", o.forgejoReviewComment.ID) + return generic.NewNodeID(o.forgejoReviewComment.ID) +} + +func (o *reviewComment) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + if err := issues_model.DeleteComment(ctx, o.forgejoReviewComment); err != nil { + panic(err) + } +} + +func newReviewComment() generic.NodeDriverInterface { + return &reviewComment{} +} diff --git a/services/f3/driver/reviewcomments.go b/services/f3/driver/reviewcomments.go new file mode 100644 index 0000000..e11aaa4 --- /dev/null +++ b/services/f3/driver/reviewcomments.go @@ -0,0 +1,43 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type reviewComments struct { + container +} + +func (o *reviewComments) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + id := f3_tree.GetReviewID(o.GetNode()) + + sess := db.GetEngine(ctx). + Table("comment"). + Where("`review_id` = ? AND `type` = ?", id, issues_model.CommentTypeCode) + if page != 0 { + sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize}) + } + forgejoReviewComments := make([]*issues_model.Comment, 0, pageSize) + if err := sess.Find(&forgejoReviewComments); err != nil { + panic(fmt.Errorf("error while listing reviewComments: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoReviewComments...)...) +} + +func newReviewComments() generic.NodeDriverInterface { + return &reviewComments{} +} diff --git a/services/f3/driver/reviews.go b/services/f3/driver/reviews.go new file mode 100644 index 0000000..a20d574 --- /dev/null +++ b/services/f3/driver/reviews.go @@ -0,0 +1,49 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type reviews struct { + container +} + +func (o *reviews) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + project := f3_tree.GetProjectID(o.GetNode()) + pullRequest := f3_tree.GetPullRequestID(o.GetNode()) + + issue, err := issues_model.GetIssueByIndex(ctx, project, pullRequest) + if err != nil { + panic(fmt.Errorf("GetIssueByIndex %v %w", pullRequest, err)) + } + + sess := db.GetEngine(ctx). + Table("review"). + Where("`issue_id` = ?", issue.ID) + if page != 0 { + sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize}) + } + forgejoReviews := make([]*issues_model.Review, 0, pageSize) + if err := sess.Find(&forgejoReviews); err != nil { + panic(fmt.Errorf("error while listing reviews: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(forgejoReviews...)...) +} + +func newReviews() generic.NodeDriverInterface { + return &reviews{} +} diff --git a/services/f3/driver/root.go b/services/f3/driver/root.go new file mode 100644 index 0000000..0e8a67f --- /dev/null +++ b/services/f3/driver/root.go @@ -0,0 +1,41 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + + "code.forgejo.org/f3/gof3/v3/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type root struct { + generic.NullDriver + + content f3.Interface +} + +func newRoot(content f3.Interface) generic.NodeDriverInterface { + return &root{ + content: content, + } +} + +func (o *root) FromFormat(content f3.Interface) { + o.content = content +} + +func (o *root) ToFormat() f3.Interface { + return o.content +} + +func (o *root) Get(context.Context) bool { return true } + +func (o *root) Put(context.Context) generic.NodeID { + return generic.NilID +} + +func (o *root) Patch(context.Context) { +} diff --git a/services/f3/driver/tests/init.go b/services/f3/driver/tests/init.go new file mode 100644 index 0000000..d7bf23a --- /dev/null +++ b/services/f3/driver/tests/init.go @@ -0,0 +1,15 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package tests + +import ( + driver_options "code.gitea.io/gitea/services/f3/driver/options" + + tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge" +) + +func init() { + tests_forge.RegisterFactory(driver_options.Name, newForgeTest) +} diff --git a/services/f3/driver/tests/new.go b/services/f3/driver/tests/new.go new file mode 100644 index 0000000..2e3dfc3 --- /dev/null +++ b/services/f3/driver/tests/new.go @@ -0,0 +1,39 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package tests + +import ( + "testing" + + driver_options "code.gitea.io/gitea/services/f3/driver/options" + + "code.forgejo.org/f3/gof3/v3/options" + "code.forgejo.org/f3/gof3/v3/tree/generic" + forge_test "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge" +) + +type forgeTest struct { + forge_test.Base +} + +func (o *forgeTest) NewOptions(t *testing.T) options.Interface { + return newTestOptions(t) +} + +func (o *forgeTest) GetExceptions() []generic.Kind { + return []generic.Kind{} +} + +func (o *forgeTest) GetNonTestUsers() []string { + return []string{ + "user1", + } +} + +func newForgeTest() forge_test.Interface { + t := &forgeTest{} + t.SetName(driver_options.Name) + return t +} diff --git a/services/f3/driver/tests/options.go b/services/f3/driver/tests/options.go new file mode 100644 index 0000000..adaa1da --- /dev/null +++ b/services/f3/driver/tests/options.go @@ -0,0 +1,21 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package tests + +import ( + "testing" + + forgejo_log "code.gitea.io/gitea/modules/log" + driver_options "code.gitea.io/gitea/services/f3/driver/options" + "code.gitea.io/gitea/services/f3/util" + + "code.forgejo.org/f3/gof3/v3/options" +) + +func newTestOptions(_ *testing.T) options.Interface { + o := options.GetFactory(driver_options.Name)().(*driver_options.Options) + o.SetLogger(util.NewF3Logger(nil, forgejo_log.GetLogger(forgejo_log.DEFAULT))) + return o +} diff --git a/services/f3/driver/topic.go b/services/f3/driver/topic.go new file mode 100644 index 0000000..16b2eb3 --- /dev/null +++ b/services/f3/driver/topic.go @@ -0,0 +1,111 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &topic{} + +type topic struct { + common + + forgejoTopic *repo_model.Topic +} + +func (o *topic) SetNative(topic any) { + o.forgejoTopic = topic.(*repo_model.Topic) +} + +func (o *topic) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoTopic.ID) +} + +func (o *topic) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *topic) ToFormat() f3.Interface { + if o.forgejoTopic == nil { + return o.NewFormat() + } + + return &f3.Topic{ + Common: f3.NewCommon(o.GetNativeID()), + Name: o.forgejoTopic.Name, + } +} + +func (o *topic) FromFormat(content f3.Interface) { + topic := content.(*f3.Topic) + o.forgejoTopic = &repo_model.Topic{ + ID: f3_util.ParseInt(topic.GetID()), + Name: topic.Name, + } +} + +func (o *topic) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + id := node.GetID().Int64() + + if has, err := db.GetEngine(ctx).Where("ID = ?", id).Get(o.forgejoTopic); err != nil { + panic(fmt.Errorf("topic %v %w", id, err)) + } else if !has { + return false + } + + return true +} + +func (o *topic) Patch(ctx context.Context) { + o.Trace("%d", o.forgejoTopic.ID) + if _, err := db.GetEngine(ctx).ID(o.forgejoTopic.ID).Cols("name").Update(o.forgejoTopic); err != nil { + panic(fmt.Errorf("UpdateTopicCols: %v %v", o.forgejoTopic, err)) + } +} + +func (o *topic) Put(ctx context.Context) generic.NodeID { + sess := db.GetEngine(ctx) + + if _, err := sess.Insert(o.forgejoTopic); err != nil { + panic(err) + } + o.Trace("topic created %d", o.forgejoTopic.ID) + return generic.NewNodeID(o.forgejoTopic.ID) +} + +func (o *topic) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + sess := db.GetEngine(ctx) + + if _, err := sess.Delete(&repo_model.RepoTopic{ + TopicID: o.forgejoTopic.ID, + }); err != nil { + panic(fmt.Errorf("Delete RepoTopic for %v %v", o.forgejoTopic, err)) + } + + if _, err := sess.Delete(o.forgejoTopic); err != nil { + panic(fmt.Errorf("Delete Topic %v %v", o.forgejoTopic, err)) + } +} + +func newTopic() generic.NodeDriverInterface { + return &topic{} +} diff --git a/services/f3/driver/topics.go b/services/f3/driver/topics.go new file mode 100644 index 0000000..2685a47 --- /dev/null +++ b/services/f3/driver/topics.go @@ -0,0 +1,41 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type topics struct { + container +} + +func (o *topics) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + pageSize := o.getPageSize() + + sess := db.GetEngine(ctx) + if page != 0 { + sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: pageSize}) + } + sess = sess.Select("`topic`.*") + topics := make([]*repo_model.Topic, 0, pageSize) + + if err := sess.Find(&topics); err != nil { + panic(fmt.Errorf("error while listing topics: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(topics...)...) +} + +func newTopics() generic.NodeDriverInterface { + return &topics{} +} diff --git a/services/f3/driver/tree.go b/services/f3/driver/tree.go new file mode 100644 index 0000000..0302ed7 --- /dev/null +++ b/services/f3/driver/tree.go @@ -0,0 +1,104 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + forgejo_options "code.gitea.io/gitea/services/f3/driver/options" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type treeDriver struct { + generic.NullTreeDriver + + options *forgejo_options.Options +} + +func (o *treeDriver) Init() { + o.NullTreeDriver.Init() +} + +func (o *treeDriver) Factory(ctx context.Context, kind generic.Kind) generic.NodeDriverInterface { + switch kind { + case f3_tree.KindForge: + return newForge() + case f3_tree.KindOrganizations: + return newOrganizations() + case f3_tree.KindOrganization: + return newOrganization() + case f3_tree.KindUsers: + return newUsers() + case f3_tree.KindUser: + return newUser() + case f3_tree.KindProjects: + return newProjects() + case f3_tree.KindProject: + return newProject() + case f3_tree.KindIssues: + return newIssues() + case f3_tree.KindIssue: + return newIssue() + case f3_tree.KindComments: + return newComments() + case f3_tree.KindComment: + return newComment() + case f3_tree.KindAssets: + return newAssets() + case f3_tree.KindAsset: + return newAsset() + case f3_tree.KindLabels: + return newLabels() + case f3_tree.KindLabel: + return newLabel() + case f3_tree.KindReactions: + return newReactions() + case f3_tree.KindReaction: + return newReaction() + case f3_tree.KindReviews: + return newReviews() + case f3_tree.KindReview: + return newReview() + case f3_tree.KindReviewComments: + return newReviewComments() + case f3_tree.KindReviewComment: + return newReviewComment() + case f3_tree.KindMilestones: + return newMilestones() + case f3_tree.KindMilestone: + return newMilestone() + case f3_tree.KindPullRequests: + return newPullRequests() + case f3_tree.KindPullRequest: + return newPullRequest() + case f3_tree.KindReleases: + return newReleases() + case f3_tree.KindRelease: + return newRelease() + case f3_tree.KindTopics: + return newTopics() + case f3_tree.KindTopic: + return newTopic() + case f3_tree.KindRepositories: + return newRepositories() + case f3_tree.KindRepository: + return newRepository(ctx) + case generic.KindRoot: + return newRoot(o.GetTree().(f3_tree.TreeInterface).NewFormat(kind)) + default: + panic(fmt.Errorf("unexpected kind %s", kind)) + } +} + +func newTreeDriver(tree generic.TreeInterface, anyOptions any) generic.TreeDriverInterface { + driver := &treeDriver{ + options: anyOptions.(*forgejo_options.Options), + } + driver.Init() + return driver +} diff --git a/services/f3/driver/user.go b/services/f3/driver/user.go new file mode 100644 index 0000000..221b06e --- /dev/null +++ b/services/f3/driver/user.go @@ -0,0 +1,128 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + "strings" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/optional" + user_service "code.gitea.io/gitea/services/user" + + "code.forgejo.org/f3/gof3/v3/f3" + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" + f3_util "code.forgejo.org/f3/gof3/v3/util" +) + +var _ f3_tree.ForgeDriverInterface = &user{} + +type user struct { + common + + forgejoUser *user_model.User +} + +func getSystemUserByName(name string) *user_model.User { + switch name { + case user_model.GhostUserName: + return user_model.NewGhostUser() + case user_model.ActionsUserName: + return user_model.NewActionsUser() + default: + return nil + } +} + +func (o *user) SetNative(user any) { + o.forgejoUser = user.(*user_model.User) +} + +func (o *user) GetNativeID() string { + return fmt.Sprintf("%d", o.forgejoUser.ID) +} + +func (o *user) NewFormat() f3.Interface { + node := o.GetNode() + return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) +} + +func (o *user) ToFormat() f3.Interface { + if o.forgejoUser == nil { + return o.NewFormat() + } + return &f3.User{ + Common: f3.NewCommon(fmt.Sprintf("%d", o.forgejoUser.ID)), + UserName: o.forgejoUser.Name, + Name: o.forgejoUser.FullName, + Email: o.forgejoUser.Email, + IsAdmin: o.forgejoUser.IsAdmin, + Password: o.forgejoUser.Passwd, + } +} + +func (o *user) FromFormat(content f3.Interface) { + user := content.(*f3.User) + o.forgejoUser = &user_model.User{ + Type: user_model.UserTypeRemoteUser, + ID: f3_util.ParseInt(user.GetID()), + Name: user.UserName, + FullName: user.Name, + Email: user.Email, + IsAdmin: user.IsAdmin, + Passwd: user.Password, + } +} + +func (o *user) Get(ctx context.Context) bool { + node := o.GetNode() + o.Trace("%s", node.GetID()) + id := node.GetID().Int64() + u, err := user_model.GetPossibleUserByID(ctx, id) + if user_model.IsErrUserNotExist(err) { + return false + } + if err != nil { + panic(fmt.Errorf("user %v %w", id, err)) + } + o.forgejoUser = u + return true +} + +func (o *user) Patch(context.Context) { +} + +func (o *user) Put(ctx context.Context) generic.NodeID { + if user := getSystemUserByName(o.forgejoUser.Name); user != nil { + return generic.NewNodeID(user.ID) + } + + o.forgejoUser.LowerName = strings.ToLower(o.forgejoUser.Name) + o.Trace("%v", *o.forgejoUser) + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsActive: optional.Some(true), + } + err := user_model.CreateUser(ctx, o.forgejoUser, overwriteDefault) + if err != nil { + panic(err) + } + + return generic.NewNodeID(o.forgejoUser.ID) +} + +func (o *user) Delete(ctx context.Context) { + node := o.GetNode() + o.Trace("%s", node.GetID()) + + if err := user_service.DeleteUser(ctx, o.forgejoUser, true); err != nil { + panic(err) + } +} + +func newUser() generic.NodeDriverInterface { + return &user{} +} diff --git a/services/f3/driver/users.go b/services/f3/driver/users.go new file mode 100644 index 0000000..92ed0bc --- /dev/null +++ b/services/f3/driver/users.go @@ -0,0 +1,48 @@ +// Copyright Earl Warren <contact@earl-warren.org> +// Copyright Loïc Dachary <loic@dachary.org> +// SPDX-License-Identifier: MIT + +package driver + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + + f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" + "code.forgejo.org/f3/gof3/v3/tree/generic" +) + +type users struct { + container +} + +func (o *users) ListPage(ctx context.Context, page int) generic.ChildrenSlice { + sess := db.GetEngine(ctx).In("type", user_model.UserTypeIndividual, user_model.UserTypeRemoteUser) + if page != 0 { + sess = db.SetSessionPagination(sess, &db.ListOptions{Page: page, PageSize: o.getPageSize()}) + } + sess = sess.Select("`user`.*") + users := make([]*user_model.User, 0, o.getPageSize()) + + if err := sess.Find(&users); err != nil { + panic(fmt.Errorf("error while listing users: %v", err)) + } + + return f3_tree.ConvertListed(ctx, o.GetNode(), f3_tree.ConvertToAny(users...)...) +} + +func (o *users) GetIDFromName(ctx context.Context, name string) generic.NodeID { + user, err := user_model.GetUserByName(ctx, name) + if err != nil { + panic(fmt.Errorf("GetUserByName: %v", err)) + } + + return generic.NewNodeID(user.ID) +} + +func newUsers() generic.NodeDriverInterface { + return &users{} +} |