Initial release: OnlyOffice Project Management API client

This commit is contained in:
2026-02-13 13:16:36 +01:00
commit e939bc5c9d
7 changed files with 998 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Example Author
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+93
View File
@@ -0,0 +1,93 @@
# go-onlyoffice
Go client library for the [OnlyOffice](https://www.onlyoffice.com/) Project Management API.
## Features
- Token-based authentication with automatic renewal
- Project CRUD operations (create, read, update, delete)
- Task management (create, update, list, filter)
- Milestone management
- User listing
- Query parameter serialization via struct tags
## Installation
```bash
go get github.com/eslider/go-onlyoffice
```
## Usage
```go
package main
import (
"fmt"
"log"
onlyoffice "github.com/eslider/go-onlyoffice"
)
func main() {
client := onlyoffice.NewClient(onlyoffice.Credentials{
Url: "https://your-onlyoffice.example.com",
User: "admin@example.com",
Password: "your-password",
})
// List projects
projects, err := client.GetProjects()
if err != nil {
log.Fatal(err)
}
for _, p := range projects {
fmt.Printf("Project: %s (ID: %d)\n", *p.Title, *p.ID)
}
}
```
Or load credentials from environment variables:
```go
client := onlyoffice.NewClient(onlyoffice.GetEnvironmentCredentials())
```
## Environment Variables
| Variable | Description |
|---|---|
| `ONLYOFFICE_URL` | OnlyOffice instance URL |
| `ONLYOFFICE_USER` | Login email or username |
| `ONLYOFFICE_PASS` | Password |
## API Reference
### Client
- `NewClient(credentials)` - Create a new client
- `GetEnvironmentCredentials()` - Load credentials from env vars
- `Auth(credentials)` - Authenticate and get token
- `Query(request, result)` - Execute an API request
### Projects
- `GetProjects()` - List all projects
- `CreateProject(req)` - Create a new project
- `UpdateProject(req)` - Update a project
- `DeleteProject(id)` - Delete a project
- `GetProjectMilestones(project)` - Get milestones for a project
### Tasks
- `GetTasks(req)` - List tasks with filtering
- `CreateProjectTask(req)` - Create a new task
- `UpdateProjectTask(req)` - Update a task
### Users
- `GetUsers()` - List all users
## License
[MIT](LICENSE)
+128
View File
@@ -0,0 +1,128 @@
package onlyoffice
import (
"os"
"strings"
"testing"
"time"
"github.com/joho/godotenv"
)
// Load environment variables
func init() {
// Load from .env file in the project root
_ = godotenv.Load(".env")
}
func skipWithoutCredentials(t *testing.T) {
t.Helper()
if os.Getenv("ONLYOFFICE_URL") == "" {
t.Skip("ONLYOFFICE_URL is not set, skipping integration test")
}
}
func TestNewClient(t *testing.T) {
skipWithoutCredentials(t)
// Create a new OnlyOffice client
credentials := GetEnvironmentCredentials()
client := NewClient(credentials)
var token *Token
var err error
token, err = client.Auth(&credentials)
if err != nil {
t.Errorf("Failed to get token: %v", err)
}
if token == nil {
t.Error("Value is empty")
}
// Clean up: remove all projects start with "Test project"
projects, err := client.GetProjects()
if err != nil {
t.Errorf("Failed to get projects: %v", err)
}
for _, project := range projects {
if strings.HasPrefix(*project.Title, "Test project") {
prjStatus, err := client.DeleteProject(*project.ID)
if err != nil {
t.Errorf("Failed to delete project: %v", err)
}
if *prjStatus.ID != *project.ID {
t.Errorf("Project ID is not equal: %v != %v", prjStatus.ID, project.ID)
}
}
}
// Test create project
project, err := client.CreateProject(NewProjectRequest{
Title: "Test project",
Description: "Test project description",
})
if err != nil {
t.Errorf("Failed to create project: %v", err)
}
// Update project
project, err = client.UpdateProject(ProjectUpdateRequest{
ID: *project.ID,
Title: "Test project updated",
ResponsibleID: *project.Responsible.ID,
})
var task *Task
// Test create project task
task, err = client.CreateProjectTask(NewProjectTaskRequest{
ProjectId: *project.ID,
Title: "Test task",
Description: "Test task description",
Notify: true,
MilestoneId: 0,
Priority: 0,
// Deadline +2 days
StartDate: Time(time.Now().AddDate(0, 0, -2)),
Deadline: Time(time.Now().AddDate(0, 0, 2)),
})
// Update project task
startDate := Time(time.Now().AddDate(0, 0, -14))
deadline := Time(time.Now().AddDate(0, 0, 3))
task, err = client.UpdateProjectTask(ProjectTaskUpdateRequest{
ID: *task.ID,
Title: "Test task updated",
Description: "Test task description updated",
StartDate: &startDate,
Deadline: &deadline,
})
if err != nil {
t.Errorf("Failed to create task: %v", err)
}
if task == nil {
t.Error("Value is empty")
}
// Delete project
prj, err := client.DeleteProject(*project.ID)
if err != nil {
t.Errorf("Failed to delete project: %v", err)
}
if *prj.ID != *project.ID {
t.Errorf("Project ID is not equal: %v != %v", prj.ID, project.ID)
}
// Test list projects
projects, err = client.GetProjects()
if err != nil {
t.Errorf("Failed to get projects: %v", err)
}
if len(projects) == 0 {
t.Error("Value is empty")
}
}
+92
View File
@@ -0,0 +1,92 @@
// Package main demonstrates basic usage of the onlyoffice client library.
//
// Set environment variables before running:
//
// export ONLYOFFICE_URL="https://your-instance.onlyoffice.com"
// export ONLYOFFICE_USER="admin@example.com"
// export ONLYOFFICE_PASS="your-password"
// go run ./examples/basic/
package main
import (
"fmt"
"log"
"os"
"time"
onlyoffice "github.com/eslider/go-onlyoffice"
)
func main() {
// Load credentials from environment
creds := onlyoffice.GetEnvironmentCredentials()
if creds.Url == "" {
fmt.Fprintln(os.Stderr, "ONLYOFFICE_URL is not set")
os.Exit(1)
}
client := onlyoffice.NewClient(creds)
// List all projects
projects, err := client.GetProjects()
if err != nil {
log.Fatalf("Failed to get projects: %v", err)
}
fmt.Printf("Found %d projects:\n", len(projects))
for _, p := range projects {
fmt.Printf(" - [%d] %s (tasks: %d)\n", *p.ID, *p.Title, safeInt(p.TaskCountTotal))
}
// List users
users, err := client.GetUsers()
if err != nil {
log.Fatalf("Failed to get users: %v", err)
}
fmt.Printf("\nFound %d users:\n", len(users))
for _, u := range users {
fmt.Printf(" - %s (%s)\n", safeStr(u.DisplayName), safeStr(u.Email))
}
// Create a demo project (uncomment to test)
// project, err := client.CreateProject(onlyoffice.NewProjectRequest{
// Title: "Demo Project",
// Description: "Created via API example",
// })
// if err != nil {
// log.Fatalf("Failed to create project: %v", err)
// }
// fmt.Printf("\nCreated project: %s (ID: %d)\n", *project.Title, *project.ID)
// Create a task in first project (uncomment to test)
// if len(projects) > 0 {
// task, err := client.CreateProjectTask(onlyoffice.NewProjectTaskRequest{
// Title: "Demo Task",
// Description: "Created via API example",
// ProjectId: *projects[0].ID,
// StartDate: onlyoffice.Time(time.Now()),
// Deadline: onlyoffice.Time(time.Now().AddDate(0, 0, 7)),
// })
// if err != nil {
// log.Fatalf("Failed to create task: %v", err)
// }
// fmt.Printf("Created task: %s (ID: %d)\n", *task.Title, *task.ID)
// }
_ = time.Now // suppress unused import if examples are commented out
}
func safeStr(s *string) string {
if s == nil {
return "<nil>"
}
return *s
}
func safeInt(i *int) int {
if i == nil {
return 0
}
return *i
}
+8
View File
@@ -0,0 +1,8 @@
module github.com/eslider/go-onlyoffice
go 1.22.2
require (
github.com/google/go-querystring v1.2.0
github.com/joho/godotenv v1.5.1
)
+6
View File
@@ -0,0 +1,6 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+650
View File
@@ -0,0 +1,650 @@
package onlyoffice
// OnlyOffice client package
import (
"encoding/json"
"fmt"
"github.com/google/go-querystring/query"
"io"
"net/http"
"os"
regexp "regexp"
"strings"
"time"
)
// NewClient API
func NewClient(c Credentials) *Client {
return &Client{
client: http.DefaultClient,
credentials: &c,
}
}
// GetEnvironmentCredentials when using environment variables
func GetEnvironmentCredentials() Credentials {
return Credentials{
Url: os.Getenv("ONLYOFFICE_URL"),
User: os.Getenv("ONLYOFFICE_USER"),
Password: os.Getenv("ONLYOFFICE_PASS"),
}
}
// Credentials of OnlyOffice User
type Credentials struct {
Url string `json:"-"`
User string `json:"userName"`
Password string `json:"password"`
}
// ToJson for Credentials
func (c Credentials) ToJson() []byte {
b, err := json.Marshal(c)
if err != nil {
return nil
}
return b
}
// Client of OnlyOffice API uses credentials to get a token and query the API by every request
type Client struct {
client *http.Client // HTTP client
credentials *Credentials // OnlyOffice credentials
token *Token // Authentication token
}
// MetaResponse Response
type MetaResponse struct {
Count int `json:"count"`
Total int `json:"total"`
Status int `json:"status"`
StatusCode int `json:"statusCode"`
}
type Permissions struct {
CanEdit *bool `json:"canEdit,omitempty"` // Is user can edit
CanDelete *bool `json:"canDelete,omitempty"` // Is user can delete
}
// 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"`
}
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"`
}
type User struct {
ID *string `json:"id,omitempty"`
UserName *string `json:"userName,omitempty"`
IsVisitor *bool `json:"isVisitor,omitempty"`
FirstName *string `json:"firstName,omitempty"`
LastName *string `json:"lastName,omitempty"`
Email *string `json:"email,omitempty"`
Status *int `json:"status,omitempty"`
ActivationStatus *int `json:"activationStatus,omitempty"`
Terminated any `json:"terminated,omitempty"`
Department *string `json:"department,omitempty"`
WorkFrom *time.Time `json:"workFrom,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
AvatarMedium *string `json:"avatarMedium,omitempty"`
Avatar *string `json:"avatar,omitempty"`
IsAdmin *bool `json:"isAdmin,omitempty"`
IsLDAP *bool `json:"isLDAP,omitempty"`
ListAdminModules []string `json:"listAdminModules,omitempty"`
IsOwner *bool `json:"isOwner,omitempty"`
CultureName *string `json:"cultureName,omitempty"`
IsSSO *bool `json:"isSSO,omitempty"`
AvatarSmall *string `json:"avatarSmall,omitempty"`
QuotaLimit *int `json:"quotaLimit,omitempty"`
UsedSpace *int `json:"usedSpace,omitempty"`
DocsSpace *int `json:"docsSpace,omitempty"`
MailSpace *int `json:"mailSpace,omitempty"`
TalkSpace *int `json:"talkSpace,omitempty"`
ProfileURL *string `json:"profileUrl,omitempty"`
Title *string `json:"title,omitempty"`
Sex *string `json:"sex,omitempty"`
Lead *string `json:"lead,omitempty"`
Birthday *time.Time `json:"birthday,omitempty"`
Location *string `json:"location,omitempty"`
Notes *string `json:"notes,omitempty"`
Contacts []Contact `json:"contacts,omitempty"`
Groups []Group `json:"groups,omitempty"`
}
type Contact struct {
Type *string `json:"type,omitempty"`
Value *string `json:"value,omitempty"`
}
type Group struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Manager interface{} `json:"manager,omitempty"`
}
type Task struct {
ID *int `json:"id,omitempty"`
Title *string `json:"title,omitempty"`
StartDate *time.Time `json:"startDate,omitempty"`
Deadline *time.Time `json:"deadline,omitempty"`
Description *string `json:"description,omitempty"`
Priority *int `json:"priority,omitempty"`
ProjectOwner *ProjectOwner `json:"projectOwner,omitempty"`
Subtasks []any `json:"subtasks,omitempty"`
Status *ProjectTaskStatus `json:"status,omitempty"`
Created *time.Time `json:"created,omitempty"`
CreatedBy *User `json:"createdBy,omitempty"`
CreatedByID *string `json:"createdById,omitempty"` // UUID
Updated *time.Time `json:"updated,omitempty"`
UpdatedBy *User `json:"updatedBy,omitempty"`
UpdatedById *string `json:"updatedById,omitempty"` // UUID
Responsibles []*User `json:"responsibles,omitempty"`
ResponsibleIDS []string `json:"responsibleIds,omitempty"` // UUID list
CanEdit *bool `json:"canEdit,omitempty"`
CanCreateSubtask *bool `json:"canCreateSubtask,omitempty"`
CanCreateTimeSpend *bool `json:"canCreateTimeSpend,omitempty"`
CanDelete *bool `json:"canDelete,omitempty"`
CanReadFiles *bool `json:"canReadFiles,omitempty"`
MilestoneID *int64 `json:"milestoneId,omitempty"`
Milestone *Milestone `json:"milestone,omitempty"`
}
var GiteaIssue2OnlyOfficeMappingRegExp = regexp.MustCompile(`URL:(.*)$`)
var TestReg = `
URL:https://git.markets-platform.com/TradePlatform/email-templates/issues/1`
// GetGiteaIssueLink from task description
func (t *Task) GetGiteaIssueLink() string {
// Get issue from description
if t.Description != nil {
var match = GiteaIssue2OnlyOfficeMappingRegExp.FindStringSubmatch(*t.Description)
if len(match) > 1 {
return strings.TrimSpace(match[1])
}
}
return ""
}
// TaskPriority High = 1, Normal = 0, Low = -1
type TaskPriority int
const (
TaskPriorityHigh TaskPriority = 1 // High
TaskPriorityNormal TaskPriority = 0 // Normal
TaskPriorityLow TaskPriority = -1
)
type ProjectOwner struct {
ID *int `json:"id,omitempty"`
Title *string `json:"title,omitempty"`
Status *int `json:"status,omitempty"`
IsPrivate *bool `json:"isPrivate,omitempty"`
}
// String for Project to return title
func (p Project) String() string {
return fmt.Sprintf(*p.Title)
}
// Token OnlyOffice
type Token struct {
Value string `json:"token"`
Expires Time `json:"expires"`
}
// Time for OnlyOffice
type Time time.Time
// To String for Time
func (t Time) String() string {
return time.Time(t).Format("2006-01-02T15:04:05")
}
func (t Time) Before(u Time) bool {
return time.Time(t).Before(time.Time(u))
}
func (t Time) After(u Time) bool {
return time.Time(t).After(time.Time(u))
}
// UnmarshalJSON for Time
func (r *Time) UnmarshalJSON(data []byte) error {
// Trim quotes
data = data[1 : len(data)-1]
t, err := time.Parse("2006-01-02T15:04:05.0000000-07:00", string(data))
if err != nil {
return err
}
*r = Time(t)
return nil
}
// Request for OnlyOffice API
type Request struct {
Uri string // URI is the path to the API endpoint e.g. /api/2.0/project.json
Method string // Method is the HTTP method e.g. GET, POST, PUT, DELETE
Params any // Params is the query parameters
Body any // Body could be a struct, map, []byte, or string. It will be marshaled to JSON
Token *string // Token is the Authorization header, if not set then it will get a token
NoAuth bool // NoAuth for skipping automatic authentication
Debug bool
}
// GetMethod for Request
func (r Request) GetMethod() string {
if r.Method == "" {
return "GET"
}
return r.Method
}
// Query the OnlyOffice API
// - If request.Method is not set then it will default to GET
// - If request.Code is not nil then it will be marshaled to JSON
// - If request.Token is nil then it will get a token
// - If request.Token is not nil then it will be used as Authorization header
// - If request.NoAuth is true then it will skip automatic authentication
func (c *Client) Query(request Request, result interface{}) (err error) {
var url = fmt.Sprintf("%s%s", c.credentials.Url, request.Uri)
var rdr io.Reader = nil
var jsonRequestBody string
// Add query parameters if available
if request.Params != nil {
v, err := query.Values(request.Params)
if err != nil {
return err
}
url = fmt.Sprintf("%s?%s", url, v.Encode())
}
// Create request
if request.Body != nil {
// Check request body if type is not []byte or string the marshal it to JSON
switch request.Body.(type) {
case []byte:
jsonRequestBody = string(request.Body.([]byte))
rdr = strings.NewReader(string(request.Body.([]byte)))
case string:
jsonRequestBody = request.Body.(string)
rdr = strings.NewReader(request.Body.(string))
default:
// Marshal to JSON
b, err := json.Marshal(request.Body)
if err != nil {
return fmt.Errorf("failed to marshal request body: %v", err)
}
jsonRequestBody = string(b)
rdr = strings.NewReader(string(b))
}
}
req, err := http.NewRequest(request.GetMethod(), url, rdr)
if err != nil {
return err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Pragma", "no-cache")
// Get token if not set?
if !request.NoAuth {
// Get token if not set or expired
if c.token == nil || time.Time(c.token.Expires).Before(time.Now()) {
c.token, err = c.Auth(c.credentials)
if err != nil {
return fmt.Errorf("failed to authenticate: %v", err)
}
}
}
// Set token if available
if request.Token != nil {
req.Header.Set("Authorization", *request.Token)
} else {
// Set token from a client if available
if c.token != nil {
req.Header.Set("Authorization", c.token.Value)
}
}
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
if result == nil {
return nil
}
if request.Debug {
// Rewind reader
_ = jsonRequestBody
var buf = new(strings.Builder)
io.Copy(buf, resp.Body)
js := buf.String()
return json.Unmarshal([]byte(js), result)
} else {
// Unmarshal response using reader
return json.NewDecoder(resp.Body).Decode(result)
}
}
// Auth to authenticate by getting a token using credentials
func (c *Client) Auth(creds *Credentials) (t *Token, err error) {
t = &Token{}
return t, c.Query(Request{
Uri: "/api/2.0/authentication.json",
Method: "POST",
Body: creds,
NoAuth: true,
}, &struct { // Unmarshal response into a struct
MetaResponse `json:",inline"`
Response *Token `json:"response"`
}{
Response: t,
})
}
// Projects list
type Projects []*Project
// Get Project by title
func (p Projects) Get(title string) *Project {
for _, prj := range p {
if *prj.Title == title {
return prj
}
}
return nil
}
// GetProjects
// - Get all projects, not excluding private projects
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})
}
// GetProjectMilestones Get project milestones
// 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), Debug: false},
&struct {
MetaResponse `json:",inline"`
//Response *[]*Milestone
Response *[]*Milestone
}{Response: &list})
return list, err
}
// GetUsers List users
func (c *Client) GetUsers() (list []*User, err error) {
return list, c.Query(Request{Uri: "/api/2.0/people/filter.json"},
&struct {
MetaResponse `json:",inline"`
Response *[]*User `json:"response"`
}{Response: &list})
}
// NewProjectRequest
type NewProjectRequest struct {
Title string `json:"title"`
Description string `json:"description"`
ResponsibleID string `json:"responsibleId"`
}
// ProjectUpdateRequest
type ProjectUpdateRequest struct {
ID int `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
ResponsibleID string `json:"responsibleId,omitempty"`
}
// NewProjectTaskRequest
type NewProjectTaskRequest struct {
Title string `url:"title"`
Description string `url:"description"`
Notify bool `url:"notify"`
MilestoneId int `url:"milestoneId"`
Priority int `url:"priority"`
ProjectId int `url:"projectId"`
StartDate Time `url:"startDate"` // 2024-04-16T00:00:00
Deadline Time `url:"deadline"` // 2024-04-20T00:00:00
Status ProjectTaskStatus
}
type ProjectTaskStatus int
const (
ProjectTaskStatusNotAccept ProjectTaskStatus = 0
ProjectTaskStatusOpen ProjectTaskStatus = 1
ProjectTaskStatusClosed ProjectTaskStatus = 2
ProjectTaskStatusDisable ProjectTaskStatus = 3
ProjectTaskStatusUnclassified ProjectTaskStatus = 4
ProjectTaskStatusNotInMilestone ProjectTaskStatus = 5
)
// ProjectTaskUpdateRequest - Update a task
// See: https://api1.onlyoffice.com/portals/method/project/put/api/2.0/project/task/%7btaskid%7d
type ProjectTaskUpdateRequest struct {
ID int `json:"id"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Priority *int `json:"priority,omitempty"` // optional, New task priority {"0"} High = 1, Normal = 0, Low = -1
StartDate *Time `json:"startDate,omitempty"`
Deadline *Time `json:"deadline,omitempty"`
//Deadline *Time `json:"closed,omitempty"`
ProjectID *int64 `json:"projectID,omitempty"` // New task project ID (optional)
MilestoneId *int64 `json:"milestoneid,omitempty"`
Responsible []string `json:"responsibles,omitempty"` // New list of task responsibles {"9924256A-739C-462b-AF15-E652A3B1B6EB"}
Notify bool `json:"notify,omitempty"`
Status ProjectTaskStatus `json:"status,omitempty"`
//Progress int64 `json:"progress,omitempty"` // New task progress
}
// MarshalJSON to quote Time as string
func (t Time) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(
`"%s"`,
time.Time(t).Format("2006-01-02T15:04:05"),
)), nil
}
// CreateProject with a new project request
// - if responsibleID is empty then it will use the first user as responsible
// - if responsibleID is empty and user is not found then it will use the current user
func (c *Client) CreateProject(np NewProjectRequest) (prj *Project, err error) {
// We need a responsible user
if np.ResponsibleID == "" {
var users []*User
users, err = c.GetUsers()
if err != nil {
return
}
// Find user by email
for _, user := range users {
if *user.Email == c.credentials.User {
np.ResponsibleID = *user.ID
break
}
}
// Has no user?
if np.ResponsibleID == "" {
// Set first user as responsible
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,
})
}
func (c *Client) CreateProjectTask(req NewProjectTaskRequest) (task *Task, err error) {
task = &Task{}
return task, c.Query(Request{
Uri: fmt.Sprintf("/api/2.0/project/%d/task.json", req.ProjectId),
Method: "POST",
Body: req,
}, &struct {
//MetaResponse `json:",inline"`
Response *Task `json:"response"`
}{task})
}
func (c *Client) DeleteProject(id int) (p *Project, err error) {
p = &Project{}
return p, c.Query(
Request{
Uri: fmt.Sprintf("/api/2.0/project/%d.json", id),
Method: "DELETE"},
&struct {
//MetaResponse `json:",inline"`
Response *Project `json:"response"`
}{p})
}
func (c *Client) UpdateProject(req ProjectUpdateRequest) (p *Project, err error) {
p = &Project{}
return p, c.Query(Request{
Uri: fmt.Sprintf("/api/2.0/project/%d.json", req.ID),
Method: "PUT",
Body: req,
},
&struct {
//MetaResponse `json:",inline"`
Response *Project `json:"response"`
}{p})
}
func (c *Client) UpdateProjectTask(req ProjectTaskUpdateRequest) (task *Task, err error) {
task = &Task{}
return task, c.Query(
Request{
Uri: fmt.Sprintf("/api/2.0/project/task/%d.json", req.ID),
Method: "PUT",
Body: req,
Debug: true,
}, &struct {
//MetaResponse `json:",inline"`
Response *Task `json:"response"`
}{task})
}
// ProjectGetTasksRequest
// See: https://api.onlyoffice.com/workspace/api-backend/usage-api/project/tasks/get-tasks-by-status/
type ProjectGetTasksRequest struct {
ProjectId int `url:"projectId"`
Count int `url:"count"`
StartIndex int `url:"startIndex"`
SortBy string `url:"sortBy"`
SortOrder string `url:"sortOrder"`
Simple bool `url:"simple"`
}
// NewProjectGetTasksRequest to get a simple task list
func NewProjectGetTasksRequest(projectId int) ProjectGetTasksRequest {
return ProjectGetTasksRequest{
ProjectId: projectId,
Count: 1000,
StartIndex: 0,
SortBy: "title",
SortOrder: "ascending",
Simple: true,
}
}
// GetTasks returns a list of all the tasks from a project with the ID specified in the request.
func (c *Client) GetTasks(req ProjectGetTasksRequest) (tasks []*Task, err error) {
return tasks, c.Query(
Request{
Uri: "/api/2.0/project/task/filter.json",
Params: req,
Debug: true,
},
&struct {
//MetaResponse `json:",inline"`
Response *[]*Task `json:"response"`
}{&tasks})
}