Initial release: OnlyOffice Project Management API client
This commit is contained in:
@@ -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.
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
@@ -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})
|
||||
}
|
||||
Reference in New Issue
Block a user