refactor!: rename oo-cli → oo, split library by domain, relocate applications
BREAKING:
- Binary renamed oo-cli → oo; install path is now
github.com/eslider/go-onlyoffice/cmd/oo.
- Former internal/cli tree removed; cobra commands live in cmd/oo/ as
package main, split by domain (calendar.go, crm.go, tasks.go, apps.go,
common.go, main.go).
- internal/applications moved to cmd/oo/applications/ (CV-specific workflow;
not a library feature).
- examples/applications removed (it depended on an internal package).
Library split (mechanical, zero API surface change):
- client.go — Client, Credentials, Defaults, env helpers, NewClient.
- request.go — Request, Query, Time, Token, MetaResponse, Permissions.
- auth.go — Authenticate/AuthenticateContext/InvalidateToken.
- http.go — transport + ResponseArray/ResponseObject/postFormObject/
putFormObject/deleteObject/unmarshalResponseObject (renamed
from httpx.go).
- projects.go, tasks.go, users.go, calendar.go, crm.go, files.go — typed
/ untyped domain methods. tasks_extra.go merged into tasks.go.
- onlyoffice.go deleted (content redistributed).
AGENTS.md, CHANGELOG.md, README.md updated accordingly.
Made-with: Cursor
This commit is contained in:
+206
@@ -0,0 +1,206 @@
|
||||
package onlyoffice
|
||||
|
||||
// Project / Milestone typed API.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Project struct
|
||||
type Project struct {
|
||||
ID *int `json:"id"`
|
||||
Title *string `json:"title"`
|
||||
Security map[string]bool `json:"security,omitempty"`
|
||||
ProjectFolder *json.Number `json:"projectFolder,omitempty"`
|
||||
Description *string `json:"description"`
|
||||
Status *int `json:"status"`
|
||||
|
||||
ResponsibleID *string `json:"responsibleId,omitempty"`
|
||||
Responsible *User `json:"responsible,omitempty"`
|
||||
|
||||
IsPrivate *bool `json:"isPrivate"`
|
||||
|
||||
TaskCount *int `json:"taskCount,omitempty"`
|
||||
TaskCountTotal *int `json:"taskCountTotal,omitempty"`
|
||||
MilestoneCount *int `json:"milestoneCount,omitempty"`
|
||||
DiscussionCount *int `json:"discussionCount,omitempty"`
|
||||
ParticipantCount *int `json:"participantCount,omitempty"`
|
||||
TimeTrackingTotal *string `json:"timeTrackingTotal,omitempty"`
|
||||
DocumentsCount *int `json:"documentsCount,omitempty"`
|
||||
|
||||
IsFollow *bool `json:"isFollow,omitempty"`
|
||||
|
||||
Created *time.Time `json:"created"`
|
||||
CreatedBy *User `json:"createdBy,omitempty"`
|
||||
CreatedByID *string `json:"createdById"`
|
||||
Updated *time.Time `json:"updated"`
|
||||
UpdatedByID *string `json:"updatedById"`
|
||||
|
||||
Permissions *Permissions `json:",inline,omitempty"`
|
||||
}
|
||||
|
||||
// String returns the project Title, or "" if the Title is nil.
|
||||
func (p Project) String() string {
|
||||
if p.Title == nil {
|
||||
return ""
|
||||
}
|
||||
return *p.Title
|
||||
}
|
||||
|
||||
// Projects is a slice with helpers for title lookup.
|
||||
type Projects []*Project
|
||||
|
||||
// Get returns the first project whose title equals title, or nil.
|
||||
func (p Projects) Get(title string) *Project {
|
||||
for _, prj := range p {
|
||||
if prj != nil && prj.Title != nil && *prj.Title == title {
|
||||
return prj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Milestone is a project milestone.
|
||||
type Milestone struct {
|
||||
ID *int64 `json:"id,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Deadline *time.Time `json:"deadline,omitempty"`
|
||||
|
||||
IsKey *bool `json:"isKey,omitempty"`
|
||||
IsNotify *bool `json:"isNotify,omitempty"`
|
||||
|
||||
ProjectOwner *ProjectOwner `json:"projectOwner,omitempty"`
|
||||
Responsible *User `json:"responsible,omitempty"`
|
||||
|
||||
ActiveTaskCount *int64 `json:"activeTaskCount,omitempty"`
|
||||
ClosedTaskCount *int64 `json:"closedTaskCount,omitempty"`
|
||||
Status *int64 `json:"status,omitempty"`
|
||||
|
||||
Created *time.Time `json:"created,omitempty"`
|
||||
CreatedBy *User `json:"createdBy,omitempty"`
|
||||
|
||||
Updated *time.Time `json:"updated,omitempty"`
|
||||
|
||||
*Permissions `json:",inline,omitempty"`
|
||||
}
|
||||
|
||||
// ProjectOwner is a compact project reference used by Milestone/Task.
|
||||
type ProjectOwner struct {
|
||||
ID *int `json:"id,omitempty"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Status *int `json:"status,omitempty"`
|
||||
IsPrivate *bool `json:"isPrivate,omitempty"`
|
||||
}
|
||||
|
||||
// NewProjectRequest is the payload for CreateProject.
|
||||
type NewProjectRequest struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
ResponsibleID string `json:"responsibleId"`
|
||||
}
|
||||
|
||||
// ProjectUpdateRequest is the payload for UpdateProject. Only non-empty
|
||||
// fields are transmitted (enforced by omitempty).
|
||||
type ProjectUpdateRequest struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ResponsibleID string `json:"responsibleId,omitempty"`
|
||||
}
|
||||
|
||||
// GetProjects returns all projects, including private ones the caller can see.
|
||||
func (c *Client) GetProjects() (list Projects, err error) {
|
||||
return list, c.Query(Request{Uri: `/api/2.0/project/filter.json?simple=true`},
|
||||
&struct {
|
||||
MetaResponse `json:",inline"`
|
||||
Response *Projects
|
||||
}{Response: &list})
|
||||
}
|
||||
|
||||
// GetProjectByID returns a single project as an untyped map. The typed
|
||||
// counterpart is not currently provided; callers can iterate GetProjects and
|
||||
// match by title or write their own typed wrapper.
|
||||
//
|
||||
// When projectID is empty the configured default is used.
|
||||
func (c *Client) GetProjectByID(ctx context.Context, projectID string) (map[string]any, error) {
|
||||
if projectID == "" {
|
||||
projectID = c.defaults.ProjectID
|
||||
}
|
||||
return c.ResponseObject(ctx, fmt.Sprintf("/api/2.0/project/%s.json", url.PathEscape(projectID)))
|
||||
}
|
||||
|
||||
// GetProjectMilestones returns milestones for the given project.
|
||||
// https://api1.onlyoffice.com/portals/method/project/post/api/2.0/project/%7bid%7d/milestone
|
||||
func (c *Client) GetProjectMilestones(project *Project) ([]*Milestone, error) {
|
||||
var list []*Milestone
|
||||
err := c.Query(Request{Uri: fmt.Sprintf(`/api/2.0/project/%d/milestone`, *project.ID)},
|
||||
&struct {
|
||||
MetaResponse `json:",inline"`
|
||||
Response *[]*Milestone
|
||||
}{Response: &list})
|
||||
return list, err
|
||||
}
|
||||
|
||||
// CreateProject creates a new project.
|
||||
// - if ResponsibleID is empty, the first user matching the client's User
|
||||
// email is picked; failing that, the first portal user.
|
||||
func (c *Client) CreateProject(np NewProjectRequest) (*Project, error) {
|
||||
if np.ResponsibleID == "" {
|
||||
users, err := c.GetUsers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, u := range users {
|
||||
if u.Email != nil && *u.Email == c.credentials.User {
|
||||
np.ResponsibleID = *u.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
if np.ResponsibleID == "" && len(users) > 0 && users[0].ID != nil {
|
||||
np.ResponsibleID = *users[0].ID
|
||||
}
|
||||
}
|
||||
|
||||
prj := new(Project)
|
||||
return prj, c.Query(Request{
|
||||
Uri: "/api/2.0/project.json",
|
||||
Method: "POST",
|
||||
Body: np,
|
||||
}, &struct {
|
||||
MetaResponse `json:",inline"`
|
||||
Response *Project `json:"response"`
|
||||
}{
|
||||
Response: prj,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteProject deletes a project by numeric ID.
|
||||
func (c *Client) DeleteProject(id int) (*Project, error) {
|
||||
p := &Project{}
|
||||
return p, c.Query(
|
||||
Request{
|
||||
Uri: fmt.Sprintf("/api/2.0/project/%d.json", id),
|
||||
Method: "DELETE",
|
||||
},
|
||||
&struct {
|
||||
Response *Project `json:"response"`
|
||||
}{p})
|
||||
}
|
||||
|
||||
// UpdateProject updates project fields.
|
||||
func (c *Client) UpdateProject(req ProjectUpdateRequest) (*Project, error) {
|
||||
p := &Project{}
|
||||
return p, c.Query(Request{
|
||||
Uri: fmt.Sprintf("/api/2.0/project/%d.json", req.ID),
|
||||
Method: "PUT",
|
||||
Body: req,
|
||||
},
|
||||
&struct {
|
||||
Response *Project `json:"response"`
|
||||
}{p})
|
||||
}
|
||||
Reference in New Issue
Block a user