projects

package
v1.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2025 License: MIT Imports: 9 Imported by: 0

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Action added in v1.2.0

type Action string

Action contains all possible activity types.

const (
	LogTypeNew       Action = "new"
	LogTypeEdited    Action = "edited"
	LogTypeCompleted Action = "completed"
	LogTypeReopened  Action = "reopened"
	LogTypeDeleted   Action = "deleted"
	LogTypeUndeleted Action = "undeleted"
	LogTypeLiked     Action = "liked"
	LogTypeReacted   Action = "reacted"
	LogTypeViewed    Action = "viewed"
)

List of activity types.

type Activity added in v1.2.0

type Activity struct {
	// ID is the unique identifier of the activity.
	ID int64 `json:"id"`

	// Action is the type of activity that occurred.
	Action Action `json:"activityType"`

	// LatestAction is the most recent activity that occurred.
	LatestAction Action `json:"latestActivityType"`

	// At is the timestamp when the activity occurred.
	At time.Time `json:"dateTime"`

	// Description is a brief summary of the activity.
	Description *string `json:"description"`

	// ExtraDescription provides additional context about the activity.
	ExtraDescription *string `json:"extraDescription"`

	// PublicInfo provides information about the activity that is visible to all
	// users.
	PublicInfo *string `json:"publicInfo"`

	// DueAt is the deadline for the activity.
	DueAt *time.Time `json:"dueDate"`

	// ForUserName is the name of the user for whom the activity is intended.
	ForUserName *string `json:"forUserName"`

	// ItemLink provides a link to the item associated with the activity.
	ItemLink *string `json:"itemLink"`

	// Link provides a link to the activity itself.
	Link *string `json:"link"`

	// User is the relationship to the user that this activity is associated with.
	User twapi.Relationship `json:"user"`

	// ForUser is the relationship to the user for whom the activity is intended.
	ForUser *twapi.Relationship `json:"forUser"`

	// Project is the relationship to the project that this activity belongs to.
	Project twapi.Relationship `json:"project"`

	// Company is the relationship to the client/company that this activity is
	// associated with.
	Company twapi.Relationship `json:"company"`

	// Item is the relationship to the item that this activity is associated with.
	Item twapi.Relationship `json:"item"`
}

Activity is a record of actions and updates that occur across your projects, tasks, and communications, giving you a clear view of what’s happening within your workspace. Activities capture changes such as task completions, activities added, files uploaded, or milestones updated, and present them in a chronological feed so teams can stay aligned without needing to check each individual project or task. This stream of information helps improve transparency, ensures accountability, and keeps everyone aware of progress and decisions as they happen.

More information can be found at: https://support.teamwork.com/projects/using-teamwork/activity

type ActivityListRequest added in v1.2.0

type ActivityListRequest struct {
	// Path contains the path parameters for the request.
	Path ActivityListRequestPath

	// Filters contains the filters for loading multiple activities.
	Filters ActivityListRequestFilters
}

ActivityListRequest represents the request body for loading multiple activities.

https://apidocs.teamwork.com/docs/teamwork/v3/activity/get-projects-api-v3-latestactivity-json https://apidocs.teamwork.com/docs/teamwork/v3/activity/get-projects-api-v3-projects-project-id-latestactivity

func NewActivityListRequest added in v1.2.0

func NewActivityListRequest() ActivityListRequest

NewActivityListRequest creates a new ActivityListRequest with default values.

func (ActivityListRequest) HTTPRequest added in v1.2.0

func (a ActivityListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the ActivityListRequest.

type ActivityListRequestFilters added in v1.2.0

type ActivityListRequestFilters struct {
	// StartDate is the start date for filtering activities.
	StartDate time.Time

	// EndDate is the end date for filtering activities.
	EndDate time.Time

	// LogItemTypes is the list of log item types to filter activities.
	LogItemTypes []LogItemType

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of activities to retrieve per page. Defaults to 50.
	PageSize int64
}

ActivityListRequestFilters contains the filters for loading multiple activities.

type ActivityListRequestPath added in v1.2.0

type ActivityListRequestPath struct {
	// ProjectID is the unique identifier of the project containing the
	// activities.
	ProjectID int64
}

ActivityListRequestPath contains the path parameters for loading multiple activities.

type ActivityListResponse added in v1.2.0

type ActivityListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Activities []Activity `json:"activities"`
	// contains filtered or unexported fields
}

ActivityListResponse contains information by multiple activities matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/activity/get-projects-api-v3-latestactivity-json https://apidocs.teamwork.com/docs/teamwork/v3/activity/get-projects-api-v3-projects-project-id-latestactivity

func ActivityList added in v1.2.0

func ActivityList(
	ctx context.Context,
	engine *twapi.Engine,
	req ActivityListRequest,
) (*ActivityListResponse, error)

ActivityList retrieves multiple activities using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startActivityServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	activitiesRequest := projects.NewActivityListRequest()
	activitiesRequest.Filters.LogItemTypes = []projects.LogItemType{projects.LogItemTypeComment}

	activitiesResponse, err := projects.ActivityList(ctx, engine, activitiesRequest)
	if err != nil {
		fmt.Printf("failed to list activities: %s", err)
	} else {
		for _, activity := range activitiesResponse.Activities {
			fmt.Printf("retrieved activity with identifier %d\n", activity.ID)
		}
	}

}

func startActivityServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /projects/api/v3/latestactivity", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"activities":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved activity with identifier 12345
retrieved activity with identifier 12346

func (*ActivityListResponse) HandleHTTPResponse added in v1.2.0

func (a *ActivityListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the ActivityListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*ActivityListResponse) Iterate added in v1.2.0

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*ActivityListResponse) SetRequest added in v1.2.0

func (a *ActivityListResponse) SetRequest(req ActivityListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type BillableRate added in v1.5.0

type BillableRate struct {
	// Rate is the billable rate amount.
	Rate float64 `json:"rate"`

	// Currency is the currency information.
	Currency twapi.Relationship `json:"currency"`
}

BillableRate contains the rate and currency information for billable amounts.

type Comment added in v0.6.0

type Comment struct {
	// ID is the unique identifier of the comment.
	ID int64 `json:"id"`

	// Body is the body of the comment.
	Body string `json:"body"`

	// HTMLBody is the HTML representation of the comment body.
	HTMLBody string `json:"htmlBody"`

	// ContentType is the content type of the comment body. It can be "TEXT" or
	// "HTML".
	ContentType string `json:"contentType"`

	// Object is the relationship to the object (task, milestone, project) that
	// this comment is associated with.
	Object *twapi.Relationship `json:"object"`

	// Project is the relationship to the project that this comment belongs to.
	Project twapi.Relationship `json:"project"`

	// PostedBy is the ID of the user who posted the comment.
	PostedBy *int64 `json:"postedBy"`

	// PostedAt is the date and time when the comment was posted.
	PostedAt *time.Time `json:"postedDateTime"`

	// LastEditedBy is the ID of the user who last edited the comment, if it was
	// edited.
	LastEditedBy *int64 `json:"lastEditedBy"`

	// EditedAt is the date and time when the comment was last edited, if it was
	// edited.
	EditedAt *time.Time `json:"dateLastEdited"`

	// Deleted indicates whether the comment has been deleted.
	Deleted bool `json:"deleted"`

	// DeletedBy is the ID of the user who deleted the comment, if it was deleted.
	DeletedBy *int64 `json:"deletedBy"`

	// DeletedAt is the date and time when the comment was deleted, if it was
	// deleted.
	DeletedAt *time.Time `json:"dateDeleted"`
}

Comment is a way for users to communicate and collaborate directly within tasks, milestones, files, or other project items. Comments allow team members to provide updates, ask questions, give feedback, or share relevant information in a centralized and contextual manner. They support rich text formatting, file attachments, and @mentions to notify specific users or teams, helping keep discussions organized and easily accessible within the project. Comments are visible to all users with access to the item, promoting transparency and keeping everyone aligned.

More information can be found at: https://support.teamwork.com/projects/getting-started/comments-overview

type CommentCreateRequest added in v0.6.0

type CommentCreateRequest struct {
	// Path contains the path parameters for the request.
	Path CommentCreateRequestPath `json:"-"`

	// Body is the body of the comment. It can contain plain text or HTML.
	Body string `json:"body"`

	// ContentType is the content type of the comment body. It can be "TEXT" or
	// "HTML". If not provided, it defaults to "TEXT".
	ContentType *string `json:"contentType,omitempty"`
}

CommentCreateRequest represents the request body for creating a new comment.

https://apidocs.teamwork.com/docs/teamwork/v1/comments/post-resource-resource-id-comments-json

func NewCommentCreateRequestInFileVersion added in v0.6.0

func NewCommentCreateRequestInFileVersion(fileVersionID int64, body string) CommentCreateRequest

NewCommentCreateRequestInFileVersion creates a new CommentCreateRequest with the provided file version ID.

func NewCommentCreateRequestInLink(linkID int64, body string) CommentCreateRequest

NewCommentCreateRequestInLink creates a new CommentCreateRequest with the provided link ID.

func NewCommentCreateRequestInMilestone added in v0.6.0

func NewCommentCreateRequestInMilestone(milestoneID int64, body string) CommentCreateRequest

NewCommentCreateRequestInMilestone creates a new CommentCreateRequest with the provided milestone ID.

func NewCommentCreateRequestInNotebook added in v0.6.0

func NewCommentCreateRequestInNotebook(notebookID int64, body string) CommentCreateRequest

NewCommentCreateRequestInNotebook creates a new CommentCreateRequest with the provided notebook ID.

func NewCommentCreateRequestInTask added in v0.6.0

func NewCommentCreateRequestInTask(taskID int64, body string) CommentCreateRequest

NewCommentCreateRequestInTask creates a new CommentCreateRequest with the provided task ID.

func (CommentCreateRequest) HTTPRequest added in v0.6.0

func (t CommentCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CommentCreateRequest.

type CommentCreateRequestPath added in v0.6.0

type CommentCreateRequestPath struct {
	// FileVersionID is the unique identifier of the file version where the
	// comment will be created. Each file can have multiple versions, and the
	// comments are associated with a specific version.
	FileVersionID int64

	// MilestoneID is the unique identifier of the milestone where the comment
	// will be created.
	MilestoneID int64

	// NotebookID is the unique identifier of the notebook where the comment will
	// be created.
	NotebookID int64

	// TaskID is the unique identifier of the task where the comment will be
	// created.
	TaskID int64

	// LinkID is the unique identifier of the link where the comment will be
	// created.
	LinkID int64
}

CommentUpdateRequestPath contains the path parameters for creating a comment.

type CommentCreateResponse added in v0.6.0

type CommentCreateResponse struct {
	// ID is the unique identifier of the created comment.
	ID LegacyNumber `json:"id"`
}

CommentCreateResponse represents the response body for creating a new comment.

https://apidocs.teamwork.com/docs/teamwork/v1/comments/post-resource-resource-id-comments-json

func CommentCreate added in v0.6.0

func CommentCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req CommentCreateRequest,
) (*CommentCreateResponse, error)

CommentCreate creates a new comment using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCommentServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	commentRequest := projects.NewCommentCreateRequestInTask(777, "<h1>New Comment</h1>")
	commentRequest.ContentType = twapi.Ptr("HTML")

	commentResponse, err := projects.CommentCreate(ctx, engine, commentRequest)
	if err != nil {
		fmt.Printf("failed to create comment: %s", err)
	} else {
		fmt.Printf("created comment with identifier %d\n", commentResponse.ID)
	}

}

func startCommentServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /tasks/{id}/comments", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created comment with identifier 12345

func (*CommentCreateResponse) HandleHTTPResponse added in v0.6.0

func (t *CommentCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CommentCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type CommentDeleteRequest added in v0.6.0

type CommentDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path CommentDeleteRequestPath
}

CommentDeleteRequest represents the request body for deleting a comment.

https://apidocs.teamwork.com/docs/teamwork/v1/comments/delete-comments-id-json

func NewCommentDeleteRequest added in v0.6.0

func NewCommentDeleteRequest(commentID int64) CommentDeleteRequest

NewCommentDeleteRequest creates a new CommentDeleteRequest with the provided comment ID.

func (CommentDeleteRequest) HTTPRequest added in v0.6.0

func (t CommentDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CommentDeleteRequest.

type CommentDeleteRequestPath added in v0.6.0

type CommentDeleteRequestPath struct {
	// ID is the unique identifier of the comment to be deleted.
	ID int64
}

CommentDeleteRequestPath contains the path parameters for deleting a comment.

type CommentDeleteResponse added in v0.6.0

type CommentDeleteResponse struct{}

CommentDeleteResponse represents the response body for deleting a comment.

https://apidocs.teamwork.com/docs/teamwork/v1/comments/delete-comments-id-json

func CommentDelete added in v0.6.0

func CommentDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req CommentDeleteRequest,
) (*CommentDeleteResponse, error)

CommentDelete deletes a comment using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCommentServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.CommentDelete(ctx, engine, projects.NewCommentDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete comment: %s", err)
	} else {
		fmt.Println("comment deleted!")
	}

}

func startCommentServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /tasks/{id}/comments", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

comment deleted!

func (*CommentDeleteResponse) HandleHTTPResponse added in v0.6.0

func (t *CommentDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CommentDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type CommentGetRequest added in v0.6.0

type CommentGetRequest struct {
	// Path contains the path parameters for the request.
	Path CommentGetRequestPath
}

CommentGetRequest represents the request body for loading a single comment.

https://apidocs.teamwork.com/docs/teamwork/v3/comments/get-projects-api-v3-comments-id-json

func NewCommentGetRequest added in v0.6.0

func NewCommentGetRequest(commentID int64) CommentGetRequest

NewCommentGetRequest creates a new CommentGetRequest with the provided comment ID. The ID is required to load a comment.

func (CommentGetRequest) HTTPRequest added in v0.6.0

func (t CommentGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CommentGetRequest.

type CommentGetRequestPath added in v0.6.0

type CommentGetRequestPath struct {
	// ID is the unique identifier of the comment to be retrieved.
	ID int64 `json:"id"`
}

CommentGetRequestPath contains the path parameters for loading a single comment.

type CommentGetResponse added in v0.6.0

type CommentGetResponse struct {
	Comment Comment `json:"comments"`
}

CommentGetResponse contains all the information related to a comment.

https://apidocs.teamwork.com/docs/teamwork/v3/comments/get-projects-api-v3-comments-id-json

func CommentGet added in v0.6.0

func CommentGet(
	ctx context.Context,
	engine *twapi.Engine,
	req CommentGetRequest,
) (*CommentGetResponse, error)

CommentGet retrieves a single comment using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCommentServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	commentResponse, err := projects.CommentGet(ctx, engine, projects.NewCommentGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve comment: %s", err)
	} else {
		fmt.Printf("retrieved comment with identifier %d\n", commentResponse.Comment.ID)
	}

}

func startCommentServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /tasks/{id}/comments", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved comment with identifier 12345

func (*CommentGetResponse) HandleHTTPResponse added in v0.6.0

func (t *CommentGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CommentGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type CommentListRequest added in v0.6.0

type CommentListRequest struct {
	// Path contains the path parameters for the request.
	Path CommentListRequestPath

	// Filters contains the filters for loading multiple comments.
	Filters CommentListRequestFilters
}

CommentListRequest represents the request body for loading multiple comments.

https://apidocs.teamwork.com/docs/teamwork/v3/comments/get-projects-api-v3-comments-json https://apidocs.teamwork.com/docs/teamwork/v3/file-version-comments/get-projects-api-v3-fileversions-id-comments-json https://apidocs.teamwork.com/docs/teamwork/v3/milestone-comments/get-projects-api-v3-milestones-milestone-id-comments-json https://apidocs.teamwork.com/docs/teamwork/v3/notebook-comments/get-projects-api-v3-notebooks-notebook-id-comments-json https://apidocs.teamwork.com/docs/teamwork/v3/task-comments/get-projects-api-v3-tasks-task-id-comments-json

func NewCommentListRequest added in v0.6.0

func NewCommentListRequest() CommentListRequest

NewCommentListRequest creates a new CommentListRequest with default values.

func (CommentListRequest) HTTPRequest added in v0.6.0

func (t CommentListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CommentListRequest.

type CommentListRequestFilters added in v0.6.0

type CommentListRequestFilters struct {
	// SearchTerm is an optional search term to filter comments by name, description
	// or commentlist's name.
	SearchTerm string

	// UserIDs is an optional list of user IDs to filter comments by users.
	UserIDs []int64

	// UpdatedAfter is an optional filter to retrieve only comments updated after
	// the specified date and time.
	UpdatedAfter time.Time

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of comments to retrieve per page. Defaults to 50.
	PageSize int64
}

CommentListRequestFilters contains the filters for loading multiple comments.

type CommentListRequestPath added in v0.6.0

type CommentListRequestPath struct {
	// FileVersionID is the unique identifier of the file version whose comments
	// are to be retrieved. Each file can have multiple versions, and the comments
	// are associated with a specific version.
	FileVersionID int64

	// MilestoneID is the unique identifier of the milestone whose comments are to
	// be retrieved.
	MilestoneID int64

	// NotebookID is the unique identifier of the notebook whose comments are to
	// be retrieved.
	NotebookID int64

	// TaskID is the unique identifier of the task whose comments are to be
	// retrieved.
	TaskID int64
}

CommentListRequestPath contains the path parameters for loading multiple comments.

type CommentListResponse added in v0.6.0

type CommentListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Comments []Comment `json:"comments"`
	// contains filtered or unexported fields
}

CommentListResponse contains information by multiple comments matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/file-version-comments/get-projects-api-v3-fileversions-id-comments-json https://apidocs.teamwork.com/docs/teamwork/v3/milestone-comments/get-projects-api-v3-milestones-milestone-id-comments-json https://apidocs.teamwork.com/docs/teamwork/v3/notebook-comments/get-projects-api-v3-notebooks-notebook-id-comments-json https://apidocs.teamwork.com/docs/teamwork/v3/task-comments/get-projects-api-v3-tasks-task-id-comments-json

func CommentList added in v0.6.0

func CommentList(
	ctx context.Context,
	engine *twapi.Engine,
	req CommentListRequest,
) (*CommentListResponse, error)

CommentList retrieves multiple comments using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCommentServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	commentsRequest := projects.NewCommentListRequest()
	commentsRequest.Filters.SearchTerm = "Example"

	commentsResponse, err := projects.CommentList(ctx, engine, commentsRequest)
	if err != nil {
		fmt.Printf("failed to list comments: %s", err)
	} else {
		for _, comment := range commentsResponse.Comments {
			fmt.Printf("retrieved comment with identifier %d\n", comment.ID)
		}
	}

}

func startCommentServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /tasks/{id}/comments", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved comment with identifier 12345
retrieved comment with identifier 12346

func (*CommentListResponse) HandleHTTPResponse added in v0.6.0

func (t *CommentListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CommentListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*CommentListResponse) Iterate added in v0.6.0

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*CommentListResponse) SetRequest added in v0.6.0

func (t *CommentListResponse) SetRequest(req CommentListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type CommentUpdateRequest added in v0.6.0

type CommentUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path CommentUpdateRequestPath `json:"-"`

	// Body is the body of the comment. It can contain plain text or HTML.
	Body string `json:"body"`

	// ContentType is the content type of the comment body. It can be "TEXT" or
	// "HTML". If not provided, it defaults to "TEXT".
	ContentType *string `json:"contentType,omitempty"`
}

CommentUpdateRequest represents the request body for updating a comment. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v1/comments/put-comments-id-json

func NewCommentUpdateRequest added in v0.6.0

func NewCommentUpdateRequest(commentID int64) CommentUpdateRequest

NewCommentUpdateRequest creates a new CommentUpdateRequest with the provided comment ID. The ID is required to update a comment.

func (CommentUpdateRequest) HTTPRequest added in v0.6.0

func (t CommentUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CommentUpdateRequest.

type CommentUpdateRequestPath added in v0.6.0

type CommentUpdateRequestPath struct {
	// ID is the unique identifier of the comment to be updated.
	ID int64
}

CommentUpdateRequestPath contains the path parameters for updating a comment.

type CommentUpdateResponse added in v0.6.0

type CommentUpdateResponse struct{}

CommentUpdateResponse represents the response body for updating a comment.

https://apidocs.teamwork.com/docs/teamwork/v1/comments/put-comments-id-json

func CommentUpdate added in v0.6.0

func CommentUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req CommentUpdateRequest,
) (*CommentUpdateResponse, error)

CommentUpdate updates a comment using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCommentServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	commentRequest := projects.NewCommentUpdateRequest(12345)
	commentRequest.Body = "<h1>Updated Comment</h1>"

	_, err = projects.CommentUpdate(ctx, engine, commentRequest)
	if err != nil {
		fmt.Printf("failed to update comment: %s", err)
	} else {
		fmt.Println("comment updated!")
	}

}

func startCommentServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /tasks/{id}/comments", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/comments", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"comments":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

comment updated!

func (*CommentUpdateResponse) HandleHTTPResponse added in v0.6.0

func (t *CommentUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CommentUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Company added in v0.4.0

type Company struct {
	// ID is the unique identifier of the company.
	ID int64 `json:"id"`

	// AddressOne is the first line of the company's address.
	AddressOne string `json:"addressOne"`

	// AddressTwo is the second line of the company's address.
	AddressTwo string `json:"addressTwo"`

	// City is the city where the company is located.
	City string `json:"city"`

	// CountryCode is the ISO 3166-1 alpha-2 country code where the company is
	// located.
	CountryCode string `json:"countryCode"`

	// EmailOne is the primary email address of the company.
	EmailOne string `json:"emailOne"`

	// EmailTwo is the secondary email address of the company.
	EmailTwo string `json:"emailTwo"`

	// EmailThree is the tertiary email address of the company.
	EmailThree string `json:"emailThree"`

	// Fax is the fax number of the company.
	Fax string `json:"fax"`

	// Name is the name of the company.
	Name string `json:"name"`

	// Phone is the phone number of the company.
	Phone string `json:"phone"`

	// Profile is the profile text of the company.
	Profile *string `json:"profileText"`

	// State is the state or province where the company is located.
	State string `json:"state"`

	// Website is the website URL of the company.
	Website string `json:"website"`

	// Zip is the ZIP or postal code where the company is located.
	Zip string `json:"zip"`

	// ManagedBy is the user managing the company.
	ManagedBy *twapi.Relationship `json:"clientManagedBy"`

	// Industry is the industry the company belongs to.
	Industry *twapi.Relationship `json:"industry"`

	// Tags is a list of tags associated with the company.
	Tags []twapi.Relationship `json:"tags"`

	// CreatedAt is the date and time when the company was created.
	CreatedAt *time.Time `json:"createdAt"`

	// UpdatedAt is the date and time when the company was last updated.
	UpdatedAt *time.Time `json:"updatedAt"`

	// Status is the status of the company. Possible values are "active" or
	// "deleted".
	Status string `json:"status"`
}

Company represents an organization or business entity that can be associated with users, projects, and tasks within the platform, and it is often referred to as a “client.” It serves as a way to group related users and projects under a single organizational umbrella, making it easier to manage permissions, assign responsibilities, and organize work. Companies (or clients) are frequently used to distinguish between internal teams and external collaborators, enabling teams to work efficiently while maintaining clear boundaries around ownership, visibility, and access levels across different projects.

More information can be found at: https://support.teamwork.com/projects/getting-started/companies-owner-and-external

type CompanyCreateRequest added in v0.4.0

type CompanyCreateRequest struct {
	// AddressOne is the first line of the company's address.
	AddressOne *string `json:"addressOne,omitempty"`

	// AddressTwo is the second line of the company's address.
	AddressTwo *string `json:"addressTwo,omitempty"`

	// City is the city where the company is located.
	City *string `json:"city,omitempty"`

	// CountryCode is the ISO 3166-1 alpha-2 country code where the company is
	// located.
	CountryCode *string `json:"countrycode,omitempty"`

	// EmailOne is the primary email address of the company.
	EmailOne *string `json:"emailOne,omitempty"`

	// EmailTwo is the secondary email address of the company.
	EmailTwo *string `json:"emailTwo,omitempty"`

	// EmailThree is the tertiary email address of the company.
	EmailThree *string `json:"emailThree,omitempty"`

	// Fax is the fax number of the company.
	Fax *string `json:"fax,omitempty"`

	// Name is the name of the company. This field is required.
	Name string `json:"name"`

	// Phone is the phone number of the company.
	Phone *string `json:"phone,omitempty"`

	// Profile is the profile text of the company.
	Profile *string `json:"profile,omitempty"`

	// State is the state or province where the company is located.
	State *string `json:"state,omitempty"`

	// Website is the website URL of the company.
	Website *string `json:"website,omitempty"`

	// Zip is the ZIP or postal code where the company is located.
	Zip *string `json:"zip,omitempty"`

	// ManagerID is the user ID of the user managing the company.
	ManagerID *int64 `json:"clientManagedBy"`

	// IndustryID is the industry ID the company belongs to.
	IndustryID *int64 `json:"industryCatId,omitempty"`

	// TagIDs is a list of tag IDs to associate with the company.
	TagIDs []int64 `json:"tagIds,omitempty"`
}

CompanyCreateRequest represents the request body for creating a new client/company.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/post-projects-api-v3-companies-json

func NewCompanyCreateRequest added in v0.4.0

func NewCompanyCreateRequest(name string) CompanyCreateRequest

NewCompanyCreateRequest creates a new CompanyCreateRequest with the provided name in a specific project.

func (CompanyCreateRequest) HTTPRequest added in v0.4.0

func (c CompanyCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CompanyCreateRequest.

type CompanyCreateResponse added in v0.4.0

type CompanyCreateResponse struct {
	// Company is the created company.
	Company Company `json:"company"`
}

CompanyCreateResponse represents the response body for creating a new client/company.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/post-projects-api-v3-companies-json

func CompanyCreate added in v0.4.0

func CompanyCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req CompanyCreateRequest,
) (*CompanyCreateResponse, error)

CompanyCreate creates a new client/company using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCompanyServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	companyRequest := projects.NewCompanyCreateRequest("Test Company")
	companyRequest.Profile = twapi.Ptr("A company created via the API.")

	companyResponse, err := projects.CompanyCreate(ctx, engine, companyRequest)
	if err != nil {
		fmt.Printf("failed to create company: %s", err)
	} else {
		fmt.Printf("created company with identifier %d\n", companyResponse.Company.ID)
	}

}

func startCompanyServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/companies", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/companies", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"companies":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created company with identifier 12345

func (*CompanyCreateResponse) HandleHTTPResponse added in v0.4.0

func (c *CompanyCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CompanyCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type CompanyDeleteRequest added in v0.4.0

type CompanyDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path CompanyDeleteRequestPath
}

CompanyDeleteRequest represents the request body for deleting a client/company.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/delete-projects-api-v3-companies-company-id-json

func NewCompanyDeleteRequest added in v0.4.0

func NewCompanyDeleteRequest(companyID int64) CompanyDeleteRequest

NewCompanyDeleteRequest creates a new CompanyDeleteRequest with the provided company ID.

func (CompanyDeleteRequest) HTTPRequest added in v0.4.0

func (c CompanyDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CompanyDeleteRequest.

type CompanyDeleteRequestPath added in v0.4.0

type CompanyDeleteRequestPath struct {
	// ID is the unique identifier of the company to be deleted.
	ID int64
}

CompanyDeleteRequestPath contains the path parameters for deleting a client/company.

type CompanyDeleteResponse added in v0.4.0

type CompanyDeleteResponse struct{}

CompanyDeleteResponse represents the response body for deleting a client/company.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/delete-projects-api-v3-companies-company-id-json

func CompanyDelete added in v0.4.0

func CompanyDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req CompanyDeleteRequest,
) (*CompanyDeleteResponse, error)

CompanyDelete deletes a client/company using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCompanyServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.CompanyDelete(ctx, engine, projects.NewCompanyDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete company: %s", err)
	} else {
		fmt.Println("company deleted!")
	}

}

func startCompanyServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/companies", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/companies", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"companies":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

company deleted!

func (*CompanyDeleteResponse) HandleHTTPResponse added in v0.4.0

func (c *CompanyDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CompanyDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type CompanyGetRequest added in v0.4.0

type CompanyGetRequest struct {
	// Path contains the path parameters for the request.
	Path CompanyGetRequestPath
}

CompanyGetRequest represents the request body for loading a single client/company.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/get-projects-api-v3-companies-company-id-json

func NewCompanyGetRequest added in v0.4.0

func NewCompanyGetRequest(companyID int64) CompanyGetRequest

NewCompanyGetRequest creates a new CompanyGetRequest with the provided company ID. The ID is required to load a company.

func (CompanyGetRequest) HTTPRequest added in v0.4.0

func (c CompanyGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CompanyGetRequest.

type CompanyGetRequestPath added in v0.4.0

type CompanyGetRequestPath struct {
	// ID is the unique identifier of the company to be retrieved.
	ID int64 `json:"id"`
}

CompanyGetRequestPath contains the path parameters for loading a single company.

type CompanyGetResponse added in v0.4.0

type CompanyGetResponse struct {
	Company Company `json:"company"`
}

CompanyGetResponse contains all the information related to a client/company.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/get-projects-api-v3-companies-company-id-json

func CompanyGet added in v0.4.0

func CompanyGet(
	ctx context.Context,
	engine *twapi.Engine,
	req CompanyGetRequest,
) (*CompanyGetResponse, error)

CompanyGet retrieves a single client/company using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCompanyServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	companyResponse, err := projects.CompanyGet(ctx, engine, projects.NewCompanyGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve company: %s", err)
	} else {
		fmt.Printf("retrieved company with identifier %d\n", companyResponse.Company.ID)
	}

}

func startCompanyServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/companies", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/companies", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"companies":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved company with identifier 12345

func (*CompanyGetResponse) HandleHTTPResponse added in v0.4.0

func (c *CompanyGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CompanyGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type CompanyListRequest added in v0.4.0

type CompanyListRequest struct {
	// Filters contains the filters for loading multiple companies.
	Filters CompanyListRequestFilters
}

CompanyListRequest represents the request body for loading multiple clients/companies.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/get-projects-api-v3-companies-json

func NewCompanyListRequest added in v0.4.0

func NewCompanyListRequest() CompanyListRequest

NewCompanyListRequest creates a new CompanyListRequest with default values.

func (CompanyListRequest) HTTPRequest added in v0.4.0

func (c CompanyListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CompanyListRequest.

type CompanyListRequestFilters added in v0.4.0

type CompanyListRequestFilters struct {
	// SearchTerm is an optional search term to filter clients/companies by name.
	SearchTerm string

	// TagIDs is an optional list of tag IDs to filter companies by tags.
	TagIDs []int64

	// MatchAllTags is an optional flag to indicate if all tags must match. If set
	// to true, only companies matching all specified tags will be returned.
	MatchAllTags *bool

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of companies to retrieve per page. Defaults to 50.
	PageSize int64
}

CompanyListRequestFilters contains the filters for loading multiple clients/companies.

type CompanyListResponse added in v0.4.0

type CompanyListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Companies []Company `json:"companies"`
	// contains filtered or unexported fields
}

CompanyListResponse contains information by multiple clients/companies matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/get-projects-api-v3-companies-json

func CompanyList added in v0.4.0

func CompanyList(
	ctx context.Context,
	engine *twapi.Engine,
	req CompanyListRequest,
) (*CompanyListResponse, error)

CompanyList retrieves multiple clients/companies using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCompanyServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	companiesRequest := projects.NewCompanyListRequest()
	companiesRequest.Filters.SearchTerm = "John"

	companiesResponse, err := projects.CompanyList(ctx, engine, companiesRequest)
	if err != nil {
		fmt.Printf("failed to list companies: %s", err)
	} else {
		for _, company := range companiesResponse.Companies {
			fmt.Printf("retrieved company with identifier %d\n", company.ID)
		}
	}

}

func startCompanyServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/companies", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/companies", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"companies":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved company with identifier 12345
retrieved company with identifier 12346

func (*CompanyListResponse) HandleHTTPResponse added in v0.4.0

func (c *CompanyListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CompanyListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*CompanyListResponse) Iterate added in v0.4.0

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*CompanyListResponse) SetRequest added in v0.4.0

func (c *CompanyListResponse) SetRequest(req CompanyListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type CompanyUpdateRequest added in v0.4.0

type CompanyUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path CompanyUpdateRequestPath `json:"-"`

	// AddressOne is the first line of the company's address.
	AddressOne *string `json:"addressOne,omitempty"`

	// AddressTwo is the second line of the company's address.
	AddressTwo *string `json:"addressTwo,omitempty"`

	// City is the city where the company is located.
	City *string `json:"city,omitempty"`

	// CountryCode is the ISO 3166-1 alpha-2 country code where the company is
	// located.
	CountryCode *string `json:"countrycode,omitempty"`

	// EmailOne is the primary email address of the company.
	EmailOne *string `json:"emailOne,omitempty"`

	// EmailTwo is the secondary email address of the company.
	EmailTwo *string `json:"emailTwo,omitempty"`

	// EmailThree is the tertiary email address of the company.
	EmailThree *string `json:"emailThree,omitempty"`

	// Fax is the fax number of the company.
	Fax *string `json:"fax,omitempty"`

	// Name is the name of the company.
	Name *string `json:"name"`

	// Phone is the phone number of the company.
	Phone *string `json:"phone,omitempty"`

	// Profile is the profile text of the company.
	Profile *string `json:"profile,omitempty"`

	// State is the state or province where the company is located.
	State *string `json:"state,omitempty"`

	// Website is the website URL of the company.
	Website *string `json:"website,omitempty"`

	// Zip is the ZIP or postal code where the company is located.
	Zip *string `json:"zip,omitempty"`

	// ManagerID is the user ID of the user managing the company.
	ManagerID *int64 `json:"clientManagedBy"`

	// IndustryID is the industry ID the company belongs to.
	IndustryID *int64 `json:"industryCatId,omitempty"`

	// TagIDs is a list of tag IDs to associate with the company.
	TagIDs []int64 `json:"tagIds,omitempty"`
}

CompanyUpdateRequest represents the request body for updating a client/company. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/patch-projects-api-v3-companies-company-id-json

func NewCompanyUpdateRequest added in v0.4.0

func NewCompanyUpdateRequest(companyID int64) CompanyUpdateRequest

NewCompanyUpdateRequest creates a new CompanyUpdateRequest with the provided client/company ID. The ID is required to update a company.

func (CompanyUpdateRequest) HTTPRequest added in v0.4.0

func (c CompanyUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the CompanyUpdateRequest.

type CompanyUpdateRequestPath added in v0.4.0

type CompanyUpdateRequestPath struct {
	// ID is the unique identifier of the company to be updated.
	ID int64
}

CompanyUpdateRequestPath contains the path parameters for updating a client/company.

type CompanyUpdateResponse added in v0.4.0

type CompanyUpdateResponse struct {
	// Company is the updated company.
	Company Company `json:"company"`
}

CompanyUpdateResponse represents the response body for updating a client/company.

https://apidocs.teamwork.com/docs/teamwork/v3/companies/patch-projects-api-v3-companies-company-id-json

func CompanyUpdate added in v0.4.0

func CompanyUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req CompanyUpdateRequest,
) (*CompanyUpdateResponse, error)

CompanyUpdate updates a new client/company using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startCompanyServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	companyRequest := projects.NewCompanyUpdateRequest(12345)
	companyRequest.Profile = twapi.Ptr("Updated profile")

	_, err = projects.CompanyUpdate(ctx, engine, companyRequest)
	if err != nil {
		fmt.Printf("failed to update company: %s", err)
	} else {
		fmt.Println("company updated!")
	}

}

func startCompanyServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/companies", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/companies/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"company":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/companies", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"companies":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

company updated!

func (*CompanyUpdateResponse) HandleHTTPResponse added in v0.4.0

func (c *CompanyUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the CompanyUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Currency added in v1.5.0

type Currency struct {
	// ID is the unique identifier of the currency.
	ID int64 `json:"id"`

	// Code is the currency code (e.g., "USD", "EUR").
	Code string `json:"code"`

	// Symbol is the currency symbol (e.g., "$", "€").
	Symbol string `json:"symbol"`

	// Name is the currency name.
	Name string `json:"name"`
}

Currency represents a currency in the rates system.

type EffectiveRateSource added in v1.5.0

type EffectiveRateSource string

EffectiveRateSource represents the source of an effective rate.

const (
	// EffectiveRateSourceInstallationRate represents a rate derived from the user's installation rate.
	EffectiveRateSourceInstallationRate EffectiveRateSource = "installationrate"

	// EffectiveRateSourceProjectRate represents a rate derived from the project's default rate.
	EffectiveRateSourceProjectRate EffectiveRateSource = "projectrate"

	// EffectiveRateSourceUserProjectRate represents a rate derived from a user's project-specific rate.
	EffectiveRateSourceUserProjectRate EffectiveRateSource = "userprojectrate"
)

type EffectiveUserProjectRate added in v1.5.0

type EffectiveUserProjectRate struct {
	// User is the user relationship.
	User twapi.Relationship `json:"user"`

	// EffectiveRate is the effective rate as a monetary amount in the smallest
	// currency unit (e.g., cents).
	EffectiveRate int64 `json:"effectiveRate"`

	// UserProjectRate is the user's project-specific rate as a monetary amount
	// in the smallest currency unit (e.g., cents).
	UserProjectRate *int64 `json:"userProjectRate,omitempty"`

	// UserInstallationRate is the user's installation rate as a monetary amount
	// in the smallest currency unit (e.g., cents).
	UserInstallationRate *int64 `json:"userInstallationRate,omitempty"`

	// ProjectRate is the project's default rate as a monetary amount in the
	// smallest currency unit (e.g., cents).
	ProjectRate *int64 `json:"projectRate,omitempty"`

	// Source indicates the source of the effective rate.
	Source *EffectiveRateSource `json:"source,omitempty"`

	// FromDate is when this rate became effective.
	FromDate *time.Time `json:"fromDate,omitempty"`

	// ToDate is when this rate stops being effective.
	ToDate *time.Time `json:"toDate,omitempty"`

	// UpdatedAt is when this rate was last updated.
	UpdatedAt *time.Time `json:"updatedAt,omitempty"`

	// UpdatedBy is who last updated this rate.
	UpdatedBy *twapi.Relationship `json:"updatedBy,omitempty"`

	// BillableRate contains the rate and currency for billing.
	BillableRate *BillableRate `json:"billableRate,omitempty"`
}

EffectiveUserProjectRate represents an effective user project rate.

type Industry added in v1.4.0

type Industry struct {
	// ID is the unique identifier of the industry.
	ID LegacyNumber `json:"id"`

	// Name is the name of the industry.
	Name string `json:"name"`
}

Industry refers to the business sector or market category that a company belongs to, such as technology, healthcare, finance, or education. It helps provide context about the nature of a company’s work and can be used to better organize and filter data across the platform. By associating companies and projects with specific industries, Teamwork.com allows teams to gain clearer insights, tailor communication, and segment information in ways that make it easier to manage relationships and understand the broader business landscape in which their clients and partners operate.

type IndustryListRequest added in v1.4.0

type IndustryListRequest struct{}

IndustryListRequest represents the request body for loading multiple industries.

Not documented.

func NewIndustryListRequest added in v1.4.0

func NewIndustryListRequest() IndustryListRequest

NewIndustryListRequest creates a new IndustryListRequest with default values.

func (IndustryListRequest) HTTPRequest added in v1.4.0

func (p IndustryListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the IndustryListRequest.

type IndustryListResponse added in v1.4.0

type IndustryListResponse struct {
	Industries []Industry `json:"industries"`
}

IndustryListResponse contains information by multiple industries matching the request filters.

Not documented.

func IndustryList added in v1.4.0

func IndustryList(
	ctx context.Context,
	engine *twapi.Engine,
	req IndustryListRequest,
) (*IndustryListResponse, error)

IndustryList retrieves multiple industries using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startIndustryServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	industryRequest := projects.NewIndustryListRequest()

	industryResponse, err := projects.IndustryList(ctx, engine, industryRequest)
	if err != nil {
		fmt.Printf("failed to list industries: %s", err)
	} else {
		for _, industry := range industryResponse.Industries {
			fmt.Printf("retrieved industry with identifier %d\n", industry.ID)
		}
	}

}

func startIndustryServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /industries", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"industries":[{"id":"12345"},{"id":"12346"}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved industry with identifier 12345
retrieved industry with identifier 12346

func (*IndustryListResponse) HandleHTTPResponse added in v1.4.0

func (p *IndustryListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the IndustryListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type LegacyDate

type LegacyDate time.Time

LegacyDate is a type alias for time.Time, used to represent legacy date values in the API.

func NewLegacyDate added in v0.3.0

func NewLegacyDate(t time.Time) LegacyDate

NewLegacyDate creates a new LegacyDate from a time.Time value.

func (LegacyDate) MarshalJSON

func (d LegacyDate) MarshalJSON() ([]byte, error)

MarshalJSON encodes the LegacyDate as a string in the format "20060102".

func (*LegacyDate) UnmarshalJSON

func (d *LegacyDate) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string into a LegacyDate type.

type LegacyNumber

type LegacyNumber int64

LegacyNumber is a type alias for int64, used to represent numeric values in the API.

func NewLegacyNumber added in v0.3.0

func NewLegacyNumber(n int64) LegacyNumber

NewLegacyNumber creates a new LegacyNumber from an int64 value.

func (LegacyNumber) MarshalJSON

func (n LegacyNumber) MarshalJSON() ([]byte, error)

MarshalJSON encodes the LegacyNumber as a string.

func (*LegacyNumber) UnmarshalJSON

func (n *LegacyNumber) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string into a LegacyNumber type.

type LegacyNumericList added in v0.5.0

type LegacyNumericList []int64

LegacyNumericList is a type alias for a slice of int64, used to represent a list of numeric values in the API.

func (*LegacyNumericList) Add added in v0.5.0

func (l *LegacyNumericList) Add(n float64)

Add adds a numeric value to the LegacyNumericList.

func (LegacyNumericList) MarshalJSON added in v0.5.0

func (l LegacyNumericList) MarshalJSON() ([]byte, error)

MarshalJSON encodes the LegacyNumericList as a JSON array of strings.

type LegacyRelationship added in v0.5.0

type LegacyRelationship struct {
	ID   LegacyNumber `json:"id"`
	Type string       `json:"type"`
}

LegacyRelationship describes the relation between the main entity and a sideload type.

type LegacyUserGroups added in v0.3.0

type LegacyUserGroups struct {
	UserIDs    []int64
	CompanyIDs []int64
	TeamIDs    []int64
}

LegacyUserGroups represents a collection of users, companies, and teams in a legacy format, where IDs are represented as strings.

func (LegacyUserGroups) IsEmpty added in v0.3.0

func (m LegacyUserGroups) IsEmpty() bool

IsEmpty checks if the LegacyUserGroups contains no IDs.

func (LegacyUserGroups) MarshalJSON added in v0.3.0

func (m LegacyUserGroups) MarshalJSON() ([]byte, error)

MarshalJSON encodes the LegacyUserGroups as a JSON object.

func (*LegacyUserGroups) UnmarshalJSON added in v0.3.0

func (m *LegacyUserGroups) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string into a LegacyUserGroups type.

type LogItemType added in v1.2.0

type LogItemType string

LogItemType contains all possible activity item types.

const (
	LogItemTypeMessage          LogItemType = "message"
	LogItemTypeComment          LogItemType = "comment"
	LogItemTypeTask             LogItemType = "task"
	LogItemTypeTasklist         LogItemType = "tasklist"
	LogItemTypeTaskgroup        LogItemType = "taskgroup"
	LogItemTypeMilestone        LogItemType = "milestone"
	LogItemTypeFile             LogItemType = "file"
	LogItemTypeForm             LogItemType = "form"
	LogItemTypeNotebook         LogItemType = "notebook"
	LogItemTypeTimelog          LogItemType = "timelog"
	LogItemTypeTaskComment      LogItemType = "task_comment"
	LogItemTypeNotebookComment  LogItemType = "notebook_comment"
	LogItemTypeFileComment      LogItemType = "file_comment"
	LogItemTypeLinkComment      LogItemType = "link_comment"
	LogItemTypeMilestoneComment LogItemType = "milestone_comment"
	LogItemTypeProject          LogItemType = "project"
	LogItemTypeLink             LogItemType = "link"
	LogItemTypeBillingInvoice   LogItemType = "billingInvoice"
	LogItemTypeRisk             LogItemType = "risk"
	LogItemTypeProjectUpdate    LogItemType = "projectUpdate"
	LogItemTypeReacted          LogItemType = "reacted"
	LogItemTypeBudget           LogItemType = "budget"
)

List of activity item types.

func (*LogItemType) UnmarshalText added in v1.2.0

func (l *LogItemType) UnmarshalText(text []byte) error

UnmarshalText decodes the text into a LogItemType.

type Milestone added in v0.3.0

type Milestone struct {
	// ID is the unique identifier of the milestone.
	ID int64 `json:"id"`

	// Name is the name of the milestone.
	Name string `json:"name"`

	// Description is the description of the milestone.
	Description string `json:"description"`

	// DueAt is the due date of the milestone.
	DueAt time.Time `json:"deadline"`

	// Project is the project associated with the milestone.
	Project twapi.Relationship `json:"project"`

	// Tasklists is the list of tasklists associated with the milestone.
	Tasklists []twapi.Relationship `json:"tasklists"`

	// Tags is the list of tags associated with the milestone.
	Tags []twapi.Relationship `json:"tags"`

	// ResponsibleParties is the list of assingees (users, teams and
	// clients/companies) responsible for the milestone.
	ResponsibleParties []twapi.Relationship `json:"responsibleParties"`

	// CreatedAt is the date and time when the milestone was created.
	CreatedAt *time.Time `json:"createdOn"`

	// UpdatedAt is the date and time when the milestone was last updated.
	UpdatedAt *time.Time `json:"lastChangedOn"`

	// DeletedAt is the date and time when the milestone was deleted, if it was
	// deleted.
	DeletedAt *time.Time `json:"deletedOn"`

	// CompletedAt is the date and time when the milestone was completed, if it
	// was completed.
	CompletedAt *time.Time `json:"completedOn"`

	// CompletedBy is the ID of the user who completed the milestone, if it was
	// completed.
	CompletedBy *int64 `json:"completedBy"`

	// Completed indicates whether the milestone is completed or not.
	Completed bool `json:"completed"`

	// Status is the status of the milestone. It can be "new", "reopened",
	// "completed" or "deleted".
	Status string `json:"status"`
}

Milestone represents a significant point or goal within a project that marks the completion of a major phase or a key deliverable. It acts as a high-level indicator of progress, helping teams track whether work is advancing according to plan. Milestones are typically used to coordinate efforts across different tasks and task lists, providing a clear deadline or objective that multiple team members or departments can align around. They don't contain individual tasks themselves but serve as checkpoints to ensure the project is moving in the right direction.

More information can be found at: https://support.teamwork.com/projects/getting-started/milestones-overview

type MilestoneCreateRequest added in v0.3.0

type MilestoneCreateRequest struct {
	// Path contains the path parameters for the request.
	Path MilestoneCreateRequestPath `json:"-"`

	// Name is the name of the milestone.
	Name string `json:"title"`

	// Description is an optional description of the milestone.
	Description *string `json:"description,omitempty"`

	// DueAt is the due date of the milestone.
	DueAt LegacyDate `json:"deadline"`

	// TasklistIDs is an optional list of tasklist IDs to associate with the
	// milestone.
	TasklistIDs []int64 `json:"tasklistIds,omitempty"`

	// TagIDs is an optional list of tag IDs to associate with the milestone.
	TagIDs []int64 `json:"tagIds,omitempty"`

	// Assignees is a list of users, companies and teams responsible for the
	// milestone.
	Assignees LegacyUserGroups `json:"responsible-party-ids"`
}

MilestoneCreateRequest represents the request body for creating a new milestone.

https://apidocs.teamwork.com/docs/teamwork/v1/milestones/post-projects-id-milestones-json

func NewMilestoneCreateRequest added in v0.3.0

func NewMilestoneCreateRequest(
	projectID int64,
	name string,
	dueAt LegacyDate,
	assignees LegacyUserGroups,
) MilestoneCreateRequest

NewMilestoneCreateRequest creates a new MilestoneCreateRequest with the provided required fields.

func (MilestoneCreateRequest) HTTPRequest added in v0.3.0

func (m MilestoneCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the MilestoneCreateRequest.

type MilestoneCreateRequestPath added in v0.3.0

type MilestoneCreateRequestPath struct {
	// ProjectID is the unique identifier of the project that will contain the
	// milestone.
	ProjectID int64
}

MilestoneUpdateRequestPath contains the path parameters for creating a milestone.

type MilestoneCreateResponse added in v0.3.0

type MilestoneCreateResponse struct {
	// ID is the unique identifier of the created milestone.
	ID LegacyNumber `json:"milestoneId"`
}

MilestoneCreateResponse represents the response body for creating a new milestone.

https://apidocs.teamwork.com/docs/teamwork/v1/milestones/post-projects-id-milestones-json

func MilestoneCreate added in v0.3.0

func MilestoneCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req MilestoneCreateRequest,
) (*MilestoneCreateResponse, error)

MilestoneCreate creates a new milestone using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startMilestoneServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	milestoneRequest := projects.NewMilestoneCreateRequest(777, "New Milestone", projects.NewLegacyDate(time.Now()),
		projects.LegacyUserGroups{
			UserIDs: []int64{456, 789},
		},
	)
	milestoneRequest.Description = twapi.Ptr("This is a new milestone created via the API.")

	milestoneResponse, err := projects.MilestoneCreate(ctx, engine, milestoneRequest)
	if err != nil {
		fmt.Printf("failed to create milestone: %s", err)
	} else {
		fmt.Printf("created milestone with identifier %d\n", milestoneResponse.ID)
	}

}

func startMilestoneServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/milestones", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","milestoneId":"12345"}`)
	})
	mux.HandleFunc("PUT /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestone":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestones":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created milestone with identifier 12345

func (*MilestoneCreateResponse) HandleHTTPResponse added in v0.3.0

func (m *MilestoneCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the MilestoneCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type MilestoneDeleteRequest added in v0.3.0

type MilestoneDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path MilestoneDeleteRequestPath
}

MilestoneDeleteRequest represents the request body for deleting a milestone.

https://apidocs.teamwork.com/docs/teamwork/v1/milestones/delete-milestones-id-json

func NewMilestoneDeleteRequest added in v0.3.0

func NewMilestoneDeleteRequest(milestoneID int64) MilestoneDeleteRequest

NewMilestoneDeleteRequest creates a new MilestoneDeleteRequest with the provided milestone ID.

func (MilestoneDeleteRequest) HTTPRequest added in v0.3.0

func (m MilestoneDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the MilestoneDeleteRequest.

type MilestoneDeleteRequestPath added in v0.3.0

type MilestoneDeleteRequestPath struct {
	// ID is the unique identifier of the milestone to be deleted.
	ID int64
}

MilestoneDeleteRequestPath contains the path parameters for deleting a milestone.

type MilestoneDeleteResponse added in v0.3.0

type MilestoneDeleteResponse struct{}

MilestoneDeleteResponse represents the response body for deleting a milestone.

https://apidocs.teamwork.com/docs/teamwork/v1/milestones/delete-milestones-id-json

func MilestoneDelete added in v0.3.0

func MilestoneDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req MilestoneDeleteRequest,
) (*MilestoneDeleteResponse, error)

MilestoneDelete deletes a milestone using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startMilestoneServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.MilestoneDelete(ctx, engine, projects.NewMilestoneDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete milestone: %s", err)
	} else {
		fmt.Println("milestone deleted!")
	}

}

func startMilestoneServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/milestones", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","milestoneId":"12345"}`)
	})
	mux.HandleFunc("PUT /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestone":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestones":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

milestone deleted!

func (*MilestoneDeleteResponse) HandleHTTPResponse added in v0.3.0

func (m *MilestoneDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the MilestoneDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type MilestoneGetRequest added in v0.3.0

type MilestoneGetRequest struct {
	// Path contains the path parameters for the request.
	Path MilestoneGetRequestPath
}

MilestoneGetRequest represents the request body for loading a single milestone.

https://apidocs.teamwork.com/docs/teamwork/v3/milestones/get-projects-api-v3-milestones-mileston-id-json

func NewMilestoneGetRequest added in v0.3.0

func NewMilestoneGetRequest(milestoneID int64) MilestoneGetRequest

NewMilestoneGetRequest creates a new MilestoneGetRequest with the provided milestone ID. The ID is required to load a milestone.

func (MilestoneGetRequest) HTTPRequest added in v0.3.0

func (m MilestoneGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the MilestoneGetRequest.

type MilestoneGetRequestPath added in v0.3.0

type MilestoneGetRequestPath struct {
	// ID is the unique identifier of the milestone to be retrieved.
	ID int64 `json:"id"`
}

MilestoneGetRequestPath contains the path parameters for loading a single milestone.

type MilestoneGetResponse added in v0.3.0

type MilestoneGetResponse struct {
	Milestone Milestone `json:"milestone"`
}

MilestoneGetResponse contains all the information related to a milestone.

https://apidocs.teamwork.com/docs/teamwork/v3/milestones/get-projects-api-v3-milestones-mileston-id-json

func MilestoneGet added in v0.3.0

func MilestoneGet(
	ctx context.Context,
	engine *twapi.Engine,
	req MilestoneGetRequest,
) (*MilestoneGetResponse, error)

MilestoneGet retrieves a single milestone using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startMilestoneServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	milestoneResponse, err := projects.MilestoneGet(ctx, engine, projects.NewMilestoneGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve milestone: %s", err)
	} else {
		fmt.Printf("retrieved milestone with identifier %d\n", milestoneResponse.Milestone.ID)
	}

}

func startMilestoneServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/milestones", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","milestoneId":"12345"}`)
	})
	mux.HandleFunc("PUT /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestone":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestones":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved milestone with identifier 12345

func (*MilestoneGetResponse) HandleHTTPResponse added in v0.3.0

func (m *MilestoneGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the MilestoneGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type MilestoneListRequest added in v0.3.0

type MilestoneListRequest struct {
	// Path contains the path parameters for the request.
	Path MilestoneListRequestPath

	// Filters contains the filters for loading multiple milestones.
	Filters MilestoneListRequestFilters
}

MilestoneListRequest represents the request body for loading multiple milestones.

https://apidocs.teamwork.com/docs/teamwork/v3/milestones/get-projects-api-v3-milestones-json https://apidocs.teamwork.com/docs/teamwork/v3/milestones/get-projects-api-v3-projects-project-id-milestones-json

func NewMilestoneListRequest added in v0.3.0

func NewMilestoneListRequest() MilestoneListRequest

NewMilestoneListRequest creates a new MilestoneListRequest with default values.

func (MilestoneListRequest) HTTPRequest added in v0.3.0

func (m MilestoneListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the MilestoneListRequest.

type MilestoneListRequestFilters added in v0.3.0

type MilestoneListRequestFilters struct {
	// SearchTerm is an optional search term to filter milestones by name.
	SearchTerm string

	// TagIDs is an optional list of tag IDs to filter milestones by tags.
	TagIDs []int64

	// MatchAllTags is an optional flag to indicate if all tags must match. If set
	// to true, only milestones matching all specified tags will be returned.
	MatchAllTags *bool

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of milestones to retrieve per page. Defaults to 50.
	PageSize int64
}

MilestoneListRequestFilters contains the filters for loading multiple milestones.

type MilestoneListRequestPath added in v0.3.0

type MilestoneListRequestPath struct {
	// ProjectID is the unique identifier of the project whose milestones are to
	// be retrieved.
	ProjectID int64
}

MilestoneListRequestPath contains the path parameters for loading multiple milestones.

type MilestoneListResponse added in v0.3.0

type MilestoneListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Milestones []Milestone `json:"milestones"`
	// contains filtered or unexported fields
}

MilestoneListResponse contains information by multiple milestones matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/milestones/get-projects-api-v3-milestones-json https://apidocs.teamwork.com/docs/teamwork/v3/milestones/get-projects-api-v3-projects-project-id-milestones-json

func MilestoneList added in v0.3.0

func MilestoneList(
	ctx context.Context,
	engine *twapi.Engine,
	req MilestoneListRequest,
) (*MilestoneListResponse, error)

MilestoneList retrieves multiple milestones using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startMilestoneServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	milestonesRequest := projects.NewMilestoneListRequest()
	milestonesRequest.Filters.SearchTerm = "Example"

	milestonesResponse, err := projects.MilestoneList(ctx, engine, milestonesRequest)
	if err != nil {
		fmt.Printf("failed to list milestones: %s", err)
	} else {
		for _, milestone := range milestonesResponse.Milestones {
			fmt.Printf("retrieved milestone with identifier %d\n", milestone.ID)
		}
	}

}

func startMilestoneServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/milestones", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","milestoneId":"12345"}`)
	})
	mux.HandleFunc("PUT /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestone":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestones":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved milestone with identifier 12345
retrieved milestone with identifier 12346

func (*MilestoneListResponse) HandleHTTPResponse added in v0.3.0

func (m *MilestoneListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the MilestoneListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*MilestoneListResponse) Iterate added in v0.3.0

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*MilestoneListResponse) SetRequest added in v0.3.0

func (m *MilestoneListResponse) SetRequest(req MilestoneListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type MilestoneUpdateRequest added in v0.3.0

type MilestoneUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path MilestoneUpdateRequestPath `json:"-"`

	// Name is the name of the milestone.
	Name *string `json:"title,omitempty"`

	// Description is an optional description of the milestone.
	Description *string `json:"description,omitempty"`

	// DueAt is the due date of the milestone.
	DueAt *LegacyDate `json:"deadline,omitempty"`

	// TasklistIDs is an optional list of tasklist IDs to associate with the
	// milestone.
	TasklistIDs []int64 `json:"tasklistIds,omitempty"`

	// TagIDs is an optional list of tag IDs to associate with the milestone.
	TagIDs []int64 `json:"tagIds,omitempty"`

	// Assignees is a list of users, companies and teams responsible for the
	// milestone.
	Assignees *LegacyUserGroups `json:"responsible-party-ids,omitempty"`
}

MilestoneUpdateRequest represents the request body for updating a milestone. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v1/milestones/put-milestones-id-json

func NewMilestoneUpdateRequest added in v0.3.0

func NewMilestoneUpdateRequest(milestoneID int64) MilestoneUpdateRequest

NewMilestoneUpdateRequest creates a new MilestoneUpdateRequest with the provided milestone ID. The ID is required to update a milestone.

func (MilestoneUpdateRequest) HTTPRequest added in v0.3.0

func (m MilestoneUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the MilestoneUpdateRequest.

type MilestoneUpdateRequestPath added in v0.3.0

type MilestoneUpdateRequestPath struct {
	// ID is the unique identifier of the milestone to be updated.
	ID int64
}

MilestoneUpdateRequestPath contains the path parameters for updating a milestone.

type MilestoneUpdateResponse added in v0.3.0

type MilestoneUpdateResponse struct{}

MilestoneUpdateResponse represents the response body for updating a milestone.

https://apidocs.teamwork.com/docs/teamwork/v1/milestones/put-milestones-id-json

func MilestoneUpdate added in v0.3.0

func MilestoneUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req MilestoneUpdateRequest,
) (*MilestoneUpdateResponse, error)

MilestoneUpdate updates a milestone using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startMilestoneServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	milestoneRequest := projects.NewMilestoneUpdateRequest(12345)
	milestoneRequest.Description = twapi.Ptr("This is an updated description.")

	_, err = projects.MilestoneUpdate(ctx, engine, milestoneRequest)
	if err != nil {
		fmt.Printf("failed to update milestone: %s", err)
	} else {
		fmt.Println("milestone updated!")
	}

}

func startMilestoneServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/milestones", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","milestoneId":"12345"}`)
	})
	mux.HandleFunc("PUT /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestone":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/milestones", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"milestones":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

milestone updated!

func (*MilestoneUpdateResponse) HandleHTTPResponse added in v0.3.0

func (m *MilestoneUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the MilestoneUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type MultiCurrencyInstallationUserRate added in v1.5.0

type MultiCurrencyInstallationUserRate struct {
	Rates map[int64]MultiCurrencyRate `json:"rates"`
}

MultiCurrencyInstallationUserRate represents an installation user rate with multiple currency options.

type MultiCurrencyRate added in v1.5.0

type MultiCurrencyRate struct {
	// Rate is the rate amount.
	Amount float64 `json:"amount"`

	// Currency is the currency information.
	Currency twapi.Relationship `json:"currency"`
}

MultiCurrencyRate represents a rate with multiple currency options.

type Notebook added in v1.3.0

type Notebook struct {
	// ID is the unique identifier of the notebook.
	ID int64 `json:"id"`

	// Name is the name of the notebook.
	Name string `json:"name"`

	// Description is the description of the notebook.
	Description string `json:"description"`

	// Contents is the contents of the notebook.
	Contents *string `json:"contents,omitempty"` // can be optionally hidden on lists

	// Type is the type of the notebook. It can be "MARKDOWN" or "HTML".
	Type NotebookType `json:"type"`

	// Project is the project associated with the notebook.
	Project twapi.Relationship `json:"project"`

	// Tags is the list of tags associated with the notebook.
	Tags []twapi.Relationship `json:"tags"`

	// CreatedAt is the date and time when the notebook was created.
	CreatedAt *time.Time `json:"createdAt"`

	// CreatedBy is the user who created the notebook.
	CreatedBy *int64 `json:"createdBy"`

	// UpdatedAt is the date and time when the notebook was last updated.
	UpdatedAt *time.Time `json:"updatedAt"`

	// UpdatedBy is the user who last updated the notebook.
	UpdatedBy *int64 `json:"updatedBy"`

	// DeletedAt is the date and time when the notebook was deleted, if it was
	// deleted.
	DeletedAt *time.Time `json:"deletedAt"`

	// DeletedBy is the ID of the user who deleted the notebook, if it was
	// deleted.
	DeletedBy *int64 `json:"deletedBy"`

	// Deleted indicates whether the notebook has been deleted.
	Deleted bool `json:"deleted"`
}

Notebook is a space where teams can create, share, and organize written content in a structured way. It’s commonly used for documenting processes, storing meeting notes, capturing research, or drafting ideas that need to be revisited and refined over time. Unlike quick messages or task comments, notebooks provide a more permanent and organized format that can be easily searched and referenced, helping teams maintain a centralized source of knowledge and ensuring important information remains accessible to everyone who needs it.

More information can be found at: https://support.teamwork.com/projects/notebooks

type NotebookCreateRequest added in v1.3.0

type NotebookCreateRequest struct {
	// Path contains the path parameters for the request.
	Path NotebookCreateRequestPath `json:"-"`

	// Name is the name of the notebook. This field is required and must not be
	// empty.
	Name string `json:"name"`

	// Description is the description of the notebook.
	Description *string `json:"description,omitempty"`

	// Contents is the contents of the notebook. This field is required and must
	// not be empty.
	Contents string `json:"contents"`

	// Type is the type of the notebook. It can be "MARKDOWN" or "HTML". This
	// field is required.
	Type NotebookType `json:"type"`

	// TagIDs is the list of tags associated with the notebook.
	TagIDs []int64 `json:"tagIds,omitempty"`
}

NotebookCreateRequest represents the request body for creating a new notebook.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/post-projects-api-v3-projects-project-id-notebooks-json

func NewNotebookCreateRequest added in v1.3.0

func NewNotebookCreateRequest(projectID int64, name, contents string, typ NotebookType) NotebookCreateRequest

NewNotebookCreateRequest creates a new NotebookCreateRequest with the provided required fields.

func (NotebookCreateRequest) HTTPRequest added in v1.3.0

func (m NotebookCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the NotebookCreateRequest.

type NotebookCreateRequestPath added in v1.3.0

type NotebookCreateRequestPath struct {
	// ProjectID is the unique identifier of the project that will contain the
	// notebook.
	ProjectID int64
}

NotebookUpdateRequestPath contains the path parameters for creating a notebook.

type NotebookCreateResponse added in v1.3.0

type NotebookCreateResponse struct {
	// Notebook is the created notebook.
	Notebook Notebook `json:"notebook"`
}

NotebookCreateResponse represents the response body for creating a new notebook.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/post-projects-api-v3-projects-project-id-notebooks-json

func NotebookCreate added in v1.3.0

func NotebookCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req NotebookCreateRequest,
) (*NotebookCreateResponse, error)

NotebookCreate creates a new notebook using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startNotebookServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	notebookRequest :=
		projects.NewNotebookCreateRequest(777, "New Notebook", "An amazing content", projects.NotebookTypeMarkdown)
	notebookRequest.Description = twapi.Ptr("This is a new notebook created via the API.")

	notebookResponse, err := projects.NotebookCreate(ctx, engine, notebookRequest)
	if err != nil {
		fmt.Printf("failed to create notebook: %s", err)
	} else {
		fmt.Printf("created notebook with identifier %d\n", notebookResponse.Notebook.ID)
	}

}

func startNotebookServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/projects/{id}/notebooks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebooks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created notebook with identifier 12345

func (*NotebookCreateResponse) HandleHTTPResponse added in v1.3.0

func (n *NotebookCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the NotebookCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type NotebookDeleteRequest added in v1.3.0

type NotebookDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path NotebookDeleteRequestPath
}

NotebookDeleteRequest represents the request body for deleting a notebook.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/delete-projects-api-v3-notebooks-notebook-id-json

func NewNotebookDeleteRequest added in v1.3.0

func NewNotebookDeleteRequest(notebookID int64) NotebookDeleteRequest

NewNotebookDeleteRequest creates a new NotebookDeleteRequest with the provided notebook ID.

func (NotebookDeleteRequest) HTTPRequest added in v1.3.0

func (m NotebookDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the NotebookDeleteRequest.

type NotebookDeleteRequestPath added in v1.3.0

type NotebookDeleteRequestPath struct {
	// ID is the unique identifier of the notebook to be deleted.
	ID int64
}

NotebookDeleteRequestPath contains the path parameters for deleting a notebook.

type NotebookDeleteResponse added in v1.3.0

type NotebookDeleteResponse struct{}

NotebookDeleteResponse represents the response body for deleting a notebook.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/delete-projects-api-v3-notebooks-notebook-id-json

func NotebookDelete added in v1.3.0

func NotebookDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req NotebookDeleteRequest,
) (*NotebookDeleteResponse, error)

NotebookDelete deletes a notebook using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startNotebookServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.NotebookDelete(ctx, engine, projects.NewNotebookDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete notebook: %s", err)
	} else {
		fmt.Println("notebook deleted!")
	}

}

func startNotebookServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/projects/{id}/notebooks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebooks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

notebook deleted!

func (*NotebookDeleteResponse) HandleHTTPResponse added in v1.3.0

func (m *NotebookDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the NotebookDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type NotebookGetRequest added in v1.3.0

type NotebookGetRequest struct {
	// Path contains the path parameters for the request.
	Path NotebookGetRequestPath
}

NotebookGetRequest represents the request body for loading a single notebook.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/get-projects-api-v3-notebooks-notebook-id-json

func NewNotebookGetRequest added in v1.3.0

func NewNotebookGetRequest(notebookID int64) NotebookGetRequest

NewNotebookGetRequest creates a new NotebookGetRequest with the provided notebook ID. The ID is required to load a notebook.

func (NotebookGetRequest) HTTPRequest added in v1.3.0

func (m NotebookGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the NotebookGetRequest.

type NotebookGetRequestPath added in v1.3.0

type NotebookGetRequestPath struct {
	// ID is the unique identifier of the notebook to be retrieved.
	ID int64 `json:"id"`
}

NotebookGetRequestPath contains the path parameters for loading a single notebook.

type NotebookGetResponse added in v1.3.0

type NotebookGetResponse struct {
	Notebook Notebook `json:"notebook"`
}

NotebookGetResponse contains all the information related to a notebook.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/get-projects-api-v3-notebooks-notebook-id-json

func NotebookGet added in v1.3.0

func NotebookGet(
	ctx context.Context,
	engine *twapi.Engine,
	req NotebookGetRequest,
) (*NotebookGetResponse, error)

NotebookGet retrieves a single notebook using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startNotebookServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	notebookResponse, err := projects.NotebookGet(ctx, engine, projects.NewNotebookGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve notebook: %s", err)
	} else {
		fmt.Printf("retrieved notebook with identifier %d\n", notebookResponse.Notebook.ID)
	}

}

func startNotebookServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/projects/{id}/notebooks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebooks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved notebook with identifier 12345

func (*NotebookGetResponse) HandleHTTPResponse added in v1.3.0

func (m *NotebookGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the NotebookGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type NotebookListRequest added in v1.3.0

type NotebookListRequest struct {
	// Filters contains the filters for loading multiple notebooks.
	Filters NotebookListRequestFilters
}

NotebookListRequest represents the request body for loading multiple notebooks.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/get-projects-api-v3-notebooks-json

func NewNotebookListRequest added in v1.3.0

func NewNotebookListRequest() NotebookListRequest

NewNotebookListRequest creates a new NotebookListRequest with default values.

func (NotebookListRequest) HTTPRequest added in v1.3.0

func (m NotebookListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the NotebookListRequest.

type NotebookListRequestFilters added in v1.3.0

type NotebookListRequestFilters struct {
	// ProjectIDs is an optional list of project IDs to filter notebooks by
	// projects. If provided, only notebooks belonging to the specified projects
	// will be returned.
	ProjectIDs []int64

	// SearchTerm is an optional search term to filter notebooks by name or
	// description.
	SearchTerm string

	// TagIDs is an optional list of tag IDs to filter notebooks by tags.
	TagIDs []int64

	// MatchAllTags is an optional flag to indicate if all tags must match. If set
	// to true, only notebooks matching all specified tags will be returned.
	MatchAllTags *bool

	// IncludeContents is an optional flag to indicate if the contents of the
	// notebooks should be included in the response. If set to true, the contents
	// will be included; otherwise, they will be omitted. Defaults to true.
	IncludeContents *bool

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of notebooks to retrieve per page. Defaults to 50.
	PageSize int64
}

NotebookListRequestFilters contains the filters for loading multiple notebooks.

type NotebookListResponse added in v1.3.0

type NotebookListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Notebooks []Notebook `json:"notebooks"`
	// contains filtered or unexported fields
}

NotebookListResponse contains information by multiple notebooks matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/get-projects-api-v3-notebooks-json

func NotebookList added in v1.3.0

func NotebookList(
	ctx context.Context,
	engine *twapi.Engine,
	req NotebookListRequest,
) (*NotebookListResponse, error)

NotebookList retrieves multiple notebooks using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startNotebookServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	notebooksRequest := projects.NewNotebookListRequest()
	notebooksRequest.Filters.SearchTerm = "Example"

	notebooksResponse, err := projects.NotebookList(ctx, engine, notebooksRequest)
	if err != nil {
		fmt.Printf("failed to list notebooks: %s", err)
	} else {
		for _, notebook := range notebooksResponse.Notebooks {
			fmt.Printf("retrieved notebook with identifier %d\n", notebook.ID)
		}
	}

}

func startNotebookServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/projects/{id}/notebooks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebooks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved notebook with identifier 12345
retrieved notebook with identifier 12346

func (*NotebookListResponse) HandleHTTPResponse added in v1.3.0

func (m *NotebookListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the NotebookListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*NotebookListResponse) Iterate added in v1.3.0

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*NotebookListResponse) SetRequest added in v1.3.0

func (m *NotebookListResponse) SetRequest(req NotebookListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type NotebookType added in v1.3.0

type NotebookType string

NotebookType defines the notebook type.

const (
	// NotebookTypeMarkdown indicates a notebook with markdown content.
	NotebookTypeMarkdown NotebookType = "MARKDOWN"

	// NotebookTypeHTML indicates a notebook with HTML content.
	NotebookTypeHTML NotebookType = "HTML"
)

List of possible notebook types.

type NotebookUpdateRequest added in v1.3.0

type NotebookUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path NotebookUpdateRequestPath `json:"-"`

	// Name is the name of the notebook.
	Name *string `json:"name,omitempty"`

	// Description is the description of the notebook.
	Description *string `json:"description,omitempty"`

	// Contents is the contents of the notebook.
	Contents *string `json:"contents,omitempty"`

	// Type is the type of the notebook. It can be "MARKDOWN" or "HTML".
	Type *NotebookType `json:"type,omitempty"`

	// TagIDs is the list of tags associated with the notebook.
	TagIDs []int64 `json:"tagIds,omitempty"`
}

NotebookUpdateRequest represents the request body for updating a notebook. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/patch-projects-api-v3-notebooks-notebook-id-json

func NewNotebookUpdateRequest added in v1.3.0

func NewNotebookUpdateRequest(notebookID int64) NotebookUpdateRequest

NewNotebookUpdateRequest creates a new NotebookUpdateRequest with the provided notebook ID. The ID is required to update a notebook.

func (NotebookUpdateRequest) HTTPRequest added in v1.3.0

func (m NotebookUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the NotebookUpdateRequest.

type NotebookUpdateRequestPath added in v1.3.0

type NotebookUpdateRequestPath struct {
	// ID is the unique identifier of the notebook to be updated.
	ID int64
}

NotebookUpdateRequestPath contains the path parameters for updating a notebook.

type NotebookUpdateResponse added in v1.3.0

type NotebookUpdateResponse struct {
	// Notebook is the updated notebook.
	Notebook Notebook `json:"notebook"`
}

NotebookUpdateResponse represents the response body for updating a notebook.

https://apidocs.teamwork.com/docs/teamwork/v3/notebooks/patch-projects-api-v3-notebooks-notebook-id-json

func NotebookUpdate added in v1.3.0

func NotebookUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req NotebookUpdateRequest,
) (*NotebookUpdateResponse, error)

NotebookUpdate updates a notebook using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startNotebookServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	notebookRequest := projects.NewNotebookUpdateRequest(12345)
	notebookRequest.Description = twapi.Ptr("This is an updated description.")

	_, err = projects.NotebookUpdate(ctx, engine, notebookRequest)
	if err != nil {
		fmt.Printf("failed to update notebook: %s", err)
	} else {
		fmt.Println("notebook updated!")
	}

}

func startNotebookServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/projects/{id}/notebooks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebook":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/notebooks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"notebooks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

notebook updated!

func (*NotebookUpdateResponse) HandleHTTPResponse added in v1.3.0

func (m *NotebookUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the NotebookUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Project added in v0.0.3

type Project struct {
	// ID is the unique identifier of the project.
	ID int64 `json:"id"`

	// Description is an optional description of the project.
	Description *string `json:"description"`

	// Name is the name of the project.
	Name string `json:"name"`

	// StartAt is the start date of the project.
	StartAt *time.Time `json:"startAt"`

	// EndAt is the end date of the project.
	EndAt *time.Time `json:"endAt"`

	// Company is the company associated with the project.
	Company twapi.Relationship `json:"company"`

	// Owner is the user who owns the project.
	Owner *twapi.Relationship `json:"projectOwner"`

	// Tags is a list of tags associated with the project.
	Tags []twapi.Relationship `json:"tags"`

	// CreatedAt is the date and time when the project was created.
	CreatedAt *time.Time `json:"createdAt"`

	// CreatedBy is the ID of the user who created the project.
	CreatedBy *int64 `json:"createdBy"`

	// UpdatedAt is the date and time when the project was last updated.
	UpdatedAt *time.Time `json:"updatedAt"`

	// UpdatedBy is the ID of the user who last updated the project.
	UpdatedBy *int64 `json:"updatedBy"`

	// CompletedAt is the date and time when the project was completed.
	CompletedAt *time.Time `json:"completedAt"`

	// CompletedBy is the ID of the user who completed the project.
	CompletedBy *int64 `json:"completedBy"`

	// Status is the status of the project. It can be "active", "inactive"
	// (archived) or "deleted".
	Status string `json:"status"`

	// Type is the type of the project. It can be "normal", "tasklists-template",
	// "projects-template", "personal", "holder-project", "tentative" or
	// "global-messages".
	Type string `json:"type"`
}

Project serves as the central workspace for organizing and managing a specific piece of work or initiative. Each project provides a dedicated area where teams can plan tasks, assign responsibilities, set deadlines, and track progress toward shared goals. Projects include tools for communication, file sharing, milestones, and time tracking, allowing teams to stay aligned and informed throughout the entire lifecycle of the work. Whether it's a product launch, client engagement, or internal initiative, projects in Teamwork.com help teams structure their efforts, collaborate more effectively, and deliver results with greater visibility and accountability.

More information can be found at: https://support.teamwork.com/projects/getting-started/projects-overview

type ProjectCreateRequest

type ProjectCreateRequest struct {
	// Name is the name of the project.
	Name string `json:"name"`

	// Description is an optional description of the project.
	Description *string `json:"description,omitempty"`

	// StartAt is an optional start date for the project. By default it doesn't
	// have a start date.
	StartAt *LegacyDate `json:"start-date,omitempty"`

	// EndAt is an optional end date for the project. By default it doesn't have
	// an end date.
	EndAt *LegacyDate `json:"end-date,omitempty"`

	// CompanyID is an optional ID of the company/client associated with the
	// project. By default it is the ID of the company of the logged user
	// creating the project.
	CompanyID int64 `json:"companyId"`

	// OwnerID is an optional ID of the user who owns the project. By default it
	// is the ID of the logged user creating the project.
	OwnerID *int64 `json:"projectOwnerId,omitempty"`

	// TagIDs is an optional list of tag IDs associated with the project.
	TagIDs []int64 `json:"tagIds,omitempty"`
}

ProjectCreateRequest represents the request body for creating a new project.

https://apidocs.teamwork.com/docs/teamwork/v1/projects/post-projects-json

func NewProjectCreateRequest added in v0.0.6

func NewProjectCreateRequest(name string) ProjectCreateRequest

NewProjectCreateRequest creates a new ProjectCreateRequest with the provided name. The name is required to create a new project.

func (ProjectCreateRequest) HTTPRequest

func (p ProjectCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the ProjectCreateRequest.

type ProjectCreateResponse

type ProjectCreateResponse struct {
	// ID is the unique identifier of the created project.
	ID LegacyNumber `json:"id"`
}

ProjectCreateResponse represents the response body for creating a new project.

https://apidocs.teamwork.com/docs/teamwork/v1/projects/post-projects-json

func ProjectCreate

func ProjectCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req ProjectCreateRequest,
) (*ProjectCreateResponse, error)

ProjectCreate creates a new project using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startProjectServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	projectRequest := projects.NewProjectCreateRequest("New Project")
	projectRequest.Description = twapi.Ptr("This is a new project created via the API.")
	projectRequest.StartAt = twapi.Ptr(projects.LegacyDate(time.Now().AddDate(0, 0, 1)))
	projectRequest.EndAt = twapi.Ptr(projects.LegacyDate(time.Now().AddDate(0, 0, 30)))
	projectRequest.CompanyID = 12345
	projectRequest.OwnerID = twapi.Ptr(int64(67890))
	projectRequest.TagIDs = []int64{11111, 22222}

	projectResponse, err := projects.ProjectCreate(ctx, engine, projectRequest)
	if err != nil {
		fmt.Printf("failed to create project: %s", err)
	} else {
		fmt.Printf("created project with identifier %d\n", projectResponse.ID)
	}

}

func startProjectServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"project":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projects":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created project with identifier 12345

func (*ProjectCreateResponse) HandleHTTPResponse

func (p *ProjectCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the ProjectCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type ProjectDeleteRequest added in v0.0.3

type ProjectDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path ProjectDeleteRequestPath
}

ProjectDeleteRequest represents the request body for deleting a project.

https://apidocs.teamwork.com/docs/teamwork/v1/projects/delete-projects-id-json

func NewProjectDeleteRequest added in v0.0.6

func NewProjectDeleteRequest(projectID int64) ProjectDeleteRequest

NewProjectDeleteRequest creates a new ProjectDeleteRequest with the provided project ID.

func (ProjectDeleteRequest) HTTPRequest added in v0.0.3

func (p ProjectDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the ProjectDeleteRequest.

type ProjectDeleteRequestPath added in v0.0.6

type ProjectDeleteRequestPath struct {
	// ID is the unique identifier of the project to be deleted.
	ID int64
}

ProjectDeleteRequestPath contains the path parameters for deleting a project.

type ProjectDeleteResponse added in v0.0.3

type ProjectDeleteResponse struct{}

ProjectDeleteResponse represents the response body for deleting a project.

https://apidocs.teamwork.com/docs/teamwork/v1/projects/delete-projects-id-json

func ProjectDelete added in v0.0.3

func ProjectDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req ProjectDeleteRequest,
) (*ProjectDeleteResponse, error)

ProjectDelete deletes a project using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startProjectServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.ProjectDelete(ctx, engine, projects.NewProjectDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete project: %s", err)
	} else {
		fmt.Println("project deleted!")
	}

}

func startProjectServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"project":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projects":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

project deleted!

func (*ProjectDeleteResponse) HandleHTTPResponse added in v0.0.3

func (p *ProjectDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the ProjectDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type ProjectGetRequest added in v0.0.6

type ProjectGetRequest struct {
	// Path contains the path parameters for the request.
	Path ProjectGetRequestPath
}

ProjectGetRequest represents the request body for loading a single project.

https://apidocs.teamwork.com/docs/teamwork/v3/projects/get-projects-api-v3-projects-project-id-json

func NewProjectGetRequest added in v0.0.6

func NewProjectGetRequest(projectID int64) ProjectGetRequest

NewProjectGetRequest creates a new ProjectGetRequest with the provided project ID. The ID is required to load a project.

func (ProjectGetRequest) HTTPRequest added in v0.0.6

func (p ProjectGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the ProjectGetRequest.

type ProjectGetRequestPath added in v0.0.6

type ProjectGetRequestPath struct {
	// ID is the unique identifier of the project to be retrieved.
	ID int64 `json:"id"`
}

ProjectGetRequestPath contains the path parameters for loading a single project.

type ProjectGetResponse added in v0.0.6

type ProjectGetResponse struct {
	Project Project `json:"project"`
}

ProjectGetResponse contains all the information related to a project.

https://apidocs.teamwork.com/docs/teamwork/v3/projects/get-projects-api-v3-projects-project-id-json

func ProjectGet added in v0.0.6

func ProjectGet(
	ctx context.Context,
	engine *twapi.Engine,
	req ProjectGetRequest,
) (*ProjectGetResponse, error)

ProjectGet retrieves a single project using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startProjectServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	projectResponse, err := projects.ProjectGet(ctx, engine, projects.NewProjectGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve project: %s", err)
	} else {
		fmt.Printf("retrieved project with identifier %d\n", projectResponse.Project.ID)
	}

}

func startProjectServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"project":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projects":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved project with identifier 12345

func (*ProjectGetResponse) HandleHTTPResponse added in v0.0.6

func (p *ProjectGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the ProjectGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type ProjectListRequest added in v0.0.6

type ProjectListRequest struct {
	// Filters contains the filters for loading multiple projects.
	Filters ProjectListRequestFilters
}

ProjectListRequest represents the request body for loading multiple projects.

https://apidocs.teamwork.com/docs/teamwork/v3/projects/get-projects-api-v3-projects-json

func NewProjectListRequest added in v0.0.6

func NewProjectListRequest() ProjectListRequest

NewProjectListRequest creates a new ProjectListRequest with default values.

func (ProjectListRequest) HTTPRequest added in v0.0.6

func (p ProjectListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the ProjectListRequest.

type ProjectListRequestFilters added in v0.0.6

type ProjectListRequestFilters struct {
	// SearchTerm is an optional search term to filter projects by name or
	// description.
	SearchTerm string

	// TagIDs is an optional list of tag IDs to filter projects by tags.
	TagIDs []int64

	// MatchAllTags is an optional flag to indicate if all tags must match. If
	// set to true, only projects matching all specified tags will be returned.
	MatchAllTags *bool

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of projects to retrieve per page. Defaults to 50.
	PageSize int64
}

ProjectListRequestFilters contains the filters for loading multiple projects.

type ProjectListResponse added in v0.0.6

type ProjectListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Projects []Project `json:"projects"`
	// contains filtered or unexported fields
}

ProjectListResponse contains information by multiple projects matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/projects/get-projects-api-v3-projects-json

func ProjectList added in v0.0.6

func ProjectList(
	ctx context.Context,
	engine *twapi.Engine,
	req ProjectListRequest,
) (*ProjectListResponse, error)

ProjectList retrieves multiple projects using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startProjectServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	projectsRequest := projects.NewProjectListRequest()
	projectsRequest.Filters.SearchTerm = "Example"

	projectsResponse, err := projects.ProjectList(ctx, engine, projectsRequest)
	if err != nil {
		fmt.Printf("failed to list projects: %s", err)
	} else {
		for _, project := range projectsResponse.Projects {
			fmt.Printf("retrieved project with identifier %d\n", project.ID)
		}
	}

}

func startProjectServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"project":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projects":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved project with identifier 12345
retrieved project with identifier 12346

func (*ProjectListResponse) HandleHTTPResponse added in v0.0.6

func (p *ProjectListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the ProjectListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*ProjectListResponse) Iterate added in v0.0.7

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*ProjectListResponse) SetRequest added in v0.0.7

func (p *ProjectListResponse) SetRequest(req ProjectListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type ProjectMemberAddRequest added in v0.3.0

type ProjectMemberAddRequest struct {
	// Path contains the path parameters for the request.
	Path ProjectMemberAddRequestPath `json:"-"`

	// UserIDs is a list of user IDs to add as project members.
	UserIDs []int64 `json:"userIds"`
}

ProjectMemberAddRequest represents the request body for adding users as project members.

https://apidocs.teamwork.com/docs/teamwork/v3/people/put-projects-api-v3-projects-project-id-people-json

func NewProjectMemberAddRequest added in v0.3.0

func NewProjectMemberAddRequest(projectID int64, userIDs ...int64) ProjectMemberAddRequest

NewProjectMemberAddRequest creates a new ProjectMemberAddRequest with the provided project and user IDs.

func (ProjectMemberAddRequest) HTTPRequest added in v0.3.0

func (u ProjectMemberAddRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the ProjectMemberAddRequest.

type ProjectMemberAddRequestPath added in v0.3.0

type ProjectMemberAddRequestPath struct {
	ProjectID int64 `json:"projectId"`
}

ProjectMemberAddRequestPath contains the path parameters for adding users as project members.

type ProjectMemberAddResponse added in v0.3.0

type ProjectMemberAddResponse struct{}

ProjectMemberAddResponse represents the response body for adding users as project members.

https://apidocs.teamwork.com/docs/teamwork/v3/people/put-projects-api-v3-projects-project-id-people-json

func ProjectMemberAdd added in v0.3.0

func ProjectMemberAdd(
	ctx context.Context,
	engine *twapi.Engine,
	req ProjectMemberAddRequest,
) (*ProjectMemberAddResponse, error)

ProjectMemberAdd adds users to a project.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startProjectMemberServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.ProjectMemberAdd(ctx, engine, projects.NewProjectMemberAddRequest(123, 456, 789))
	if err != nil {
		fmt.Printf("failed to add project members: %s", err)
	} else {
		fmt.Println("added users as project members")
	}

}

func startProjectMemberServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("PUT /projects/api/v3/projects/{id}/people", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "123" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"usersAdded":[456],"usersAlreadyInProject":[789],"usersNotAdded":[]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

added users as project members

func (*ProjectMemberAddResponse) HandleHTTPResponse added in v0.3.0

func (u *ProjectMemberAddResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the ProjectMemberAddResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type ProjectRate added in v1.5.0

type ProjectRate struct {
	// ProjectID is the ID of the project.
	ProjectID int64 `json:"projectId"`

	// Rate is the monetary amount in the smallest currency unit
	// (e.g., cents). For example, €10.00 is represented as 1000.
	Rate int64 `json:"rate"`

	// Currency is the currency information.
	Currency Currency `json:"currency"`
}

ProjectRate represents a project's rate information.

type ProjectUpdateRequest added in v0.0.3

type ProjectUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path ProjectUpdateRequestPath

	// Name is the name of the project.
	Name *string `json:"name,omitempty"`

	// Description is the project description.
	Description *string `json:"description,omitempty"`

	// StartAt is the start date for the project.
	StartAt *LegacyDate `json:"start-date,omitempty"`

	// EndAt is the end date for the project.
	EndAt *LegacyDate `json:"end-date,omitempty"`

	// CompanyID is the company/client associated with the project.
	CompanyID *int64 `json:"companyId,omitempty"`

	// OwnerID is the ID of the user who owns the project.
	OwnerID *int64 `json:"projectOwnerId,omitempty"`

	// TagIDs is the list of tag IDs associated with the project.
	TagIDs []int64 `json:"tagIds,omitempty"`
}

ProjectUpdateRequest represents the request body for updating a project. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v1/projects/put-projects-id-json

func NewProjectUpdateRequest added in v0.0.6

func NewProjectUpdateRequest(projectID int64) ProjectUpdateRequest

NewProjectUpdateRequest creates a new ProjectUpdateRequest with the provided project ID. The ID is required to update a project.

func (ProjectUpdateRequest) HTTPRequest added in v0.0.3

func (p ProjectUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the ProjectUpdateRequest.

type ProjectUpdateRequestPath added in v0.0.6

type ProjectUpdateRequestPath struct {
	// ID is the unique identifier of the project to be updated.
	ID int64
}

ProjectUpdateRequestPath contains the path parameters for updating a project.

type ProjectUpdateResponse added in v0.0.3

type ProjectUpdateResponse struct{}

ProjectUpdateResponse represents the response body for updating a project.

https://apidocs.teamwork.com/docs/teamwork/v1/projects/put-projects-id-json

func ProjectUpdate added in v0.0.3

func ProjectUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req ProjectUpdateRequest,
) (*ProjectUpdateResponse, error)

ProjectUpdate updates a project using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startProjectServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	projectRequest := projects.NewProjectUpdateRequest(12345)
	projectRequest.Description = twapi.Ptr("This is an updated description.")

	_, err = projects.ProjectUpdate(ctx, engine, projectRequest)
	if err != nil {
		fmt.Printf("failed to update project: %s", err)
	} else {
		fmt.Println("project updated!")
	}

}

func startProjectServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"project":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/projects", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projects":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

project updated!

func (*ProjectUpdateResponse) HandleHTTPResponse added in v0.0.3

func (p *ProjectUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the ProjectUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type ProjectUserRateRequest added in v1.5.0

type ProjectUserRateRequest struct {
	// FromDate is the date from which this rate is effective.
	FromDate *time.Time `json:"fromDate,omitempty"`

	// User is the user relationship.
	User twapi.Relationship `json:"user"`

	// UserRate is the rate for the user as a monetary amount in the smallest
	// currency unit (e.g., cents).
	UserRate int64 `json:"userRate"`
}

ProjectUserRateRequest represents a user rate update within a project.

type RateInstallationUserBulkUpdateRequest added in v1.5.0

type RateInstallationUserBulkUpdateRequest struct {
	// All indicates whether to update all users.
	All bool `json:"all,omitempty"`

	// IDs contains the user IDs to update (if All is false).
	IDs []int64 `json:"ids,omitempty"`

	// ExcludeIDs contains user IDs to exclude (if All is true).
	ExcludeIDs []int64 `json:"excludeIds,omitempty"`

	// CurrencyID is the ID of the currency for the rate (optional, only used in multi-currency mode).
	CurrencyID *int64 `json:"currencyId,omitempty"`

	// UserRate is the new rate for the users as a monetary amount in the
	// smallest currency unit (e.g., cents). Use nil to clear/remove the rate.
	UserRate *int64 `json:"userRate"`
}

RateInstallationUserBulkUpdateRequest represents the request for bulk updating installation user rates.

func NewRateInstallationUserBulkUpdateRequest added in v1.5.0

func NewRateInstallationUserBulkUpdateRequest(rate *int64) RateInstallationUserBulkUpdateRequest

NewRateInstallationUserBulkUpdateRequest creates a new RateInstallationUserBulkUpdateRequest. Rate should be provided in the smallest currency unit (e.g., cents). For example, €10.00 is represented as 1000.

func (RateInstallationUserBulkUpdateRequest) HTTPRequest added in v1.5.0

HTTPRequest creates an HTTP request for the RateInstallationUserBulkUpdateRequest.

type RateInstallationUserBulkUpdateResponse added in v1.5.0

type RateInstallationUserBulkUpdateResponse struct {
	// All indicates whether all users were updated.
	All bool `json:"all"`

	// IDs contains the user IDs that were updated.
	IDs []int64 `json:"ids"`

	// ExcludeIDs contains user IDs that were excluded.
	ExcludeIDs []int64 `json:"excludeIds"`

	// Rate is the rate that was set, as a monetary amount in the smallest
	// currency unit (e.g., cents).
	Rate int64 `json:"rate"`
}

RateInstallationUserBulkUpdateResponse represents the response for bulk updating installation user rates.

func RateInstallationUserBulkUpdate added in v1.5.0

func RateInstallationUserBulkUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req RateInstallationUserBulkUpdateRequest,
) (*RateInstallationUserBulkUpdateResponse, error)

RateInstallationUserBulkUpdate bulk updates installation user rates using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	var rate int64 = 6000
	req := projects.NewRateInstallationUserBulkUpdateRequest(&rate) // Rate (cents)
	req.IDs = []int64{12345}                                        // Update specific user IDs

	_, err = projects.RateInstallationUserBulkUpdate(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to bulk update installation user rates: %s", err)
	} else {
		fmt.Printf("bulk updated installation user rates\n")
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

bulk updated installation user rates

func (*RateInstallationUserBulkUpdateResponse) HandleHTTPResponse added in v1.5.0

func (r *RateInstallationUserBulkUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateInstallationUserBulkUpdateResponse.

type RateInstallationUserGetRequest added in v1.5.0

type RateInstallationUserGetRequest struct {
	// Path contains the path parameters for the request.
	Path RateInstallationUserGetRequestPath

	// Filters contains the filters for the request.
	Filters RateInstallationUserGetRequestFilters
}

RateInstallationUserGetRequest represents the request for getting an installation user rate.

func NewRateInstallationUserGetRequest added in v1.5.0

func NewRateInstallationUserGetRequest(userID int64) RateInstallationUserGetRequest

NewRateInstallationUserGetRequest creates a new RateInstallationUserGetRequest with the provided user ID.

func (RateInstallationUserGetRequest) HTTPRequest added in v1.5.0

func (r RateInstallationUserGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateInstallationUserGetRequest.

type RateInstallationUserGetRequestFilters added in v1.5.0

type RateInstallationUserGetRequestFilters struct {
	// Include specifies which related data to include.
	Include []RateInstallationUserGetRequestSideload
}

RateInstallationUserGetRequestFilters contains the filters for getting an installation user rate.

type RateInstallationUserGetRequestPath added in v1.5.0

type RateInstallationUserGetRequestPath struct {
	// UserID is the unique identifier of the user whose installation rate is to be retrieved.
	UserID int64
}

RateInstallationUserGetRequestPath contains the path parameters for getting an installation user rate.

type RateInstallationUserGetRequestSideload added in v1.5.0

type RateInstallationUserGetRequestSideload string

RateInstallationUserGetRequestSideload specifies which related resources to include in the response.

const (
	RateInstallationUserGetRequestSideloadCurrencies RateInstallationUserGetRequestSideload = "currencies"
)

type RateInstallationUserGetResponse added in v1.5.0

type RateInstallationUserGetResponse struct {
	// UserRate is the user's rate (legacy field) as a monetary amount in the
	// smallest currency unit (e.g., cents).
	UserRate int64 `json:"userRate"`

	// UserRates contains rates in different currencies (key is currency ID as string for JSON compatibility).
	UserRates map[string]MultiCurrencyRate `json:"userRates,omitempty"`

	// Included contains related data.
	Included struct {
		Currencies map[string]Currency `json:"currencies"`
	} `json:"included"`
}

RateInstallationUserGetResponse represents the response for getting an installation user rate.

func RateInstallationUserGet added in v1.5.0

func RateInstallationUserGet(
	ctx context.Context,
	engine *twapi.Engine,
	req RateInstallationUserGetRequest,
) (*RateInstallationUserGetResponse, error)

RateInstallationUserGet retrieves an installation user rate using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	req := projects.NewRateInstallationUserGetRequest(12345) // User ID
	req.Filters.Include = []projects.RateInstallationUserGetRequestSideload{
		projects.RateInstallationUserGetRequestSideloadCurrencies,
	}

	_, err = projects.RateInstallationUserGet(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to get installation user rate: %s", err)
	} else {
		fmt.Printf("retrieved installation user rate with identifier %d\n", 12345)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved installation user rate with identifier 12345

func (*RateInstallationUserGetResponse) HandleHTTPResponse added in v1.5.0

func (r *RateInstallationUserGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateInstallationUserGetResponse.

type RateInstallationUserListRequest added in v1.5.0

type RateInstallationUserListRequest struct {
	// Filters contains the filters for the request.
	Filters RateInstallationUserListRequestFilters
}

RateInstallationUserListRequest represents the request for listing installation user rates.

func NewRateInstallationUserListRequest added in v1.5.0

func NewRateInstallationUserListRequest() RateInstallationUserListRequest

NewRateInstallationUserListRequest creates a new RateInstallationUserListRequest with default values.

func (RateInstallationUserListRequest) HTTPRequest added in v1.5.0

func (r RateInstallationUserListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateInstallationUserListRequest.

type RateInstallationUserListRequestFilters added in v1.5.0

type RateInstallationUserListRequestFilters struct {
	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of rates to retrieve per page. Defaults to 50.
	PageSize int64
}

RateInstallationUserListRequestFilters contains the filters for listing installation user rates.

type RateInstallationUserListResponse added in v1.5.0

type RateInstallationUserListResponse struct {

	// Meta contains pagination information.
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`

	// UserRates contains the list of user rates.
	UserRates []struct {
		User twapi.Relationship `json:"user"`
		// Rate is the monetary amount in the smallest currency unit
		// (e.g., cents). For example, €10.00 is represented as 1000.
		Rate int64 `json:"rate"`
	} `json:"userRates"`

	InstallationUserRates map[int64]MultiCurrencyInstallationUserRate `json:"installationUserRates"`

	// Included contains related data.
	Included struct {
		Currencies map[string]Currency           `json:"currencies"`
		Users      map[string]twapi.Relationship `json:"users"`
	} `json:"included"`
	// contains filtered or unexported fields
}

RateInstallationUserListResponse represents the response for listing installation user rates.

func RateInstallationUserList added in v1.5.0

func RateInstallationUserList(
	ctx context.Context,
	engine *twapi.Engine,
	req RateInstallationUserListRequest,
) (*RateInstallationUserListResponse, error)

RateInstallationUserList retrieves installation user rates using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	req := projects.NewRateInstallationUserListRequest()
	// Configure pagination
	req.Filters.Page = 1
	req.Filters.PageSize = 10

	resp, err := projects.RateInstallationUserList(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to list installation user rates: %s", err)
	} else {
		fmt.Printf("retrieved %d installation user rate(s)\n", len(resp.UserRates))
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved 1 installation user rate(s)

func (*RateInstallationUserListResponse) HandleHTTPResponse added in v1.5.0

func (r *RateInstallationUserListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateInstallationUserListResponse.

func (*RateInstallationUserListResponse) Iterate added in v1.5.0

Iterate returns the request set to the next page, if available.

func (*RateInstallationUserListResponse) SetRequest added in v1.5.0

SetRequest sets the request used to load this response.

type RateInstallationUserUpdateRequest added in v1.5.0

type RateInstallationUserUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path RateInstallationUserUpdateRequestPath `json:"-"`

	// CurrencyID is the ID of the currency for the rate (optional, only used in multi-currency mode).
	CurrencyID *int64 `json:"currencyId,omitempty"`

	// UserRate is the new rate for the user as a monetary amount in the
	// smallest currency unit (e.g., cents). Use nil to clear/remove the rate.
	UserRate *int64 `json:"userRate"`
}

RateInstallationUserUpdateRequest represents the request for updating an installation user rate.

func NewRateInstallationUserUpdateRequest added in v1.5.0

func NewRateInstallationUserUpdateRequest(userID int64, rate *int64) RateInstallationUserUpdateRequest

NewRateInstallationUserUpdateRequest creates a new RateInstallationUserUpdateRequest. Rate should be provided in the smallest currency unit (e.g., cents). For example, €10.00 is represented as 1000.

func (RateInstallationUserUpdateRequest) HTTPRequest added in v1.5.0

func (r RateInstallationUserUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateInstallationUserUpdateRequest.

type RateInstallationUserUpdateRequestPath added in v1.5.0

type RateInstallationUserUpdateRequestPath struct {
	// UserID is the unique identifier of the user whose rate is to be updated.
	UserID int64
}

RateInstallationUserUpdateRequestPath contains the path parameters for updating an installation user rate.

type RateInstallationUserUpdateResponse added in v1.5.0

type RateInstallationUserUpdateResponse struct{}

RateInstallationUserUpdateResponse represents the response for updating an installation user rate.

func RateInstallationUserUpdate added in v1.5.0

func RateInstallationUserUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req RateInstallationUserUpdateRequest,
) (*RateInstallationUserUpdateResponse, error)

RateInstallationUserUpdate updates an installation user rate using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	var rate int64 = 5000
	req := projects.NewRateInstallationUserUpdateRequest(12345, &rate) // User ID, Rate (cents)
	_, err = projects.RateInstallationUserUpdate(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to update user rate: %s", err)
	} else {
		fmt.Printf("updated installation user rate with identifier %d\n", 12345)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

updated installation user rate with identifier 12345

func (*RateInstallationUserUpdateResponse) HandleHTTPResponse added in v1.5.0

func (r *RateInstallationUserUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateInstallationUserUpdateResponse.

type RateProjectAndUsersUpdateRequest added in v1.5.0

type RateProjectAndUsersUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path RateProjectAndUsersUpdateRequestPath `json:"-"`

	// ProjectRate is the new rate for the project as a monetary amount in the
	// smallest currency unit (e.g., cents).
	ProjectRate int64 `json:"projectRate"`

	// UserRates contains the user rates to set as exceptions.
	UserRates []ProjectUserRateRequest `json:"userRates,omitempty"`
}

RateProjectAndUsersUpdateRequest represents the request for updating a project rate and user rates.

func NewRateProjectAndUsersUpdateRequest added in v1.5.0

func NewRateProjectAndUsersUpdateRequest(projectID int64, projectRate int64) RateProjectAndUsersUpdateRequest

NewRateProjectAndUsersUpdateRequest creates a new request. Rate should be provided in the smallest currency unit (e.g., cents). For example, €10.00 is 1000.

func (RateProjectAndUsersUpdateRequest) HTTPRequest added in v1.5.0

func (r RateProjectAndUsersUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateProjectAndUsersUpdateRequest.

type RateProjectAndUsersUpdateRequestPath added in v1.5.0

type RateProjectAndUsersUpdateRequestPath struct {
	// ProjectID is the unique identifier of the project.
	ProjectID int64
}

RateProjectAndUsersUpdateRequestPath contains the path parameters for updating a project and user rates.

type RateProjectAndUsersUpdateResponse added in v1.5.0

type RateProjectAndUsersUpdateResponse struct{}

RateProjectAndUsersUpdateResponse represents the response for updating a project rate and user rates.

func RateProjectAndUsersUpdate added in v1.5.0

func RateProjectAndUsersUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req RateProjectAndUsersUpdateRequest,
) (*RateProjectAndUsersUpdateResponse, error)

RateProjectAndUsersUpdate updates a project rate and user rates using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	var projectRate int64 = 8000
	req := projects.NewRateProjectAndUsersUpdateRequest(67890, projectRate) // Project ID, Rate (cents)
	// Add user rates as exceptions
	var userRate int64 = 9000
	req.UserRates = []projects.ProjectUserRateRequest{
		{User: twapi.Relationship{ID: 12345}, UserRate: userRate},
	}

	_, err = projects.RateProjectAndUsersUpdate(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to update project and users rates: %s", err)
	} else {
		fmt.Printf("updated project and users rates with identifier %d\n", 67890)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

updated project and users rates with identifier 67890

func (*RateProjectAndUsersUpdateResponse) HandleHTTPResponse added in v1.5.0

func (r *RateProjectAndUsersUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateProjectAndUsersUpdateResponse.

type RateProjectGetRequest added in v1.5.0

type RateProjectGetRequest struct {
	// Path contains the path parameters for the request.
	Path RateProjectGetRequestPath

	// Filters contains the filters for the request.
	Filters RateProjectGetRequestFilters
}

RateProjectGetRequest represents the request for getting a project rate.

func NewRateProjectGetRequest added in v1.5.0

func NewRateProjectGetRequest(projectID int64) RateProjectGetRequest

NewRateProjectGetRequest creates a new RateProjectGetRequest with the provided project ID.

func (RateProjectGetRequest) HTTPRequest added in v1.5.0

func (r RateProjectGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateProjectGetRequest.

type RateProjectGetRequestFilters added in v1.5.0

type RateProjectGetRequestFilters struct {
	// Include specifies which related data to include.
	Include []RateProjectGetRequestSideload
}

RateProjectGetRequestFilters contains the filters for getting a project rate.

type RateProjectGetRequestPath added in v1.5.0

type RateProjectGetRequestPath struct {
	// ProjectID is the unique identifier of the project whose rate is to be retrieved.
	ProjectID int64
}

RateProjectGetRequestPath contains the path parameters for getting a project rate.

type RateProjectGetRequestSideload added in v1.5.0

type RateProjectGetRequestSideload string

RateProjectGetRequestSideload specifies which related resources to include in the response.

const (
	RateProjectGetRequestSideloadCurrencies RateProjectGetRequestSideload = "currencies"
)

type RateProjectGetResponse added in v1.5.0

type RateProjectGetResponse struct {
	// ProjectRate is the project's rate as a monetary amount in the smallest
	// currency unit (e.g., cents).
	ProjectRate int64 `json:"projectRate"`

	// Rate is the rate in money format.
	Rate *MultiCurrencyRate `json:"rate,omitempty"`

	// Included contains related data.
	Included struct {
		Currencies map[string]Currency `json:"currencies"`
	} `json:"included"`
}

RateProjectGetResponse represents the response for getting a project rate.

func RateProjectGet added in v1.5.0

func RateProjectGet(
	ctx context.Context,
	engine *twapi.Engine,
	req RateProjectGetRequest,
) (*RateProjectGetResponse, error)

RateProjectGet retrieves a project rate using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	req := projects.NewRateProjectGetRequest(67890) // Project ID
	req.Filters.Include = []projects.RateProjectGetRequestSideload{
		projects.RateProjectGetRequestSideloadCurrencies,
	}

	_, err = projects.RateProjectGet(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to get project rate: %s", err)
	} else {
		fmt.Printf("retrieved project rate with identifier %d\n", 67890)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved project rate with identifier 67890

func (*RateProjectGetResponse) HandleHTTPResponse added in v1.5.0

func (r *RateProjectGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateProjectGetResponse.

type RateProjectUpdateRequest added in v1.5.0

type RateProjectUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path RateProjectUpdateRequestPath `json:"-"`

	// ProjectRate is the new rate for the project as a monetary amount in the
	// smallest currency unit (e.g., cents). Use nil to clear/remove the rate.
	ProjectRate *int64 `json:"projectRate"`
}

RateProjectUpdateRequest represents the request for updating a project rate.

func NewRateProjectUpdateRequest added in v1.5.0

func NewRateProjectUpdateRequest(projectID int64, rate *int64) RateProjectUpdateRequest

NewRateProjectUpdateRequest creates a new request. Rate should be provided in the smallest currency unit (e.g., cents). For example, €10.00 is 1000.

func (RateProjectUpdateRequest) HTTPRequest added in v1.5.0

func (r RateProjectUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateProjectUpdateRequest.

type RateProjectUpdateRequestPath added in v1.5.0

type RateProjectUpdateRequestPath struct {
	// ProjectID is the unique identifier of the project whose rate is to be updated.
	ProjectID int64
}

RateProjectUpdateRequestPath contains the path parameters for updating a project rate.

type RateProjectUpdateResponse added in v1.5.0

type RateProjectUpdateResponse struct{}

RateProjectUpdateResponse represents the response for updating a project rate.

func RateProjectUpdate added in v1.5.0

func RateProjectUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req RateProjectUpdateRequest,
) (*RateProjectUpdateResponse, error)

RateProjectUpdate updates a project rate using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	var rate int64 = 7500
	req := projects.NewRateProjectUpdateRequest(67890, &rate) // Project ID, Rate (cents)
	_, err = projects.RateProjectUpdate(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to update project rate: %s", err)
	} else {
		fmt.Printf("updated project rate with identifier %d\n", 67890)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

updated project rate with identifier 67890

func (*RateProjectUpdateResponse) HandleHTTPResponse added in v1.5.0

func (r *RateProjectUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateProjectUpdateResponse.

type RateProjectUserGetRequest added in v1.5.0

type RateProjectUserGetRequest struct {
	// Path contains the path parameters for the request.
	Path RateProjectUserGetRequestPath

	// Filters contains the filters for the request.
	Filters RateProjectUserGetRequestFilters
}

RateProjectUserGetRequest represents the request for getting a project user rate.

func NewRateProjectUserGetRequest added in v1.5.0

func NewRateProjectUserGetRequest(projectID int64, userID int64) RateProjectUserGetRequest

NewRateProjectUserGetRequest creates a new RateProjectUserGetRequest.

func (RateProjectUserGetRequest) HTTPRequest added in v1.5.0

func (r RateProjectUserGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateProjectUserGetRequest.

type RateProjectUserGetRequestFilters added in v1.5.0

type RateProjectUserGetRequestFilters struct {
	// Include specifies which related data to include.
	Include []RateProjectUserGetRequestSideload
}

RateProjectUserGetRequestFilters contains the filters for getting a project user rate.

type RateProjectUserGetRequestPath added in v1.5.0

type RateProjectUserGetRequestPath struct {
	// ProjectID is the unique identifier of the project.
	ProjectID int64

	// UserID is the unique identifier of the user.
	UserID int64
}

RateProjectUserGetRequestPath contains the path parameters for getting a project user rate.

type RateProjectUserGetRequestSideload added in v1.5.0

type RateProjectUserGetRequestSideload string

RateProjectUserGetRequestSideload specifies which related resources to include in the response.

const (
	RateProjectUserGetRequestSideloadCurrencies RateProjectUserGetRequestSideload = "currencies"
)

type RateProjectUserGetResponse added in v1.5.0

type RateProjectUserGetResponse struct {
	// UserRate is the user's rate.
	UserRate *MultiCurrencyRate `json:"rate,omitempty"`

	// Rate is the rate as a monetary amount in the smallest currency unit
	// (e.g., cents).
	Rate int64 `json:"userRate"`

	// Included contains related data.
	Included struct {
		Currencies map[string]Currency `json:"currencies"`
	} `json:"included"`
}

RateProjectUserGetResponse represents the response for getting a project user rate.

func RateProjectUserGet added in v1.5.0

func RateProjectUserGet(
	ctx context.Context,
	engine *twapi.Engine,
	req RateProjectUserGetRequest,
) (*RateProjectUserGetResponse, error)

RateProjectUserGet retrieves a project user rate using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	req := projects.NewRateProjectUserGetRequest(67890, 12345) // Project ID, User ID
	req.Filters.Include = []projects.RateProjectUserGetRequestSideload{
		projects.RateProjectUserGetRequestSideloadCurrencies,
	}

	_, err = projects.RateProjectUserGet(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to get project user rate: %s", err)
	} else {
		fmt.Printf("retrieved project user rate with identifier %d\n", 12345)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved project user rate with identifier 12345

func (*RateProjectUserGetResponse) HandleHTTPResponse added in v1.5.0

func (r *RateProjectUserGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateProjectUserGetResponse.

type RateProjectUserHistoryGetRequest added in v1.5.0

type RateProjectUserHistoryGetRequest struct {
	// Path contains the path parameters for the request.
	Path RateProjectUserHistoryGetRequestPath

	// Filters contains the filters for the request.
	Filters RateProjectUserHistoryGetRequestFilters
}

RateProjectUserHistoryGetRequest represents the request for getting project user rate history.

func NewRateProjectUserHistoryGetRequest added in v1.5.0

func NewRateProjectUserHistoryGetRequest(projectID int64, userID int64) RateProjectUserHistoryGetRequest

NewRateProjectUserHistoryGetRequest creates a new RateProjectUserHistoryGetRequest.

func (RateProjectUserHistoryGetRequest) HTTPRequest added in v1.5.0

func (r RateProjectUserHistoryGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateProjectUserHistoryGetRequest.

type RateProjectUserHistoryGetRequestFilters added in v1.5.0

type RateProjectUserHistoryGetRequestFilters struct {
	// SearchTerm is an optional search term to filter by first name or last name.
	SearchTerm string

	// OrderBy specifies the ordering of results.
	OrderBy RateProjectUserHistoryGetRequestOrderBy

	// OrderMode specifies the order direction (asc, desc).
	OrderMode twapi.OrderMode

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of rates to retrieve per page. Defaults to 50.
	PageSize int64

	// Include specifies which related data to include.
	Include []RateProjectUserHistoryGetRequestSideload
}

RateProjectUserHistoryGetRequestFilters contains the filters for getting project user rate history.

type RateProjectUserHistoryGetRequestOrderBy added in v1.5.0

type RateProjectUserHistoryGetRequestOrderBy string

RateProjectUserHistoryGetRequestOrderBy specifies the ordering of results.

const (
	RateProjectUserHistoryGetRequestOrderByUsername RateProjectUserHistoryGetRequestOrderBy = "name"
)

Supported order by fields.

type RateProjectUserHistoryGetRequestPath added in v1.5.0

type RateProjectUserHistoryGetRequestPath struct {
	// ProjectID is the unique identifier of the project.
	ProjectID int64

	// UserID is the unique identifier of the user.
	UserID int64
}

RateProjectUserHistoryGetRequestPath contains the path parameters for getting project user rate history.

type RateProjectUserHistoryGetRequestSideload added in v1.5.0

type RateProjectUserHistoryGetRequestSideload string

RateProjectUserHistoryGetRequestSideload specifies which related resources to include in the response.

const (
	RateProjectUserHistoryGetRequestSideloadCurrencies RateProjectUserHistoryGetRequestSideload = "currencies"
	RateProjectUserHistoryGetRequestSideloadUsers      RateProjectUserHistoryGetRequestSideload = "users"
)

type RateProjectUserHistoryGetResponse added in v1.5.0

type RateProjectUserHistoryGetResponse struct {

	// Meta contains pagination information.
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`

	// UserRateHistory contains the list of historical rates.
	UserRateHistory []UserRateHistory `json:"userRateHistory"`

	// Included contains related data.
	Included struct {
		Currencies map[string]Currency           `json:"currencies"`
		Users      map[string]twapi.Relationship `json:"users"`
	} `json:"included"`
	// contains filtered or unexported fields
}

RateProjectUserHistoryGetResponse represents the response for getting project user rate history.

func RateProjectUserHistoryGet added in v1.5.0

func RateProjectUserHistoryGet(
	ctx context.Context,
	engine *twapi.Engine,
	req RateProjectUserHistoryGetRequest,
) (*RateProjectUserHistoryGetResponse, error)

RateProjectUserHistoryGet retrieves project user rate history using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	req := projects.NewRateProjectUserHistoryGetRequest(67890, 12345) // Project ID, User ID
	// Configure optional filters
	req.Filters.SearchTerm = "rate"

	resp, err := projects.RateProjectUserHistoryGet(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to get project user rate history: %s", err)
	} else {
		fmt.Printf("retrieved %d rate history entries for user %d\n", len(resp.UserRateHistory), 12345)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved 2 rate history entries for user 12345

func (*RateProjectUserHistoryGetResponse) HandleHTTPResponse added in v1.5.0

func (r *RateProjectUserHistoryGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateProjectUserHistoryGetResponse.

func (*RateProjectUserHistoryGetResponse) Iterate added in v1.5.0

Iterate returns the request set to the next page, if available.

func (*RateProjectUserHistoryGetResponse) SetRequest added in v1.5.0

SetRequest sets the request used to load this response.

type RateProjectUserListRequest added in v1.5.0

type RateProjectUserListRequest struct {
	// Path contains the path parameters for the request.
	Path RateProjectUserListRequestPath

	// Filters contains the filters for the request.
	Filters RateProjectUserListRequestFilters
}

RateProjectUserListRequest represents the request for listing project user rates.

func NewRateProjectUserListRequest added in v1.5.0

func NewRateProjectUserListRequest(projectID int64) RateProjectUserListRequest

NewRateProjectUserListRequest creates a new RateProjectUserListRequest.

func (RateProjectUserListRequest) HTTPRequest added in v1.5.0

func (r RateProjectUserListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateProjectUserListRequest.

type RateProjectUserListRequestFilters added in v1.5.0

type RateProjectUserListRequestFilters struct {
	// SearchTerm is an optional search term to filter by first name or last name.
	SearchTerm string

	// OrderBy specifies the ordering of results.
	OrderBy RateProjectUserListRequestOrderBy

	// OrderMode specifies the order direction (asc, desc).
	OrderMode twapi.OrderMode

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of rates to retrieve per page. Defaults to 50.
	PageSize int64
}

RateProjectUserListRequestFilters contains the filters for listing project user rates.

type RateProjectUserListRequestOrderBy added in v1.5.0

type RateProjectUserListRequestOrderBy string

RateProjectUserListRequestOrderBy specifies the ordering of results.

const (
	RateProjectUserListRequestOrderByUsername RateProjectUserListRequestOrderBy = "name"
)

Supported order by fields.

type RateProjectUserListRequestPath added in v1.5.0

type RateProjectUserListRequestPath struct {
	// ProjectID is the unique identifier of the project.
	ProjectID int64
}

RateProjectUserListRequestPath contains the path parameters for listing project user rates.

type RateProjectUserListResponse added in v1.5.0

type RateProjectUserListResponse struct {

	// Meta contains pagination information.
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`

	// UserRates contains the list of effective user project rates.
	UserRates []EffectiveUserProjectRate `json:"userRates"`

	// Included contains related data.
	Included struct {
		CostRates  map[string]any                `json:"costRates"`
		Currencies map[string]Currency           `json:"currencies"`
		Users      map[string]twapi.Relationship `json:"users"`
	} `json:"included"`
	// contains filtered or unexported fields
}

RateProjectUserListResponse represents the response for listing project user rates.

func RateProjectUserList added in v1.5.0

func RateProjectUserList(
	ctx context.Context,
	engine *twapi.Engine,
	req RateProjectUserListRequest,
) (*RateProjectUserListResponse, error)

RateProjectUserList retrieves project user rates using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	req := projects.NewRateProjectUserListRequest(67890) // Project ID
	// Configure pagination
	req.Filters.Page = 1
	req.Filters.PageSize = 10

	resp, err := projects.RateProjectUserList(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to list project user rates: %s", err)
	} else {
		fmt.Printf("retrieved %d project user rate(s)\n", len(resp.UserRates))
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved 2 project user rate(s)

func (*RateProjectUserListResponse) HandleHTTPResponse added in v1.5.0

func (r *RateProjectUserListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateProjectUserListResponse.

func (*RateProjectUserListResponse) Iterate added in v1.5.0

Iterate returns the request set to the next page, if available.

func (*RateProjectUserListResponse) SetRequest added in v1.5.0

SetRequest sets the request used to load this response.

type RateProjectUserUpdateRequest added in v1.5.0

type RateProjectUserUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path RateProjectUserUpdateRequestPath `json:"-"`

	// CurrencyID is the ID of the currency for the rate (optional, only used in multi-currency mode).
	CurrencyID *int64 `json:"currencyId,omitempty"`

	// UserRate is the new rate for the user as a monetary amount in the
	// smallest currency unit (e.g., cents). Use nil to clear/remove the rate.
	UserRate *int64 `json:"userRate"`
}

RateProjectUserUpdateRequest represents the request for updating a project user rate.

func NewRateProjectUserUpdateRequest added in v1.5.0

func NewRateProjectUserUpdateRequest(projectID int64, userID int64, rate *int64) RateProjectUserUpdateRequest

NewRateProjectUserUpdateRequest creates a new request. Rate should be provided in the smallest currency unit (e.g., cents). For example, €10.00 is 1000.

func (RateProjectUserUpdateRequest) HTTPRequest added in v1.5.0

func (r RateProjectUserUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateProjectUserUpdateRequest.

type RateProjectUserUpdateRequestPath added in v1.5.0

type RateProjectUserUpdateRequestPath struct {
	// ProjectID is the unique identifier of the project.
	ProjectID int64

	// UserID is the unique identifier of the user.
	UserID int64
}

RateProjectUserUpdateRequestPath contains the path parameters for updating a project user rate.

type RateProjectUserUpdateResponse added in v1.5.0

type RateProjectUserUpdateResponse struct {
	// UserRate is the user's updated rate as a monetary amount in the smallest
	// currency unit (e.g., cents).
	UserRate int64 `json:"userRate"`

	// Rate is the rate as a monetary amount in the smallest currency unit
	// (e.g., cents).
	Rate int64 `json:"rate"`

	// Included contains related data.
	Included struct {
		Currencies map[string]Currency `json:"currencies"`
	} `json:"included"`
}

RateProjectUserUpdateResponse represents the response for updating a project user rate.

func RateProjectUserUpdate added in v1.5.0

func RateProjectUserUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req RateProjectUserUpdateRequest,
) (*RateProjectUserUpdateResponse, error)

RateProjectUserUpdate updates a project user rate using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	var rate int64 = 8500
	req := projects.NewRateProjectUserUpdateRequest(67890, 12345, &rate) // Project ID, User ID, Rate (cents)
	_, err = projects.RateProjectUserUpdate(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to update user rate: %s", err)
	} else {
		fmt.Printf("updated project user rate with identifier %d\n", 12345)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

updated project user rate with identifier 12345

func (*RateProjectUserUpdateResponse) HandleHTTPResponse added in v1.5.0

func (r *RateProjectUserUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateProjectUserUpdateResponse.

type RateUserGetRequest added in v1.5.0

type RateUserGetRequest struct {
	// Path contains the path parameters for the request.
	Path RateUserGetRequestPath

	// Filters contains the filters for the request.
	Filters RateUserGetRequestFilters
}

RateUserGetRequest represents the request for getting a user's rates.

func NewRateUserGetRequest added in v1.5.0

func NewRateUserGetRequest(userID int64) RateUserGetRequest

NewRateUserGetRequest creates a new RateUserGetRequest with the provided user ID and default values.

func (RateUserGetRequest) HTTPRequest added in v1.5.0

func (r RateUserGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the RateUserGetRequest.

type RateUserGetRequestFilters added in v1.5.0

type RateUserGetRequestFilters struct {
	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of rates to retrieve per page. Defaults to 50.
	PageSize int64

	// IncludeInstallationRate includes the installation rate in the response.
	IncludeInstallationRate bool

	// IncludeUserCost includes the user cost in the response.
	IncludeUserCost bool

	// IncludeArchivedProjects includes archived projects in the response.
	IncludeArchivedProjects bool

	// IncludeDeletedProjects includes deleted projects in the response.
	IncludeDeletedProjects bool

	// Include specifies which related data to include. Supports: "projects".
	Include []RateUserGetRequestSideload
}

RateUserGetRequestFilters contains the filters for getting a user's rates.

type RateUserGetRequestPath added in v1.5.0

type RateUserGetRequestPath struct {
	// ID is the unique identifier of the user whose rates are to be retrieved.
	ID int64
}

RateUserGetRequestPath contains the path parameters for getting a user's rates.

type RateUserGetRequestSideload added in v1.5.0

type RateUserGetRequestSideload string

RateUserGetRequestSideload specifies which related resources to include in the response.

const (
	RateSideloadProjects RateUserGetRequestSideload = "projects"
)

type RateUserGetResponse added in v1.5.0

type RateUserGetResponse struct {
	// ProjectRates contains project-specific rates.
	ProjectRates []UserProjectRate `json:"projectRates"`

	// InstallationRate is the user's installation rate (optional) as a
	// monetary amount in the smallest currency unit (e.g., cents).
	InstallationRate *int64 `json:"installationRate,omitempty"`

	// InstallationRates contains rates in different currencies (optional)
	// as monetary amounts in the smallest currency unit (e.g., cents).
	InstallationRates map[int64]MultiCurrencyRate `json:"installationRates,omitempty"`

	// UserCost is the user's cost (optional) as a monetary amount in the
	// smallest currency unit (e.g., cents).
	UserCost *int64 `json:"userCost,omitempty"`

	// Meta contains pagination information.
	Meta struct {
		Page struct {
			PageOffset int64 `json:"pageOffset"`
			PageSize   int64 `json:"pageSize"`
			Count      int64 `json:"count"`
			HasMore    bool  `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`

	// Included contains related data.
	Included struct {
		Currencies map[string]Currency           `json:"currencies,omitempty"`
		Projects   map[string]twapi.Relationship `json:"projects,omitempty"`
	} `json:"included"`
}

RateUserGetResponse represents the response for getting a user's rates.

func RateUserGet added in v1.5.0

func RateUserGet(
	ctx context.Context,
	engine *twapi.Engine,
	req RateUserGetRequest,
) (*RateUserGetResponse, error)

RateUserGet retrieves a user's rates using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startRatesServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	req := projects.NewRateUserGetRequest(12345) // User ID
	// Configure optional filters
	req.Filters.IncludeUserCost = true
	// Include supported related resources via enum
	req.Filters.Include = []projects.RateUserGetRequestSideload{
		projects.RateSideloadProjects,
	}

	_, err = projects.RateUserGet(ctx, engine, req)
	if err != nil {
		fmt.Printf("failed to get user rates: %s", err)
	} else {
		fmt.Printf("retrieved user rates with identifier %d\n", 12345)
	}

}

func startRatesServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()

	mux.HandleFunc("GET /projects/api/v3/people/{id}/rates", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"installationRate":5000,"projectRates":[{"id":123,"rate":7500}],`+
			`"installationRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}},"userCost":4000}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"rate":5000}],`+
			`"meta":{"page":{"count":1,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRate":5000,"userRates":{"1":{"rate":5000,"currency":{"id":1,"code":"USD"}}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/installation/users/bulk/update",
		func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"all":false,"ids":[12345],"excludeIds":[],"rate":6000}`)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"projectRate":7500,"rate":{"amount":75.00,"currency":{"id":1,"code":"USD"}}}`)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{id}/actions/update",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("id") != "67890" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusNoContent)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{id}/users", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "67890" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"userRates":[{"user":{"id":12345},"effectiveRate":8500},`+
			`{"user":{"id":67891},"effectiveRate":9000}],"meta":{"page":{"count":2,"hasMore":false}}}`)
	})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"rate":{"amount":85.00,"currency":{"id":1,"code":"USD"}},"userRate":8500}`)
		})

	mux.HandleFunc("PUT /projects/api/v3/rates/projects/{projectId}/users/{userId}",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusCreated)
		})

	mux.HandleFunc("GET /projects/api/v3/rates/projects/{projectId}/users/{userId}/history",
		func(w http.ResponseWriter, r *http.Request) {
			if r.PathValue("projectId") != "67890" || r.PathValue("userId") != "12345" {
				http.Error(w, "Not Found", http.StatusNotFound)
				return
			}
			w.WriteHeader(http.StatusOK)
			w.Header().Set("Content-Type", "application/json")
			_, _ = fmt.Fprintln(w, `{"userRateHistory":[{"rate":8500,"fromDate":"2023-01-01T00:00:00Z"},`+
				`{"rate":9000,"fromDate":"2023-06-01T00:00:00Z"}],"meta":{"page":{"hasMore":false}}}`)
		})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved user rates with identifier 12345

func (*RateUserGetResponse) HandleHTTPResponse added in v1.5.0

func (r *RateUserGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the RateUserGetResponse.

type Tag added in v0.4.0

type Tag struct {
	// ID is the unique identifier of the tag.
	ID int64 `json:"id"`

	// Name is the name of the tag.
	Name string `json:"name"`

	// Project is the project the tag belongs to.
	Project *twapi.Relationship `json:"project"`
}

Tag is a customizable label that can be applied to various items such as tasks, projects, milestones, messages, and more, to help categorize and organize work efficiently. Tags provide a flexible way to filter, search, and group related items across the platform, making it easier for teams to manage complex workflows, highlight priorities, or track themes and statuses. Since tags are user-defined, they adapt to each team’s specific needs and can be color-coded for better visual clarity.

More information can be found at: https://support.teamwork.com/projects/glossary/tags-overview

type TagCreateRequest added in v0.4.0

type TagCreateRequest struct {
	// Name is the name of the tag. This field is required. It must be less than
	// 50 characters.
	Name string `json:"name"`

	// ProjectID is the unique identifier of the project the tag belongs to. This
	// is for project-scoped tags.
	ProjectID *int64 `json:"projectId,omitempty"`
}

TagCreateRequest represents the request body for creating a new tag.

https://apidocs.teamwork.com/docs/teamwork/v3/tags/post-projects-api-v3-tags-json

func NewTagCreateRequest added in v0.4.0

func NewTagCreateRequest(name string) TagCreateRequest

NewTagCreateRequest creates a new TagCreateRequest with the provided name.

func (TagCreateRequest) HTTPRequest added in v0.4.0

func (t TagCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TagCreateRequest.

type TagCreateResponse added in v0.4.0

type TagCreateResponse struct {
	// Tag is the created tag.
	Tag Tag `json:"tag"`
}

TagCreateResponse represents the response body for creating a new tag.

https://apidocs.teamwork.com/docs/teamwork/v3/tags/post-projects-api-v3-tags-json

func TagCreate added in v0.4.0

func TagCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req TagCreateRequest,
) (*TagCreateResponse, error)

TagCreate creates a new tag using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTagServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tagResponse, err := projects.TagCreate(ctx, engine, projects.NewTagCreateRequest("Test Tag"))
	if err != nil {
		fmt.Printf("failed to create tag: %s", err)
	} else {
		fmt.Printf("created tag with identifier %d\n", tagResponse.Tag.ID)
	}

}

func startTagServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tags", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tags", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tags":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created tag with identifier 12345

func (*TagCreateResponse) HandleHTTPResponse added in v0.4.0

func (t *TagCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TagCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TagDeleteRequest added in v0.4.0

type TagDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path TagDeleteRequestPath
}

TagDeleteRequest represents the request body for deleting a tag.

https://apidocs.teamwork.com/docs/teamwork/v3/tags/delete-projects-api-v3-tags-tag-id-json

func NewTagDeleteRequest added in v0.4.0

func NewTagDeleteRequest(tagID int64) TagDeleteRequest

NewTagDeleteRequest creates a new TagDeleteRequest with the provided tag ID.

func (TagDeleteRequest) HTTPRequest added in v0.4.0

func (t TagDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TagDeleteRequest.

type TagDeleteRequestPath added in v0.4.0

type TagDeleteRequestPath struct {
	// ID is the unique identifier of the tag to be deleted.
	ID int64
}

TagDeleteRequestPath contains the path parameters for deleting a tag.

type TagDeleteResponse added in v0.4.0

type TagDeleteResponse struct{}

TagDeleteResponse represents the response body for deleting a tag.

https://apidocs.teamwork.com/docs/teamwork/v1/tags/delete-tags-id-json

func TagDelete added in v0.4.0

func TagDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req TagDeleteRequest,
) (*TagDeleteResponse, error)

TagDelete deletes a tag using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTagServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.TagDelete(ctx, engine, projects.NewTagDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete tag: %s", err)
	} else {
		fmt.Println("tag deleted!")
	}

}

func startTagServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tags", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tags", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tags":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

tag deleted!

func (*TagDeleteResponse) HandleHTTPResponse added in v0.4.0

func (t *TagDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TagDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TagGetRequest added in v0.4.0

type TagGetRequest struct {
	// Path contains the path parameters for the request.
	Path TagGetRequestPath
}

TagGetRequest represents the request body for loading a single tag.

https://apidocs.teamwork.com/docs/teamwork/v3/tags/get-projects-api-v3-tags-tag-id-json

func NewTagGetRequest added in v0.4.0

func NewTagGetRequest(tagID int64) TagGetRequest

NewTagGetRequest creates a new TagGetRequest with the provided tag ID. The ID is required to load a tag.

func (TagGetRequest) HTTPRequest added in v0.4.0

func (t TagGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TagGetRequest.

type TagGetRequestPath added in v0.4.0

type TagGetRequestPath struct {
	// ID is the unique identifier of the tag to be retrieved.
	ID int64 `json:"id"`
}

TagGetRequestPath contains the path parameters for loading a single tag.

type TagGetResponse added in v0.4.0

type TagGetResponse struct {
	Tag Tag `json:"tag"`
}

TagGetResponse contains all the information related to a tag.

https://apidocs.teamwork.com/docs/teamwork/v3/tags/get-projects-api-v3-tags-tag-id-json

func TagGet added in v0.4.0

func TagGet(
	ctx context.Context,
	engine *twapi.Engine,
	req TagGetRequest,
) (*TagGetResponse, error)

TagGet retrieves a single tag using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTagServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tagResponse, err := projects.TagGet(ctx, engine, projects.NewTagGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve tag: %s", err)
	} else {
		fmt.Printf("retrieved tag with identifier %d\n", tagResponse.Tag.ID)
	}

}

func startTagServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tags", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tags", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tags":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved tag with identifier 12345

func (*TagGetResponse) HandleHTTPResponse added in v0.4.0

func (t *TagGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TagGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TagListRequest added in v0.4.0

type TagListRequest struct {
	// Filters contains the filters for loading multiple tags.
	Filters TagListRequestFilters
}

TagListRequest represents the request body for loading multiple tags.

https://apidocs.teamwork.com/docs/teamwork/v3/tags/get-projects-api-v3-tags-json

func NewTagListRequest added in v0.4.0

func NewTagListRequest() TagListRequest

NewTagListRequest creates a new TagListRequest with default values.

func (TagListRequest) HTTPRequest added in v0.4.0

func (t TagListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TagListRequest.

type TagListRequestFilters added in v0.4.0

type TagListRequestFilters struct {
	// SearchTerm is an optional search term to filter tags by name.
	SearchTerm string

	// ItemType is the type of item the tag is associated with. Valid values are
	// 'project', 'task', 'tasklist', 'milestone', 'message', 'timelog',
	// 'notebook', 'file', 'company' and 'link'.
	ItemType string

	// ProjectIDs is an optional list of project IDs to filter tags by
	// belonging to specific projects.
	ProjectIDs []int64

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of tags to retrieve per page. Defaults to 50.
	PageSize int64
}

TagListRequestFilters contains the filters for loading multiple tags.

type TagListResponse added in v0.4.0

type TagListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Tags []Tag `json:"tags"`
	// contains filtered or unexported fields
}

TagListResponse contains information by multiple tags matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/tags/get-projects-api-v3-tags-json

func TagList added in v0.4.0

func TagList(
	ctx context.Context,
	engine *twapi.Engine,
	req TagListRequest,
) (*TagListResponse, error)

TagList retrieves multiple tags using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTagServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tagsRequest := projects.NewTagListRequest()
	tagsRequest.Filters.SearchTerm = "Q&A"

	tagsResponse, err := projects.TagList(ctx, engine, tagsRequest)
	if err != nil {
		fmt.Printf("failed to list tags: %s", err)
	} else {
		for _, tag := range tagsResponse.Tags {
			fmt.Printf("retrieved tag with identifier %d\n", tag.ID)
		}
	}

}

func startTagServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tags", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tags", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tags":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved tag with identifier 12345
retrieved tag with identifier 12346

func (*TagListResponse) HandleHTTPResponse added in v0.4.0

func (t *TagListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TagListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*TagListResponse) Iterate added in v0.4.0

func (t *TagListResponse) Iterate() *TagListRequest

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*TagListResponse) SetRequest added in v0.4.0

func (t *TagListResponse) SetRequest(req TagListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type TagUpdateRequest added in v0.4.0

type TagUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path TagUpdateRequestPath `json:"-"`

	// Name is the name of the tag. It must be less than 50 characters when
	// provided.
	Name *string `json:"name,omitempty"`

	// ProjectID is the unique identifier of the project the tag belongs to. This
	// is for project-scoped tags.
	ProjectID *int64 `json:"projectId,omitempty"`
}

TagUpdateRequest represents the request body for updating a tag. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v3/tags/patch-projects-api-v3-tags-tag-id-json

func NewTagUpdateRequest added in v0.4.0

func NewTagUpdateRequest(tagID int64) TagUpdateRequest

NewTagUpdateRequest creates a new TagUpdateRequest with the provided tag ID. The ID is required to update a tag.

func (TagUpdateRequest) HTTPRequest added in v0.4.0

func (t TagUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TagUpdateRequest.

type TagUpdateRequestPath added in v0.4.0

type TagUpdateRequestPath struct {
	// ID is the unique identifier of the tag to be updated.
	ID int64
}

TagUpdateRequestPath contains the path parameters for updating a tag.

type TagUpdateResponse added in v0.4.0

type TagUpdateResponse struct {
	// Tag is the updated tag.
	Tag Tag `json:"tag"`
}

TagUpdateResponse represents the response body for updating a tag.

https://apidocs.teamwork.com/docs/teamwork/v1/tags/put-tags-id-json

func TagUpdate added in v0.4.0

func TagUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req TagUpdateRequest,
) (*TagUpdateResponse, error)

TagUpdate updates a tag using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTagServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tagRequest := projects.NewTagUpdateRequest(12345)
	tagRequest.Name = twapi.Ptr("Updated tag")

	_, err = projects.TagUpdate(ctx, engine, tagRequest)
	if err != nil {
		fmt.Printf("failed to update tag: %s", err)
	} else {
		fmt.Println("tag updated!")
	}

}

func startTagServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tags", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/tags/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tag":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tags", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tags":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

tag updated!

func (*TagUpdateResponse) HandleHTTPResponse added in v0.4.0

func (t *TagUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TagUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Task added in v0.2.0

type Task struct {
	// ID is the unique identifier of the task.
	ID int64 `json:"id"`

	// Name is the name of the task.
	Name string `json:"name"`

	// Description is the description of the task.
	Description *string `json:"description"`

	// DescriptionContentType is the content type of the description. It can be
	// "TEXT" or "HTML".
	DescriptionContentType *string `json:"descriptionContentType"`

	// Priority is the priority of the task. It can be "none", "low", "medium" or
	// "high".
	Priority *string `json:"priority"`

	// Progress is the progress of the task, in percentage (0-100).
	Progress int64 `json:"progress"`

	// StartAt is the date and time when the task is scheduled to start.
	StartAt *time.Time `json:"startDate"`

	// DueAt is the date and time when the task is scheduled to be completed.
	DueAt *time.Time `json:"dueDate"`

	// EstimatedMinutes is the estimated time to complete the task, in minutes.
	EstimatedMinutes int64 `json:"estimateMinutes"`

	// Tasklist is the relationship to the tasklist containing this task.
	Tasklist twapi.Relationship `json:"tasklist"`

	// ParentTask is the relationship to the parent task, if this task is a
	// subtask.
	ParentTask *twapi.Relationship `json:"parentTask"`

	// Assignees is the list of users, teams or clients/companies assigned to this
	// task.
	Assignees []twapi.Relationship `json:"assignees"`

	// Tags is the list of tags associated with this task.
	Tags []twapi.Relationship `json:"tags"`

	// Predecessors is the list of tasks that must be completed before this task
	// can be started or completed.
	Predecessors []twapi.Relationship `json:"predecessors"`

	// CreatedBy is the ID of the user who created the task.
	CreatedBy *int64 `json:"createdBy"`

	// CreatedAt is the date and time when the task was created.
	CreatedAt *time.Time `json:"createdAt"`

	// UpdatedBy is the ID of the user who last updated the task.
	UpdatedBy *int64 `json:"updatedBy"`

	// UpdatedAt is the date and time when the task was last updated.
	UpdatedAt time.Time `json:"updatedAt"`

	// DeletedBy is the ID of the user who deleted the task, if it was deleted.
	DeletedBy *int64 `json:"deletedBy"`

	// DeletedAt is the date and time when the task was deleted, if it was
	// deleted.
	DeletedAt *time.Time `json:"deletedAt"`

	// CompletedBy is the ID of the user who completed the task, if it was
	// completed.
	CompletedBy *int64 `json:"completedBy,omitempty"`

	// CompletedAt is the date and time when the task was completed, if it was
	// completed.
	CompletedAt *time.Time `json:"completedAt,omitempty"`

	// Status is the status of the task. It can be "new", "reopened", "completed"
	// or "deleted".
	Status string `json:"status"`
}

Task represents an individual unit of work assigned to one or more team members within a project. Each task can include details such as a title, description, priority, estimated time, assignees, and due date, along with the ability to attach files, leave comments, track time, and set dependencies on other tasks. Tasks are organized within task lists, helping structure and sequence work logically. They serve as the building blocks of project management in Teamwork, allowing teams to collaborate, monitor progress, and ensure accountability throughout the project's lifecycle.

More information can be found at: https://support.teamwork.com/projects/getting-started/tasks-overview

type TaskCreateRequest added in v0.2.0

type TaskCreateRequest struct {
	// Path contains the path parameters for the request.
	Path TaskCreateRequestPath `json:"-"`

	// Name is the name of the task
	Name string `json:"name"`

	// Description is an optional description of the task.
	Description *string `json:"description,omitempty"`

	// Priority is the priority of the task. It can be "none", "low", "medium" or
	// "high".
	Priority *string `json:"priority,omitempty"`

	// Progress is the progress of the task, in percentage (0-100).
	Progress *int64 `json:"progress,omitempty"`

	// StartAt is the date and time when the task is scheduled to start.
	StartAt *twapi.Date `json:"startAt,omitempty"`

	// DueAt is the date and time when the task is scheduled to be completed.
	DueAt *twapi.Date `json:"dueAt,omitempty"`

	// EstimatedMinutes is the estimated time to complete the task, in minutes.
	EstimatedMinutes *int64 `json:"estimatedMinutes,omitempty"`

	// ParentTaskID is the identifier of the parent task, if this task is a
	// subtask.
	ParentTaskID *int64 `json:"parentTaskId,omitempty"`

	// Assignees is the list of users, teams or clients/companies assigned to this
	// task.
	Assignees *UserGroups `json:"assignees,omitempty"`

	// TagIDs is the list of tag IDs associated with this task.
	TagIDs []int64 `json:"tagIds,omitempty"`

	// Predecessors is the list of task predecessors associated with this task.
	Predecessors []TaskPredecessor `json:"-"`
}

TaskCreateRequest represents the request body for creating a new task.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/post-projects-api-v3-tasklists-tasklist-id-tasks-json

func NewTaskCreateRequest added in v0.2.0

func NewTaskCreateRequest(tasklistID int64, name string) TaskCreateRequest

NewTaskCreateRequest creates a new TaskCreateRequest with the provided name in a specific tasklist.

func (TaskCreateRequest) HTTPRequest added in v0.2.0

func (t TaskCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TaskCreateRequest.

type TaskCreateRequestPath added in v0.2.0

type TaskCreateRequestPath struct {
	// TasklistID is the unique identifier of the tasklist that will contain the
	// task.
	TasklistID int64
}

TaskUpdateRequestPath contains the path parameters for creating a task.

type TaskCreateResponse added in v0.2.0

type TaskCreateResponse struct {
	// Task is the created task.
	Task Task `json:"task"`
}

TaskCreateResponse represents the response body for creating a new task.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/post-projects-api-v3-tasklists-tasklist-id-tasks-json

func TaskCreate added in v0.2.0

func TaskCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req TaskCreateRequest,
) (*TaskCreateResponse, error)

TaskCreate creates a new task using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTaskServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	taskRequest := projects.NewTaskCreateRequest(777, "New Task")
	taskRequest.Description = twapi.Ptr("This is a new task created via the API.")

	taskResponse, err := projects.TaskCreate(ctx, engine, taskRequest)
	if err != nil {
		fmt.Printf("failed to create task: %s", err)
	} else {
		fmt.Printf("created task with identifier %d\n", taskResponse.Task.ID)
	}

}

func startTaskServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasklists/{id}/tasks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"affected":{"taskIds":[12345]}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created task with identifier 12345

func (*TaskCreateResponse) HandleHTTPResponse added in v0.2.0

func (t *TaskCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TaskCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TaskDeleteRequest added in v0.2.0

type TaskDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path TaskDeleteRequestPath
}

TaskDeleteRequest represents the request body for deleting a task.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/delete-projects-api-v3-tasks-task-id-json

func NewTaskDeleteRequest added in v0.2.0

func NewTaskDeleteRequest(taskID int64) TaskDeleteRequest

NewTaskDeleteRequest creates a new TaskDeleteRequest with the provided task ID.

func (TaskDeleteRequest) HTTPRequest added in v0.2.0

func (t TaskDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TaskDeleteRequest.

type TaskDeleteRequestPath added in v0.2.0

type TaskDeleteRequestPath struct {
	// ID is the unique identifier of the task to be deleted.
	ID int64
}

TaskDeleteRequestPath contains the path parameters for deleting a task.

type TaskDeleteResponse added in v0.2.0

type TaskDeleteResponse struct{}

TaskDeleteResponse represents the response body for deleting a task.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/delete-projects-api-v3-tasks-task-id-json

func TaskDelete added in v0.2.0

func TaskDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req TaskDeleteRequest,
) (*TaskDeleteResponse, error)

TaskDelete deletes a task using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTaskServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.TaskDelete(ctx, engine, projects.NewTaskDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete task: %s", err)
	} else {
		fmt.Println("task deleted!")
	}

}

func startTaskServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasklists/{id}/tasks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"affected":{"taskIds":[12345]}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

task deleted!

func (*TaskDeleteResponse) HandleHTTPResponse added in v0.2.0

func (t *TaskDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TaskDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TaskGetRequest added in v0.2.0

type TaskGetRequest struct {
	// Path contains the path parameters for the request.
	Path TaskGetRequestPath
}

TaskGetRequest represents the request body for loading a single task.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/get-projects-api-v3-tasks-task-id-json

func NewTaskGetRequest added in v0.2.0

func NewTaskGetRequest(taskID int64) TaskGetRequest

NewTaskGetRequest creates a new TaskGetRequest with the provided task ID. The ID is required to load a task.

func (TaskGetRequest) HTTPRequest added in v0.2.0

func (t TaskGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TaskGetRequest.

type TaskGetRequestPath added in v0.2.0

type TaskGetRequestPath struct {
	// ID is the unique identifier of the task to be retrieved.
	ID int64 `json:"id"`
}

TaskGetRequestPath contains the path parameters for loading a single task.

type TaskGetResponse added in v0.2.0

type TaskGetResponse struct {
	Task Task `json:"task"`
}

TaskGetResponse contains all the information related to a task.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/get-projects-api-v3-tasks-task-id-json

func TaskGet added in v0.2.0

func TaskGet(
	ctx context.Context,
	engine *twapi.Engine,
	req TaskGetRequest,
) (*TaskGetResponse, error)

TaskGet retrieves a single task using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTaskServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	taskResponse, err := projects.TaskGet(ctx, engine, projects.NewTaskGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve task: %s", err)
	} else {
		fmt.Printf("retrieved task with identifier %d\n", taskResponse.Task.ID)
	}

}

func startTaskServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasklists/{id}/tasks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"affected":{"taskIds":[12345]}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved task with identifier 12345

func (*TaskGetResponse) HandleHTTPResponse added in v0.2.0

func (t *TaskGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TaskGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TaskListRequest added in v0.2.0

type TaskListRequest struct {
	// Path contains the path parameters for the request.
	Path TaskListRequestPath

	// Filters contains the filters for loading multiple tasks.
	Filters TaskListRequestFilters
}

TaskListRequest represents the request body for loading multiple tasks.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/get-projects-api-v3-tasks-json https://apidocs.teamwork.com/docs/teamwork/v3/tasks/get-projects-api-v3-projects-project-id-tasks-json https://apidocs.teamwork.com/docs/teamwork/v3/tasks/get-projects-api-v3-tasklists-tasklist-id-tasks-json

func NewTaskListRequest added in v0.2.0

func NewTaskListRequest() TaskListRequest

NewTaskListRequest creates a new TaskListRequest with default values.

func (TaskListRequest) HTTPRequest added in v0.2.0

func (t TaskListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TaskListRequest.

type TaskListRequestFilters added in v0.2.0

type TaskListRequestFilters struct {
	// SearchTerm is an optional search term to filter tasks by name, description
	// or tasklist's name.
	SearchTerm string

	// TagIDs is an optional list of tag IDs to filter tasks by tags.
	TagIDs []int64

	// AssigneeUserIDs is an optional list of User IDs to filter tasks by assigned user.
	AssigneeUserIDs []int64

	// MatchAllTags is an optional flag to indicate if all tags must match. If set
	// to true, only tasks matching all specified tags will be returned.
	MatchAllTags *bool

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of tasks to retrieve per page. Defaults to 50.
	PageSize int64
}

TaskListRequestFilters contains the filters for loading multiple tasks.

type TaskListRequestPath added in v0.2.0

type TaskListRequestPath struct {
	// ProjectID is the unique identifier of the project whose tasks are to be
	// retrieved.
	ProjectID int64
	// TasklistID is the unique identifier of the tasklist whose tasks are to be
	// retrieved. If provided, the ProjectID is ignored.
	TasklistID int64
}

TaskListRequestPath contains the path parameters for loading multiple tasks.

type TaskListResponse added in v0.2.0

type TaskListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Tasks []Task `json:"tasks"`
	// contains filtered or unexported fields
}

TaskListResponse contains information by multiple tasks matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/get-projects-api-v3-tasks-json https://apidocs.teamwork.com/docs/teamwork/v3/tasks/get-projects-api-v3-projects-project-id-tasks-json https://apidocs.teamwork.com/docs/teamwork/v3/tasks/get-projects-api-v3-tasklists-tasklist-id-tasks-json

func TaskList added in v0.2.0

func TaskList(
	ctx context.Context,
	engine *twapi.Engine,
	req TaskListRequest,
) (*TaskListResponse, error)

TaskList retrieves multiple tasks using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTaskServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tasksRequest := projects.NewTaskListRequest()
	tasksRequest.Filters.SearchTerm = "Example"

	tasksResponse, err := projects.TaskList(ctx, engine, tasksRequest)
	if err != nil {
		fmt.Printf("failed to list tasks: %s", err)
	} else {
		for _, task := range tasksResponse.Tasks {
			fmt.Printf("retrieved task with identifier %d\n", task.ID)
		}
	}

}

func startTaskServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasklists/{id}/tasks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"affected":{"taskIds":[12345]}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved task with identifier 12345
retrieved task with identifier 12346

func (*TaskListResponse) HandleHTTPResponse added in v0.2.0

func (t *TaskListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TaskListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*TaskListResponse) Iterate added in v0.2.0

func (t *TaskListResponse) Iterate() *TaskListRequest

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*TaskListResponse) SetRequest added in v0.2.0

func (t *TaskListResponse) SetRequest(req TaskListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type TaskPredecessor added in v1.3.0

type TaskPredecessor struct {
	// ID is the unique identifier of the predecessor task.
	ID int64 `json:"id"`

	// Type is the type of predecessor constraint.
	Type TaskPredecessorType `json:"type"`
}

TaskPredecessor represents a task predecessor with its type. This is used when creating or updating a task to set a dependency on another task. That means the task cannot be started or completed until the predecessor task is completed, depending on the type.

type TaskPredecessorType added in v1.3.0

type TaskPredecessorType string

TaskPredecessorType defines the predecessor constraint type

const (
	// TaskPredecessorTypeStart must start before task.
	TaskPredecessorTypeStart TaskPredecessorType = "start"
	// TaskPredecessorTypeFinish must finish before task.
	TaskPredecessorTypeFinish TaskPredecessorType = "complete"
)

type TaskUpdateRequest added in v0.2.0

type TaskUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path TaskUpdateRequestPath `json:"-"`

	// Name is the name of the task
	Name *string `json:"name,omitempty"`

	// Description is an optional description of the task.
	Description *string `json:"description,omitempty"`

	// Priority is the priority of the task. It can be "none", "low", "medium" or
	// "high".
	Priority *string `json:"priority,omitempty"`

	// Progress is the progress of the task, in percentage (0-100).
	Progress *int64 `json:"progress,omitempty"`

	// StartAt is the date and time when the task is scheduled to start.
	StartAt *twapi.Date `json:"startAt,omitempty"`

	// DueAt is the date and time when the task is scheduled to be completed.
	DueAt *twapi.Date `json:"dueAt,omitempty"`

	// EstimatedMinutes is the estimated time to complete the task, in minutes.
	EstimatedMinutes *int64 `json:"estimatedMinutes,omitempty"`

	// TasklistID is the identifier of the tasklist that will contain the task. If
	// provided, the task will be moved to this tasklist.
	TasklistID *int64 `json:"tasklistId,omitempty"`

	// ParentTaskID is the identifier of the parent task, if this task is a
	// subtask. If provided, the task will be moved under this parent task.
	ParentTaskID *int64 `json:"parentTaskId,omitempty"`

	// Assignees is the list of users, teams or clients/companies assigned to this
	// task.
	Assignees *UserGroups `json:"assignees,omitempty"`

	// TagIDs is the list of tag IDs associated with this task.
	TagIDs []int64 `json:"tagIds,omitempty"`

	// Predecessors is the list of task predecessors associated with this task.
	Predecessors []TaskPredecessor `json:"-"`
}

TaskUpdateRequest represents the request body for updating a task. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/patch-projects-api-v3-tasks-task-id-json

func NewTaskUpdateRequest added in v0.2.0

func NewTaskUpdateRequest(taskID int64) TaskUpdateRequest

NewTaskUpdateRequest creates a new TaskUpdateRequest with the provided task ID. The ID is required to update a task.

func (TaskUpdateRequest) HTTPRequest added in v0.2.0

func (t TaskUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TaskUpdateRequest.

type TaskUpdateRequestPath added in v0.2.0

type TaskUpdateRequestPath struct {
	// ID is the unique identifier of the task to be updated.
	ID int64
}

TaskUpdateRequestPath contains the path parameters for updating a task.

type TaskUpdateResponse added in v0.2.0

type TaskUpdateResponse struct {
	// Task is the updated task.
	Task Task `json:"task"`
}

TaskUpdateResponse represents the response body for updating a task.

https://apidocs.teamwork.com/docs/teamwork/v3/tasks/patch-projects-api-v3-tasks-task-id-json

func TaskUpdate added in v0.2.0

func TaskUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req TaskUpdateRequest,
) (*TaskUpdateResponse, error)

TaskUpdate updates a task using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTaskServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	taskRequest := projects.NewTaskUpdateRequest(12345)
	taskRequest.Description = twapi.Ptr("This is an updated description.")

	_, err = projects.TaskUpdate(ctx, engine, taskRequest)
	if err != nil {
		fmt.Printf("failed to update task: %s", err)
	} else {
		fmt.Println("task updated!")
	}

}

func startTaskServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasklists/{id}/tasks", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"affected":{"taskIds":[12345]}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"task":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasks", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasks":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

task updated!

func (*TaskUpdateResponse) HandleHTTPResponse added in v0.2.0

func (t *TaskUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TaskUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Tasklist added in v0.1.0

type Tasklist struct {
	// ID is the unique identifier of the tasklist.
	ID int64 `json:"id"`

	// Name is the name of the tasklist.
	Name string `json:"name"`

	// Description is the description of the tasklist.
	Description string `json:"description"`

	// Project is the project associated with the tasklist.
	Project twapi.Relationship `json:"project"`

	// Milestone is the milestone associated with the tasklist.
	Milestone *twapi.Relationship `json:"milestone"`

	// CreatedAt is the date and time when the tasklist was created.
	CreatedAt *time.Time `json:"createdAt"`

	// UpdatedAt is the date and time when the tasklist was last updated.
	UpdatedAt *time.Time `json:"updatedAt"`

	// Status is the status of the tasklist. It can be "new", "reopened",
	// "completed" or "deleted".
	Status string `json:"status"`
}

Tasklist is a way to group related tasks within a project, helping teams organize their work into meaningful sections such as phases, categories, or deliverables. Each task list belongs to a specific project and can include multiple tasks that are typically aligned with a common goal. Task lists can be associated with milestones, and they support privacy settings that control who can view or interact with the tasks they contain. This structure helps teams manage progress, assign responsibilities, and maintain clarity across complex projects.

More information can be found at: https://support.teamwork.com/projects/getting-started/task-lists-overview

type TasklistCreateRequest added in v0.1.0

type TasklistCreateRequest struct {
	// Path contains the path parameters for the request.
	Path TasklistCreateRequestPath `json:"-"`

	// Name is the name of the tasklist
	Name string `json:"name"`

	// Description is an optional description of the tasklist.
	Description *string `json:"description,omitempty"`

	// MilestoneID is an optional ID of the milestone associated with the
	// tasklist.
	MilestoneID *int64 `json:"milestone-Id,omitempty"`
}

TasklistCreateRequest represents the request body for creating a new tasklist.

https://apidocs.teamwork.com/docs/teamwork/v1/task-lists/post-projects-id-tasklists-json

func NewTasklistCreateRequest added in v0.1.0

func NewTasklistCreateRequest(projectID int64, name string) TasklistCreateRequest

NewTasklistCreateRequest creates a new TasklistCreateRequest with the provided name in a specific project.

func (TasklistCreateRequest) HTTPRequest added in v0.1.0

func (t TasklistCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TasklistCreateRequest.

type TasklistCreateRequestPath added in v0.1.0

type TasklistCreateRequestPath struct {
	// ProjectID is the unique identifier of the project that will contain the
	// tasklist.
	ProjectID int64
}

TasklistUpdateRequestPath contains the path parameters for creating a tasklist.

type TasklistCreateResponse added in v0.1.0

type TasklistCreateResponse struct {
	// ID is the unique identifier of the created tasklist.
	ID LegacyNumber `json:"tasklistId"`
}

TasklistCreateResponse represents the response body for creating a new tasklist.

https://apidocs.teamwork.com/docs/teamwork/v1/task-lists/post-projects-id-tasklists-json

func TasklistCreate added in v0.1.0

func TasklistCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req TasklistCreateRequest,
) (*TasklistCreateResponse, error)

TasklistCreate creates a new tasklist using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTasklistServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tasklistRequest := projects.NewTasklistCreateRequest(777, "New Tasklist")
	tasklistRequest.Description = twapi.Ptr("This is a new tasklist created via the API.")

	tasklistResponse, err := projects.TasklistCreate(ctx, engine, tasklistRequest)
	if err != nil {
		fmt.Printf("failed to create tasklist: %s", err)
	} else {
		fmt.Printf("created tasklist with identifier %d\n", tasklistResponse.ID)
	}

}

func startTasklistServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/tasklists", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","tasklistId":"12345"}`)
	})
	mux.HandleFunc("PUT /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklist":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklists":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created tasklist with identifier 12345

func (*TasklistCreateResponse) HandleHTTPResponse added in v0.1.0

func (t *TasklistCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TasklistCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TasklistDeleteRequest added in v0.1.0

type TasklistDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path TasklistDeleteRequestPath
}

TasklistDeleteRequest represents the request body for deleting a tasklist.

https://apidocs.teamwork.com/docs/teamwork/v1/task-lists/delete-tasklists-id-json

func NewTasklistDeleteRequest added in v0.1.0

func NewTasklistDeleteRequest(tasklistID int64) TasklistDeleteRequest

NewTasklistDeleteRequest creates a new TasklistDeleteRequest with the provided tasklist ID.

func (TasklistDeleteRequest) HTTPRequest added in v0.1.0

func (t TasklistDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TasklistDeleteRequest.

type TasklistDeleteRequestPath added in v0.1.0

type TasklistDeleteRequestPath struct {
	// ID is the unique identifier of the tasklist to be deleted.
	ID int64
}

TasklistDeleteRequestPath contains the path parameters for deleting a tasklist.

type TasklistDeleteResponse added in v0.1.0

type TasklistDeleteResponse struct{}

TasklistDeleteResponse represents the response body for deleting a tasklist.

https://apidocs.teamwork.com/docs/teamwork/v1/task-lists/delete-tasklists-id-json

func TasklistDelete added in v0.1.0

func TasklistDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req TasklistDeleteRequest,
) (*TasklistDeleteResponse, error)

TasklistDelete deletes a tasklist using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTasklistServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.TasklistDelete(ctx, engine, projects.NewTasklistDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete tasklist: %s", err)
	} else {
		fmt.Println("tasklist deleted!")
	}

}

func startTasklistServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/tasklists", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","tasklistId":"12345"}`)
	})
	mux.HandleFunc("PUT /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklist":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklists":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

tasklist deleted!

func (*TasklistDeleteResponse) HandleHTTPResponse added in v0.1.0

func (t *TasklistDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TasklistDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TasklistGetRequest added in v0.1.0

type TasklistGetRequest struct {
	// Path contains the path parameters for the request.
	Path TasklistGetRequestPath
}

TasklistGetRequest represents the request body for loading a single tasklist.

https://apidocs.teamwork.com/docs/teamwork/v3/task-lists/get-projects-api-v3-tasklists-tasklist-id

func NewTasklistGetRequest added in v0.1.0

func NewTasklistGetRequest(tasklistID int64) TasklistGetRequest

NewTasklistGetRequest creates a new TasklistGetRequest with the provided tasklist ID. The ID is required to load a tasklist.

func (TasklistGetRequest) HTTPRequest added in v0.1.0

func (t TasklistGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TasklistGetRequest.

type TasklistGetRequestPath added in v0.1.0

type TasklistGetRequestPath struct {
	// ID is the unique identifier of the tasklist to be retrieved.
	ID int64 `json:"id"`
}

TasklistGetRequestPath contains the path parameters for loading a single tasklist.

type TasklistGetResponse added in v0.1.0

type TasklistGetResponse struct {
	Tasklist Tasklist `json:"tasklist"`
}

TasklistGetResponse contains all the information related to a tasklist.

https://apidocs.teamwork.com/docs/teamwork/v3/task-lists/get-projects-api-v3-tasklists-tasklist-id

func TasklistGet added in v0.1.0

func TasklistGet(
	ctx context.Context,
	engine *twapi.Engine,
	req TasklistGetRequest,
) (*TasklistGetResponse, error)

TasklistGet retrieves a single tasklist using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTasklistServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tasklistResponse, err := projects.TasklistGet(ctx, engine, projects.NewTasklistGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve tasklist: %s", err)
	} else {
		fmt.Printf("retrieved tasklist with identifier %d\n", tasklistResponse.Tasklist.ID)
	}

}

func startTasklistServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/tasklists", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","tasklistId":"12345"}`)
	})
	mux.HandleFunc("PUT /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklist":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklists":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved tasklist with identifier 12345

func (*TasklistGetResponse) HandleHTTPResponse added in v0.1.0

func (t *TasklistGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TasklistGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TasklistListRequest added in v0.1.0

type TasklistListRequest struct {
	// Path contains the path parameters for the request.
	Path TasklistListRequestPath

	// Filters contains the filters for loading multiple tasklists.
	Filters TasklistListRequestFilters
}

TasklistListRequest represents the request body for loading multiple tasklists.

https://apidocs.teamwork.com/docs/teamwork/v3/task-lists/get-projects-api-v3-tasklists https://apidocs.teamwork.com/docs/teamwork/v3/task-lists/get-projects-api-v3-projects-project-id-tasklists

func NewTasklistListRequest added in v0.1.0

func NewTasklistListRequest() TasklistListRequest

NewTasklistListRequest creates a new TasklistListRequest with default values.

func (TasklistListRequest) HTTPRequest added in v0.1.0

func (t TasklistListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TasklistListRequest.

type TasklistListRequestFilters added in v0.1.0

type TasklistListRequestFilters struct {
	// SearchTerm is an optional search term to filter tasklists by name.
	SearchTerm string

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of tasklists to retrieve per page. Defaults to 50.
	PageSize int64
}

TasklistListRequestFilters contains the filters for loading multiple tasklists.

type TasklistListRequestPath added in v0.1.0

type TasklistListRequestPath struct {
	// ProjectID is the unique identifier of the project whose tasklists are to be
	// retrieved.
	ProjectID int64
}

TasklistListRequestPath contains the path parameters for loading multiple tasklists.

type TasklistListResponse added in v0.1.0

type TasklistListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Tasklists []Tasklist `json:"tasklists"`
	// contains filtered or unexported fields
}

TasklistListResponse contains information by multiple tasklists matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/task-lists/get-projects-api-v3-tasklists https://apidocs.teamwork.com/docs/teamwork/v3/task-lists/get-projects-api-v3-projects-project-id-tasklists

func TasklistList added in v0.1.0

func TasklistList(
	ctx context.Context,
	engine *twapi.Engine,
	req TasklistListRequest,
) (*TasklistListResponse, error)

TasklistList retrieves multiple tasklists using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTasklistServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tasklistsRequest := projects.NewTasklistListRequest()
	tasklistsRequest.Filters.SearchTerm = "Example"

	tasklistsResponse, err := projects.TasklistList(ctx, engine, tasklistsRequest)
	if err != nil {
		fmt.Printf("failed to list tasklists: %s", err)
	} else {
		for _, tasklist := range tasklistsResponse.Tasklists {
			fmt.Printf("retrieved tasklist with identifier %d\n", tasklist.ID)
		}
	}

}

func startTasklistServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/tasklists", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","tasklistId":"12345"}`)
	})
	mux.HandleFunc("PUT /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklist":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklists":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved tasklist with identifier 12345
retrieved tasklist with identifier 12346

func (*TasklistListResponse) HandleHTTPResponse added in v0.1.0

func (t *TasklistListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TasklistListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*TasklistListResponse) Iterate added in v0.1.0

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*TasklistListResponse) SetRequest added in v0.1.0

func (t *TasklistListResponse) SetRequest(req TasklistListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type TasklistUpdateRequest added in v0.1.0

type TasklistUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path TasklistUpdateRequestPath `json:"-"`

	// Name is the name of the tasklist.
	Name *string `json:"name,omitempty"`

	// Description is the tasklist description.
	Description *string `json:"description,omitempty"`

	// MilestoneID is the ID of the milestone associated with the tasklist.
	MilestoneID *int64 `json:"milestone-Id,omitempty"`
}

TasklistUpdateRequest represents the request body for updating a tasklist. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v1/task-lists/put-tasklists-id-json

func NewTasklistUpdateRequest added in v0.1.0

func NewTasklistUpdateRequest(tasklistID int64) TasklistUpdateRequest

NewTasklistUpdateRequest creates a new TasklistUpdateRequest with the provided tasklist ID. The ID is required to update a tasklist.

func (TasklistUpdateRequest) HTTPRequest added in v0.1.0

func (t TasklistUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TasklistUpdateRequest.

type TasklistUpdateRequestPath added in v0.1.0

type TasklistUpdateRequestPath struct {
	// ID is the unique identifier of the tasklist to be updated.
	ID int64
}

TasklistUpdateRequestPath contains the path parameters for updating a tasklist.

type TasklistUpdateResponse added in v0.1.0

type TasklistUpdateResponse struct{}

TasklistUpdateResponse represents the response body for updating a tasklist.

https://apidocs.teamwork.com/docs/teamwork/v1/task-lists/put-tasklists-id-json

func TasklistUpdate added in v0.1.0

func TasklistUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req TasklistUpdateRequest,
) (*TasklistUpdateResponse, error)

TasklistUpdate updates a tasklist using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTasklistServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	tasklistRequest := projects.NewTasklistUpdateRequest(12345)
	tasklistRequest.Description = twapi.Ptr("This is an updated description.")

	_, err = projects.TasklistUpdate(ctx, engine, tasklistRequest)
	if err != nil {
		fmt.Printf("failed to update tasklist: %s", err)
	} else {
		fmt.Println("tasklist updated!")
	}

}

func startTasklistServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/{id}/tasklists", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","tasklistId":"12345"}`)
	})
	mux.HandleFunc("PUT /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklist":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/tasklists", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"tasklists":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

tasklist updated!

func (*TasklistUpdateResponse) HandleHTTPResponse added in v0.1.0

func (t *TasklistUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TasklistUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Team added in v0.5.0

type Team struct {
	// ID is the unique identifier of the team.
	ID LegacyNumber `json:"id"`

	// Name is the name of the team.
	Name string `json:"name"`

	// Description is an optional description of the team.
	Description *string `json:"description"`

	// Handle is the unique handle of the team, used in mentions.
	Handle string `json:"handle"`

	// LogoURL is the URL of the team's logo image.
	LogoURL *string `json:"logoUrl"`

	// LogoIcon is the icon of the team's logo, if available.
	LogoIcon *string `json:"logoIcon"`

	// LogoColor is the color of the team's logo, if available.
	LogoColor *string `json:"logoColor"`

	// ProjectID is the unique identifier of the project this team belongs to.
	// This is only set when the team is a project team.
	ProjectID LegacyNumber `json:"projectId"`

	// Company is the client/company the team belongs to.
	Company *struct {
		ID   LegacyNumber `json:"id"`
		Name string       `json:"name"`
	} `json:"company"`

	// ParentTeam is the parent team of this team, if available.
	ParentTeam *struct {
		ID     LegacyNumber `json:"id"`
		Name   string       `json:"name"`
		Handle string       `json:"handle"`
	} `json:"parentTeam"`

	// RootTeam is the root team of this team, if available.
	RootTeam *struct {
		ID     LegacyNumber `json:"id"`
		Name   string       `json:"name"`
		Handle string       `json:"handle"`
	} `json:"rootTeam"`

	// Members is the list of members in this team.
	Members []LegacyRelationship `json:"members"`

	// CreatedBy is the team who created this team.
	CreatedBy LegacyNumber `json:"createdByUserId"`

	// CreatedAt is the date and time when the team was created.
	CreatedAt time.Time `json:"dateCreated"`

	// UpdatedBy is the team who last updated this team.
	UpdatedBy LegacyNumber `json:"updatedByUserId"`

	// UpdatedAt is the date and time when the team was last updated.
	UpdatedAt time.Time `json:"dateUpdated"`

	// Deleted indicates whether the team has been deleted.
	Deleted bool `json:"deleted"`

	// DeletedAt is the date and time when the team was deleted, if applicable.
	DeletedAt *twapi.OptionalDateTime `json:"deletedDate"`
}

Team is a group of users who are organized together to collaborate more efficiently on projects and tasks. Teams help structure work by grouping individuals with similar roles, responsibilities, or departmental functions, making it easier to assign work, track progress, and manage communication. By using teams, organizations can streamline project planning and ensure the right people are involved in the right parts of a project, enhancing clarity and accountability across the platform.

type TeamCreateRequest added in v0.5.0

type TeamCreateRequest struct {
	// Name is the name of the team.
	Name string `json:"name"`

	// Handle is the unique handle of the team, used in mentions. It must not have
	// spaces or special characters.
	Handle *string `json:"handle,omitempty"`

	// Description is an optional description of the team.
	Description *string `json:"description,omitempty"`

	// ParentTeamID is the unique identifier of the parent team. If not provided,
	// the team will be created as a root team.
	ParentTeamID *int64 `json:"parentTeamId,omitempty"`

	// Company is the client/company the team belongs to. By default is the same
	// from the logged team creating the new team.
	CompanyID *int64 `json:"companyId,omitempty"`

	// ProjectID is the unique identifier of the project this team belongs to.
	ProjectID *int64 `json:"projectId,omitempty"`

	// UserIDs is the list of user IDs to be added as members of the team.
	UserIDs LegacyNumericList `json:"userIds,omitempty"`
}

TeamCreateRequest represents the request body for creating a new team.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/post-teams-json

func NewTeamCreateRequest added in v0.5.0

func NewTeamCreateRequest(name string) TeamCreateRequest

NewTeamCreateRequest creates a new TeamCreateRequest with the provided name.

func (TeamCreateRequest) HTTPRequest added in v0.5.0

func (u TeamCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TeamCreateRequest.

type TeamCreateResponse added in v0.5.0

type TeamCreateResponse struct {
	// ID is the unique identifier of the created team.
	ID LegacyNumber `json:"id"`
}

TeamCreateResponse represents the response body for creating a new team.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/post-teams-json

func TeamCreate added in v0.5.0

func TeamCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req TeamCreateRequest,
) (*TeamCreateResponse, error)

TeamCreate creates a new team using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTeamServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	teamRequest := projects.NewTeamCreateRequest("My Team")

	teamResponse, err := projects.TeamCreate(ctx, engine, teamRequest)
	if err != nil {
		fmt.Printf("failed to create team: %s", err)
	} else {
		fmt.Printf("created team with identifier %d\n", teamResponse.ID)
	}

}

func startTeamServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /teams", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"team":{"id":"12345"}}`)
	})
	mux.HandleFunc("GET /teams", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"teams":[{"id":"12345"},{"id":"12346"}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created team with identifier 12345

func (*TeamCreateResponse) HandleHTTPResponse added in v0.5.0

func (u *TeamCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TeamCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TeamDeleteRequest added in v0.5.0

type TeamDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path TeamDeleteRequestPath
}

TeamDeleteRequest represents the request body for deleting a team.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/delete-teams-id-json

func NewTeamDeleteRequest added in v0.5.0

func NewTeamDeleteRequest(teamID int64) TeamDeleteRequest

NewTeamDeleteRequest creates a new TeamDeleteRequest with the provided team ID.

func (TeamDeleteRequest) HTTPRequest added in v0.5.0

func (u TeamDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TeamDeleteRequest.

type TeamDeleteRequestPath added in v0.5.0

type TeamDeleteRequestPath struct {
	// ID is the unique identifier of the team to be deleted.
	ID int64
}

TeamDeleteRequestPath contains the path parameters for deleting a team.

type TeamDeleteResponse added in v0.5.0

type TeamDeleteResponse struct{}

TeamDeleteResponse represents the response body for deleting a team.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/delete-teams-id-json

func TeamDelete added in v0.5.0

func TeamDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req TeamDeleteRequest,
) (*TeamDeleteResponse, error)

TeamDelete deletes a team using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTeamServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.TeamDelete(ctx, engine, projects.NewTeamDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete team: %s", err)
	} else {
		fmt.Println("team deleted!")
	}

}

func startTeamServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /teams", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"team":{"id":"12345"}}`)
	})
	mux.HandleFunc("GET /teams", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"teams":[{"id":"12345"},{"id":"12346"}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

team deleted!

func (*TeamDeleteResponse) HandleHTTPResponse added in v0.5.0

func (u *TeamDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TeamDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TeamGetRequest added in v0.5.0

type TeamGetRequest struct {
	// Path contains the path parameters for the request.
	Path TeamGetRequestPath
}

TeamGetRequest represents the request body for loading a single team.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/get-teams-id-json

func NewTeamGetRequest added in v0.5.0

func NewTeamGetRequest(teamID int64) TeamGetRequest

NewTeamGetRequest creates a new TeamGetRequest with the provided team ID. The ID is required to load a team.

func (TeamGetRequest) HTTPRequest added in v0.5.0

func (u TeamGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TeamGetRequest.

type TeamGetRequestPath added in v0.5.0

type TeamGetRequestPath struct {
	// ID is the unique identifier of the team to be retrieved.
	ID int64 `json:"id"`
}

TeamGetRequestPath contains the path parameters for loading a single team.

type TeamGetResponse added in v0.5.0

type TeamGetResponse struct {
	Team Team `json:"team"`
}

TeamGetResponse contains all the information related to a team.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/get-teams-id-json

func TeamGet added in v0.5.0

func TeamGet(
	ctx context.Context,
	engine *twapi.Engine,
	req TeamGetRequest,
) (*TeamGetResponse, error)

TeamGet retrieves a single team using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTeamServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	teamResponse, err := projects.TeamGet(ctx, engine, projects.NewTeamGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve team: %s", err)
	} else {
		fmt.Printf("retrieved team with identifier %d\n", teamResponse.Team.ID)
	}

}

func startTeamServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /teams", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"team":{"id":"12345"}}`)
	})
	mux.HandleFunc("GET /teams", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"teams":[{"id":"12345"},{"id":"12346"}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved team with identifier 12345

func (*TeamGetResponse) HandleHTTPResponse added in v0.5.0

func (u *TeamGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TeamGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TeamListRequest added in v0.5.0

type TeamListRequest struct {
	// Path contains the path parameters for the request.
	Path TeamListRequestPath

	// Filters contains the filters for loading multiple teams.
	Filters TeamListRequestFilters
}

TeamListRequest represents the request body for loading multiple teams.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/get-teams-json https://apidocs.teamwork.com/docs/teamwork/v1/teams/get-projects-id-teams-json https://apidocs.teamwork.com/docs/teamwork/v1/teams/get-companies-id-teams-json

func NewTeamListRequest added in v0.5.0

func NewTeamListRequest() TeamListRequest

NewTeamListRequest creates a new TeamListRequest with default values.

func (TeamListRequest) HTTPRequest added in v0.5.0

func (u TeamListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TeamListRequest.

type TeamListRequestFilters added in v0.5.0

type TeamListRequestFilters struct {
	// SearchTerm is an optional search term to filter teams by name or e-mail.
	SearchTerm string

	// IncludeCompanyTeams indicates whether to include client/company teams in
	// the response. By default client/company teams are not included.
	IncludeCompanyTeams bool

	// IncludeProjectTeams indicates whether to include project teams in the
	// response. By default project teams are not included.
	IncludeProjectTeams bool

	// IncludeSubteams indicates whether to include subteams in the response. By
	// default sub-teams are not included.
	IncludeSubteams bool

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of teams to retrieve per page. Defaults to 50.
	PageSize int64
}

TeamListRequestFilters contains the filters for loading multiple teams.

type TeamListRequestPath added in v0.5.0

type TeamListRequestPath struct {
	// ProjectID is the unique identifier of the project to load teams for.
	ProjectID int64

	// CompanyID is the unique identifier of the company to load teams for.
	CompanyID int64
}

TeamListRequestPath contains the path parameters for loading multiple teams.

type TeamListResponse added in v0.5.0

type TeamListResponse struct {
	Teams []Team `json:"teams"`
	// contains filtered or unexported fields
}

TeamListResponse contains information by multiple teams matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/get-teams-json https://apidocs.teamwork.com/docs/teamwork/v1/teams/get-projects-id-teams-json https://apidocs.teamwork.com/docs/teamwork/v1/teams/get-companies-id-teams-json

func TeamList added in v0.5.0

func TeamList(
	ctx context.Context,
	engine *twapi.Engine,
	req TeamListRequest,
) (*TeamListResponse, error)

TeamList retrieves multiple teams using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTeamServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	teamsRequest := projects.NewTeamListRequest()
	teamsRequest.Filters.SearchTerm = "Team A"

	teamsResponse, err := projects.TeamList(ctx, engine, teamsRequest)
	if err != nil {
		fmt.Printf("failed to list teams: %s", err)
	} else {
		for _, team := range teamsResponse.Teams {
			fmt.Printf("retrieved team with identifier %d\n", team.ID)
		}
	}

}

func startTeamServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /teams", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"team":{"id":"12345"}}`)
	})
	mux.HandleFunc("GET /teams", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"teams":[{"id":"12345"},{"id":"12346"}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved team with identifier 12345
retrieved team with identifier 12346

func (*TeamListResponse) HandleHTTPResponse added in v0.5.0

func (u *TeamListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TeamListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*TeamListResponse) Iterate added in v0.5.0

func (u *TeamListResponse) Iterate() *TeamListRequest

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*TeamListResponse) SetRequest added in v0.5.0

func (u *TeamListResponse) SetRequest(req TeamListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type TeamUpdateRequest added in v0.5.0

type TeamUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path TeamUpdateRequestPath `json:"-"`

	// Name is the name of the team.
	Name *string `json:"name,omitempty"`

	// Handle is the unique handle of the team, used in mentions. It must not have
	// spaces or special characters.
	Handle *string `json:"handle,omitempty"`

	// Description is an optional description of the team.
	Description *string `json:"description,omitempty"`

	// CompanyID is the unique identifier of the company the team belongs to.
	CompanyID *int64 `json:"companyId,omitempty"`

	// ProjectID is the unique identifier of the project this team belongs to.
	ProjectID *int64 `json:"projectId,omitempty"`

	// UserIDs is the list of user IDs to be added as members of the team.
	UserIDs LegacyNumericList `json:"userIds,omitempty"`
}

TeamUpdateRequest represents the request body for updating a team. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/put-teams-id-json

func NewTeamUpdateRequest added in v0.5.0

func NewTeamUpdateRequest(teamID int64) TeamUpdateRequest

NewTeamUpdateRequest creates a new TeamUpdateRequest with the provided team ID. The ID is required to update a team.

func (TeamUpdateRequest) HTTPRequest added in v0.5.0

func (u TeamUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TeamUpdateRequest.

type TeamUpdateRequestPath added in v0.5.0

type TeamUpdateRequestPath struct {
	// ID is the unique identifier of the team to be updated.
	ID int64
}

TeamUpdateRequestPath contains the path parameters for updating a team.

type TeamUpdateResponse added in v0.5.0

type TeamUpdateResponse struct{}

TeamUpdateResponse represents the response body for updating a team.

https://apidocs.teamwork.com/docs/teamwork/v1/teams/put-teams-id-json

func TeamUpdate added in v0.5.0

func TeamUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req TeamUpdateRequest,
) (*TeamUpdateResponse, error)

TeamUpdate updates a team using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTeamServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	teamRequest := projects.NewTeamUpdateRequest(12345)
	teamRequest.Description = twapi.Ptr("This is an updated team description.")

	_, err = projects.TeamUpdate(ctx, engine, teamRequest)
	if err != nil {
		fmt.Printf("failed to update team: %s", err)
	} else {
		fmt.Println("team updated!")
	}

}

func startTeamServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /teams", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /teams/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"team":{"id":"12345"}}`)
	})
	mux.HandleFunc("GET /teams", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"teams":[{"id":"12345"},{"id":"12346"}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

team updated!

func (*TeamUpdateResponse) HandleHTTPResponse added in v0.5.0

func (u *TeamUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TeamUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Timelog added in v0.7.0

type Timelog struct {
	// ID is the unique identifier of the timelog.
	ID int64 `json:"id"`

	// Description is the description of the timelog.
	Description string `json:"description"`

	// Billable indicates whether the timelog is billable or not.
	Billable bool `json:"billable"`

	// Minutes is the number of minutes logged in the timelog.
	Minutes int64 `json:"minutes"`

	// LoggedAt is the date and time when the timelog was logged.
	LoggedAt time.Time `json:"timeLogged"`

	// User is the user that this timelog belongs to.
	User twapi.Relationship `json:"user"`

	// Task is the task associated with the timelog. It can be nil if the timelog
	// is not associated with a task.
	Task *twapi.Relationship `json:"task"`

	// Project is the project associated with the timelog.
	Project twapi.Relationship `json:"project"`

	// Tags are the tags associated with the timelog. They can be used to
	// categorize or label the timelog for easier filtering and searching.
	Tags []twapi.Relationship `json:"tags,omitempty"`

	// CreatedAt is the date and time when the timelog was created.
	CreatedAt time.Time `json:"createdAt"`

	// LoggedBy is the unique identifier of the user who logged the timelog.
	LoggedBy int64 `json:"loggedBy"`

	// UpdatedAt is the date and time when the timelog was last updated.
	UpdatedAt *time.Time `json:"updatedAt"`

	// UpdatedBy is the unique identifier of the user who last updated the
	// timelog.
	UpdatedBy *int64 `json:"updatedBy"`

	// DeletedAt is the date and time when the timelog was deleted, if it has been
	// deleted.
	DeletedAt *time.Time `json:"deletedAt"`

	// DeletedBy is the unique identifier of the user who deleted the timelog, if
	// it has been deleted.
	DeletedBy *int64 `json:"deletedBy"`

	// Deleted indicates whether the timelog has been deleted or not.
	Deleted bool `json:"deleted"`
}

Timelog refers to a recorded entry that tracks the amount of time a person has spent working on a specific task, project, or piece of work. These entries typically include details such as the duration of time worked, the date and time it was logged, who logged it, and any optional notes describing what was done during that period. Timelogs are essential for understanding how time is being allocated across projects, enabling teams to manage resources more effectively, invoice clients accurately, and assess productivity. They can be created manually or with timers, and are often used for reporting and billing purposes.

type TimelogCreateRequest added in v0.7.0

type TimelogCreateRequest struct {
	// Path contains the path parameters for the request.
	Path TimelogCreateRequestPath `json:"-"`

	// Description is an optional description of the timelog.
	Description *string `json:"description"`

	// Date is the date when the timelog was logged. Only the date part is used,
	// the time part is ignored.
	Date twapi.Date `json:"date"`

	// Time is the time when the timelog was logged. It can be in local time or
	// UTC.
	Time twapi.Time `json:"time"`

	// IsUTC indicates whether the time is in UTC. When false, it will consider
	// the timezone configured for the logged user.
	IsUTC bool `json:"isUTC"`

	// Hours is the number of hours logged in the timelog. This is optional and
	// can be used instead of Minutes. If both Hours and Minutes are provided,
	// they will be summed up to calculate the total time logged.
	Hours int64 `json:"hours"`

	// Minutes is the number of minutes logged in the timelog. This is optional
	// and can be used instead of Hours. If both Hours and Minutes are provided,
	// they will be summed up to calculate the total time logged.
	Minutes int64 `json:"minutes"`

	// Billable indicates whether the timelog is billable or not.
	Billable bool `json:"isBillable"`

	// UserID is an optional ID of the user who logged the timelog. If not
	// provided, the timelog will be logged by the user making the request.
	UserID *int64 `json:"userId"`

	// TagIDs is an optional list of tag IDs to associate with the timelog.
	TagIDs []int64 `json:"tagIds"`
}

TimelogCreateRequest represents the request body for creating a new timelog.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/post-projects-api-v3-tasks-task-id-time-json https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/post-projects-api-v3-projects-project-id-time-json

func NewTimelogCreateRequestInProject added in v0.7.0

func NewTimelogCreateRequestInProject(
	projectID int64,
	datetime time.Time,
	duration time.Duration,
) TimelogCreateRequest

NewTimelogCreateRequestInProject creates a new TimelogCreateRequest with the provided datetime and duration in a specific project.

func NewTimelogCreateRequestInTask added in v0.7.0

func NewTimelogCreateRequestInTask(taskID int64, datetime time.Time, duration time.Duration) TimelogCreateRequest

NewTimelogCreateRequestInTask creates a new TimelogCreateRequest with the provided datetime and duration, associating it with a specific task.

func (TimelogCreateRequest) HTTPRequest added in v0.7.0

func (t TimelogCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimelogCreateRequest.

type TimelogCreateRequestPath added in v0.7.0

type TimelogCreateRequestPath struct {
	// TaskID is an optional ID of the task associated with the timelog. If
	// provided, the timelog will be associated with this task. At least one of
	// TaskID or ProjectID must be provided. If both are provided, the timelog
	// will be associated with the task.
	TaskID int64
	// ProjectID is the unique identifier of the project where the timelog will be
	// created. At least one of TaskID or ProjectID must be provided. If both are
	// provided, the timelog will be associated with the task.
	ProjectID int64
}

TimelogUpdateRequestPath contains the path parameters for creating a timelog.

type TimelogCreateResponse added in v0.7.0

type TimelogCreateResponse struct {
	// Timelog contains the created timelog information.
	Timelog Timelog `json:"timelog"`
}

TimelogCreateResponse represents the response body for creating a new timelog.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/post-projects-api-v3-tasks-task-id-time-json https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/post-projects-api-v3-projects-project-id-time-json

func TimelogCreate added in v0.7.0

func TimelogCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req TimelogCreateRequest,
) (*TimelogCreateResponse, error)

TimelogCreate creates a new timelog using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimelogServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timelogRequest := projects.NewTimelogCreateRequestInTask(777, time.Now(), 30*time.Minute)
	timelogRequest.Description = twapi.Ptr("This is a new timelog created via the API.")

	timelogResponse, err := projects.TimelogCreate(ctx, engine, timelogRequest)
	if err != nil {
		fmt.Printf("failed to create timelog: %s", err)
	} else {
		fmt.Printf("created timelog with identifier %d\n", timelogResponse.Timelog.ID)
	}

}

func startTimelogServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasks/{id}/time", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/time", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelogs":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created timelog with identifier 12345

func (*TimelogCreateResponse) HandleHTTPResponse added in v0.7.0

func (t *TimelogCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimelogCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimelogDeleteRequest added in v0.7.0

type TimelogDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path TimelogDeleteRequestPath
}

TimelogDeleteRequest represents the request body for deleting a timelog.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/delete-projects-api-v3-time-timelog-id-json

func NewTimelogDeleteRequest added in v0.7.0

func NewTimelogDeleteRequest(timelogID int64) TimelogDeleteRequest

NewTimelogDeleteRequest creates a new TimelogDeleteRequest with the provided timelog ID.

func (TimelogDeleteRequest) HTTPRequest added in v0.7.0

func (t TimelogDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimelogDeleteRequest.

type TimelogDeleteRequestPath added in v0.7.0

type TimelogDeleteRequestPath struct {
	// ID is the unique identifier of the timelog to be deleted.
	ID int64
}

TimelogDeleteRequestPath contains the path parameters for deleting a timelog.

type TimelogDeleteResponse added in v0.7.0

type TimelogDeleteResponse struct{}

TimelogDeleteResponse represents the response body for deleting a timelog.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/delete-projects-api-v3-time-timelog-id-json

func TimelogDelete added in v0.7.0

func TimelogDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req TimelogDeleteRequest,
) (*TimelogDeleteResponse, error)

TimelogDelete deletes a timelog using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimelogServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.TimelogDelete(ctx, engine, projects.NewTimelogDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete timelog: %s", err)
	} else {
		fmt.Println("timelog deleted!")
	}

}

func startTimelogServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasks/{id}/time", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/time", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelogs":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

timelog deleted!

func (*TimelogDeleteResponse) HandleHTTPResponse added in v0.7.0

func (t *TimelogDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimelogDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimelogGetRequest added in v0.7.0

type TimelogGetRequest struct {
	// Path contains the path parameters for the request.
	Path TimelogGetRequestPath
}

TimelogGetRequest represents the request body for loading a single timelog.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-time-timelog-id-json

func NewTimelogGetRequest added in v0.7.0

func NewTimelogGetRequest(timelogID int64) TimelogGetRequest

NewTimelogGetRequest creates a new TimelogGetRequest with the provided timelog ID. The ID is required to load a timelog.

func (TimelogGetRequest) HTTPRequest added in v0.7.0

func (t TimelogGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimelogGetRequest.

type TimelogGetRequestPath added in v0.7.0

type TimelogGetRequestPath struct {
	// ID is the unique identifier of the timelog to be retrieved.
	ID int64 `json:"id"`
}

TimelogGetRequestPath contains the path parameters for loading a single timelog.

type TimelogGetResponse added in v0.7.0

type TimelogGetResponse struct {
	Timelog Timelog `json:"timelog"`
}

TimelogGetResponse contains all the information related to a timelog.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-time-timelog-id-json

func TimelogGet added in v0.7.0

func TimelogGet(
	ctx context.Context,
	engine *twapi.Engine,
	req TimelogGetRequest,
) (*TimelogGetResponse, error)

TimelogGet retrieves a single timelog using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimelogServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timelogResponse, err := projects.TimelogGet(ctx, engine, projects.NewTimelogGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve timelog: %s", err)
	} else {
		fmt.Printf("retrieved timelog with identifier %d\n", timelogResponse.Timelog.ID)
	}

}

func startTimelogServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasks/{id}/time", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/time", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelogs":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved timelog with identifier 12345

func (*TimelogGetResponse) HandleHTTPResponse added in v0.7.0

func (t *TimelogGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimelogGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimelogListRequest added in v0.7.0

type TimelogListRequest struct {
	// Path contains the path parameters for the request.
	Path TimelogListRequestPath

	// Filters contains the filters for loading multiple timelogs.
	Filters TimelogListRequestFilters
}

TimelogListRequest represents the request body for loading multiple timelogs.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-time-json https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-tasks-task-id-time-json https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-projects-project-id-time-json

func NewTimelogListRequest added in v0.7.0

func NewTimelogListRequest() TimelogListRequest

NewTimelogListRequest creates a new TimelogListRequest with default values.

func (TimelogListRequest) HTTPRequest added in v0.7.0

func (t TimelogListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimelogListRequest.

type TimelogListRequestFilters added in v0.7.0

type TimelogListRequestFilters struct {
	// TagIDs is an optional list of tag IDs to filter the timelogs by. If provided,
	// only timelogs associated with these tags will be returned.
	TagIDs []int64

	// MatchAllTags indicates whether to match all tags or any tag. If true, only
	// timelogs that have all the specified tags will be returned. If false,
	// timelogs that have at least one of the specified tags will be returned.
	MatchAllTags *bool

	// StartDate is an optional start date to filter the timelogs by. If provided,
	// only timelogs that started on or after this date will be returned.
	StartDate *time.Time

	// EndDate is an optional end date to filter the timelogs by. If provided,
	// only timelogs that ended on or before this date will be returned.
	EndDate *time.Time

	// AssignedToUserIDs is an optional list of user IDs to filter the timelogs
	// by. If provided, only timelogs assigned to these users will be returned.
	AssignedToUserIDs []int64

	// AssignedToCompanyIDs is an optional list of client/company IDs to filter
	// the timelogs by users in the specific companies. If provided, only timelogs
	// assigned to these companies will be returned.
	AssignedToCompanyIDs []int64

	// AssignedToTeamIDs is an optional list of team IDs to filter the timelogs by
	// users in the specific teams. If provided, only timelogs assigned to these
	// teams will be returned.
	AssignedToTeamIDs []int64

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of timelogs to retrieve per page. Defaults to 50.
	PageSize int64
}

TimelogListRequestFilters contains the filters for loading multiple timelogs.

type TimelogListRequestPath added in v0.7.0

type TimelogListRequestPath struct {
	// TaskID is an optional ID of the task whose timelogs are to be retrieved. If
	// provided, the timelogs will be filtered by this task. When both TaskID and
	// ProjectID are provided, the timelogs will be filtered by the task.
	TaskID int64
	// ProjectID is the unique identifier of the project whose timelogs are to be
	// retrieved. If provided, the timelogs will be filtered by this project. When
	// both TaskID and ProjectID are provided, the timelogs will be filtered by
	// the task.
	ProjectID int64
}

TimelogListRequestPath contains the path parameters for loading multiple timelogs.

type TimelogListResponse added in v0.7.0

type TimelogListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Timelogs []Timelog `json:"timelogs"`
	// contains filtered or unexported fields
}

TimelogListResponse contains information by multiple timelogs matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-time-json https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-tasks-task-id-time-json https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-projects-project-id-time-json

func TimelogList added in v0.7.0

func TimelogList(
	ctx context.Context,
	engine *twapi.Engine,
	req TimelogListRequest,
) (*TimelogListResponse, error)

TimelogList retrieves multiple timelogs using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimelogServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timelogsRequest := projects.NewTimelogListRequest()
	timelogsResponse, err := projects.TimelogList(ctx, engine, timelogsRequest)
	if err != nil {
		fmt.Printf("failed to list timelogs: %s", err)
	} else {
		for _, timelog := range timelogsResponse.Timelogs {
			fmt.Printf("retrieved timelog with identifier %d\n", timelog.ID)
		}
	}

}

func startTimelogServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasks/{id}/time", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/time", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelogs":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved timelog with identifier 12345
retrieved timelog with identifier 12346

func (*TimelogListResponse) HandleHTTPResponse added in v0.7.0

func (t *TimelogListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimelogListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*TimelogListResponse) Iterate added in v0.7.0

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*TimelogListResponse) SetRequest added in v0.7.0

func (t *TimelogListResponse) SetRequest(req TimelogListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type TimelogUpdateRequest added in v0.7.0

type TimelogUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path TimelogUpdateRequestPath `json:"-"`

	// Description is an optional description of the timelog.
	Description *string `json:"description,omitempty"`

	// Date is the date when the timelog was logged. Only the date part is used,
	// the time part is ignored.
	Date *twapi.Date `json:"date,omitempty"`

	// Time is the time when the timelog was logged. It can be in local time or
	// UTC.
	Time *twapi.Time `json:"time,omitempty"`

	// IsUTC indicates whether the time is in UTC. When false, it will consider
	// the timezone configured for the logged user.
	IsUTC *bool `json:"isUTC,omitempty"`

	// Hours is the number of hours logged in the timelog. This is optional and
	// can be used instead of Minutes. If both Hours and Minutes are provided,
	// they will be summed up to calculate the total time logged.
	Hours *int64 `json:"hours,omitempty"`

	// Minutes is the number of minutes logged in the timelog. This is optional
	// and can be used instead of Hours. If both Hours and Minutes are provided,
	// they will be summed up to calculate the total time logged.
	Minutes *int64 `json:"minutes,omitempty"`

	// Billable indicates whether the timelog is billable or not.
	Billable *bool `json:"isBillable,omitempty"`

	// UserID is an optional ID of the user who logged the timelog. If not
	// provided, the timelog will be logged by the user making the request.
	UserID *int64 `json:"userId,omitempty"`

	// TagIDs is an optional list of tag IDs to associate with the timelog.
	TagIDs []int64 `json:"tagIds,omitempty"`
}

TimelogUpdateRequest represents the request body for updating a timelog. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/patch-projects-api-v3-time-timelog-id-json

func NewTimelogUpdateRequest added in v0.7.0

func NewTimelogUpdateRequest(timelogID int64) TimelogUpdateRequest

NewTimelogUpdateRequest creates a new TimelogUpdateRequest with the provided timelog ID. The ID is required to update a timelog.

func (TimelogUpdateRequest) HTTPRequest added in v0.7.0

func (t TimelogUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimelogUpdateRequest.

type TimelogUpdateRequestPath added in v0.7.0

type TimelogUpdateRequestPath struct {
	// ID is the unique identifier of the timelog to be updated.
	ID int64
}

TimelogUpdateRequestPath contains the path parameters for updating a timelog.

type TimelogUpdateResponse added in v0.7.0

type TimelogUpdateResponse struct{}

TimelogUpdateResponse represents the response body for updating a timelog.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/patch-projects-api-v3-time-timelog-id-json

func TimelogUpdate added in v0.7.0

func TimelogUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req TimelogUpdateRequest,
) (*TimelogUpdateResponse, error)

TimelogUpdate updates a timelog using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimelogServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timelogRequest := projects.NewTimelogUpdateRequest(12345)
	timelogRequest.Description = twapi.Ptr("This is an updated description.")

	_, err = projects.TimelogUpdate(ctx, engine, timelogRequest)
	if err != nil {
		fmt.Printf("failed to update timelog: %s", err)
	} else {
		fmt.Println("timelog updated!")
	}

}

func startTimelogServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/tasks/{id}/time", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "777" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("PATCH /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/time/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelog":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/time", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timelogs":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

timelog updated!

func (*TimelogUpdateResponse) HandleHTTPResponse added in v0.7.0

func (t *TimelogUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimelogUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Timer added in v1.1.0

type Timer struct {
	// ID is the unique identifier of the timer.
	ID int64 `json:"id"`

	// Description is a brief summary of the timer's purpose.
	Description string `json:"description"`

	// Running indicates whether the timer is currently running.
	Running bool `json:"running"`

	// Billable indicates whether the timer is billable.
	Billable bool `json:"billable"`

	// User is the user associated with the timer.
	User twapi.Relationship `json:"user"`

	// Task is the task associated with the timer.
	Task *twapi.Relationship `json:"task"`

	// Project is the project associated with the timer.
	Project twapi.Relationship `json:"project"`

	// Timelog is the timelog associated with the timer.
	Timelog *twapi.Relationship `json:"timelog,omitempty"`

	// CreatedAt is the date and time when the timer was created.
	CreatedAt time.Time `json:"createdAt"`

	// UpdatedAt is the date and time when the timer was last updated.
	UpdatedAt time.Time `json:"updatedAt"`

	// DeletedAt is the date and time when the timer was deleted.
	DeletedAt *time.Time `json:"deletedAt"`

	// Deleted indicates whether the timer has been deleted.
	Deleted bool `json:"deleted"`

	// Duration is the total duration of the timer in seconds.
	Duration int64 `json:"duration"`

	// LastStartedAt is the date and time when the timer was last started.
	LastStartedAt time.Time `json:"lastStartedAt"`

	// LastIntervalAt is the date and time when the last interval ended.
	LastIntervalAt *time.Time `json:"timerLastIntervalEnd,omitempty"`

	// Intervals is a list of time intervals for the timer.
	Intervals []struct {
		// ID is the unique identifier of the interval.
		ID int64 `json:"id"`

		// From is the start time of the interval.
		From time.Time `json:"from"`

		// To is the end time of the interval.
		To time.Time `json:"to"`

		// Duration is the total duration of the interval in seconds.
		Duration int64 `json:"duration"`
	} `json:"intervals"`
}

Timer is a built-in tool that allows users to accurately track the time they spend working on specific tasks, projects, or client work. Instead of manually recording hours, users can start, pause, and stop timers directly within the platform or through the desktop and mobile apps, ensuring precise time logs without interrupting their workflow. Once recorded, these entries are automatically linked to the relevant task or project, making it easier to monitor productivity, manage billable hours, and generate detailed reports for both internal tracking and client invoicing.

More information can be found at: https://support.teamwork.com/projects/time-tracking/multiple-timers

type TimerCompleteRequest added in v1.1.0

type TimerCompleteRequest struct {
	// Path contains the path parameters for the request.
	Path TimerCompleteRequestPath `json:"-"`
}

TimerCompleteRequest represents the request body for completing a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/put-projects-api-v3-me-timers-timer-id-complete-json

func NewTimerCompleteRequest added in v1.1.0

func NewTimerCompleteRequest(timerID int64) TimerCompleteRequest

NewTimerCompleteRequest creates a new TimerCompleteRequest with the provided timer ID. The ID is required to complete a timer.

func (TimerCompleteRequest) HTTPRequest added in v1.1.0

func (t TimerCompleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimerCompleteRequest.

type TimerCompleteRequestPath added in v1.1.0

type TimerCompleteRequestPath struct {
	// ID is the unique identifier of the timer to be completed.
	ID int64
}

TimerCompleteRequestPath contains the path parameters for completing a timer.

type TimerCompleteResponse added in v1.1.0

type TimerCompleteResponse struct {
	// Timer represents the completed timer.
	Timer Timer `json:"timer"`
}

TimerCompleteResponse represents the response body for completing a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/put-projects-api-v3-me-timers-timer-id-complete-json

func TimerComplete added in v1.1.0

func TimerComplete(
	ctx context.Context,
	engine *twapi.Engine,
	req TimerCompleteRequest,
) (*TimerCompleteResponse, error)

TimerComplete completes a timer using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimerServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timerRequest := projects.NewTimerCompleteRequest(12345)

	_, err = projects.TimerComplete(ctx, engine, timerRequest)
	if err != nil {
		fmt.Printf("failed to complete timer: %s", err)
	} else {
		fmt.Println("timer completed!")
	}

}

func startTimerServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/me/timers", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/pause", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/complete", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/timers", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timers":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

timer completed!

func (*TimerCompleteResponse) HandleHTTPResponse added in v1.1.0

func (t *TimerCompleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimerCompleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimerCreateRequest added in v1.1.0

type TimerCreateRequest struct {
	// Description is a brief summary of the timer's purpose.
	Description *string `json:"description"`

	// Billable indicates whether the timer is billable.
	Billable *bool `json:"isBillable"`

	// Running indicates whether the timer is currently running.
	Running *bool `json:"isRunning"`

	// Seconds is the total duration of the timer in seconds.
	Seconds *int64 `json:"seconds"`

	// StopRunningTimers indicates whether to stop all running timers.
	StopRunningTimers *bool `json:"stopRunningTimers"`

	// ProjectID is the unique identifier of the project associated with the
	// timer. The ProjectID must be provided.
	ProjectID int64 `json:"projectId"`

	// TaskID is the unique identifier of the task associated with the timer.
	TaskID *int64 `json:"taskId"`
}

TimerCreateRequest represents the request body for creating a new timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/post-projects-api-v3-me-timers-json

func NewTimerCreateRequest added in v1.1.0

func NewTimerCreateRequest(projectID int64) TimerCreateRequest

NewTimerCreateRequest creates a new TimerCreateRequest with the provided project.

func (TimerCreateRequest) HTTPRequest added in v1.1.0

func (t TimerCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimerCreateRequest.

type TimerCreateResponse added in v1.1.0

type TimerCreateResponse struct {
	// Timer represents the created timer.
	Timer Timer `json:"timer"`
}

TimerCreateResponse represents the response body for creating a new timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/post-projects-api-v3-me-timers-json

func TimerCreate added in v1.1.0

func TimerCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req TimerCreateRequest,
) (*TimerCreateResponse, error)

TimerCreate creates a new timer using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimerServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timerRequest := projects.NewTimerCreateRequest(777)
	timerRequest.Description = twapi.Ptr("This is a new timer created via the API.")

	timerResponse, err := projects.TimerCreate(ctx, engine, timerRequest)
	if err != nil {
		fmt.Printf("failed to create timer: %s", err)
	} else {
		fmt.Printf("created timer with identifier %d\n", timerResponse.Timer.ID)
	}

}

func startTimerServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/me/timers", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/pause", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/complete", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/timers", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timers":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created timer with identifier 12345

func (*TimerCreateResponse) HandleHTTPResponse added in v1.1.0

func (t *TimerCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimerCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimerDeleteRequest added in v1.1.0

type TimerDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path TimerDeleteRequestPath
}

TimerDeleteRequest represents the request body for deleting a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/delete-projects-api-v3-time-timer-id-json

func NewTimerDeleteRequest added in v1.1.0

func NewTimerDeleteRequest(timerID int64) TimerDeleteRequest

NewTimerDeleteRequest creates a new TimerDeleteRequest with the provided timer ID.

func (TimerDeleteRequest) HTTPRequest added in v1.1.0

func (t TimerDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimerDeleteRequest.

type TimerDeleteRequestPath added in v1.1.0

type TimerDeleteRequestPath struct {
	// ID is the unique identifier of the timer to be deleted.
	ID int64
}

TimerDeleteRequestPath contains the path parameters for deleting a timer.

type TimerDeleteResponse added in v1.1.0

type TimerDeleteResponse struct{}

TimerDeleteResponse represents the response body for deleting a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/delete-projects-api-v3-time-timer-id-json

func TimerDelete added in v1.1.0

func TimerDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req TimerDeleteRequest,
) (*TimerDeleteResponse, error)

TimerDelete deletes a timer using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimerServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.TimerDelete(ctx, engine, projects.NewTimerDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete timer: %s", err)
	} else {
		fmt.Println("timer deleted!")
	}

}

func startTimerServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/me/timers", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/pause", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/complete", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/timers", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timers":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

timer deleted!

func (*TimerDeleteResponse) HandleHTTPResponse added in v1.1.0

func (t *TimerDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimerDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimerGetRequest added in v1.1.0

type TimerGetRequest struct {
	// Path contains the path parameters for the request.
	Path TimerGetRequestPath
}

TimerGetRequest represents the request body for loading a single timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-timers-timer-id-json

func NewTimerGetRequest added in v1.1.0

func NewTimerGetRequest(timerID int64) TimerGetRequest

NewTimerGetRequest creates a new TimerGetRequest with the provided timer ID. The ID is required to load a timer.

func (TimerGetRequest) HTTPRequest added in v1.1.0

func (t TimerGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimerGetRequest.

type TimerGetRequestPath added in v1.1.0

type TimerGetRequestPath struct {
	// ID is the unique identifier of the timer to be retrieved.
	ID int64 `json:"id"`
}

TimerGetRequestPath contains the path parameters for loading a single timer.

type TimerGetResponse added in v1.1.0

type TimerGetResponse struct {
	Timer Timer `json:"timer"`
}

TimerGetResponse contains all the information related to a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-timers-timer-id-json

func TimerGet added in v1.1.0

func TimerGet(
	ctx context.Context,
	engine *twapi.Engine,
	req TimerGetRequest,
) (*TimerGetResponse, error)

TimerGet retrieves a single timer using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimerServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timerResponse, err := projects.TimerGet(ctx, engine, projects.NewTimerGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve timer: %s", err)
	} else {
		fmt.Printf("retrieved timer with identifier %d\n", timerResponse.Timer.ID)
	}

}

func startTimerServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/me/timers", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/pause", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/complete", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/timers", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timers":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved timer with identifier 12345

func (*TimerGetResponse) HandleHTTPResponse added in v1.1.0

func (t *TimerGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimerGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimerListRequest added in v1.1.0

type TimerListRequest struct {
	// Filters contains the filters for loading multiple timers.
	Filters TimerListRequestFilters
}

TimerListRequest represents the request body for loading multiple timers.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-timers-json

func NewTimerListRequest added in v1.1.0

func NewTimerListRequest() TimerListRequest

NewTimerListRequest creates a new TimerListRequest with default values.

func (TimerListRequest) HTTPRequest added in v1.1.0

func (t TimerListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimerListRequest.

type TimerListRequestFilters added in v1.1.0

type TimerListRequestFilters struct {
	// UserID is the unique identifier of the user whose timers are to be
	// retrieved.
	UserID int64

	// ProjectID is the unique identifier of the project whose timers are to be
	// retrieved.
	ProjectID int64

	// TaskID is the unique identifier of the task whose timers are to be
	// retrieved.
	TaskID int64

	// RunningTimersOnly is a flag to indicate whether to retrieve only running
	// timers.
	RunningTimersOnly bool

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of timers to retrieve per page. Defaults to 50.
	PageSize int64
}

TimerListRequestFilters contains the filters for loading multiple timers.

type TimerListResponse added in v1.1.0

type TimerListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Timers []Timer `json:"timers"`
	// contains filtered or unexported fields
}

TimerListResponse contains information by multiple timers matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/get-projects-api-v3-timers-json

func TimerList added in v1.1.0

func TimerList(
	ctx context.Context,
	engine *twapi.Engine,
	req TimerListRequest,
) (*TimerListResponse, error)

TimerList retrieves multiple timers using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimerServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timersRequest := projects.NewTimerListRequest()
	timersResponse, err := projects.TimerList(ctx, engine, timersRequest)
	if err != nil {
		fmt.Printf("failed to list timers: %s", err)
	} else {
		for _, timer := range timersResponse.Timers {
			fmt.Printf("retrieved timer with identifier %d\n", timer.ID)
		}
	}

}

func startTimerServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/me/timers", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/pause", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/complete", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/timers", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timers":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved timer with identifier 12345
retrieved timer with identifier 12346

func (*TimerListResponse) HandleHTTPResponse added in v1.1.0

func (u *TimerListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimerListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*TimerListResponse) Iterate added in v1.1.0

func (u *TimerListResponse) Iterate() *TimerListRequest

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*TimerListResponse) SetRequest added in v1.1.0

func (u *TimerListResponse) SetRequest(req TimerListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type TimerPauseRequest added in v1.1.0

type TimerPauseRequest struct {
	// Path contains the path parameters for the request.
	Path TimerPauseRequestPath `json:"-"`
}

TimerPauseRequest represents the request body for pausing a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/put-projects-api-v3-me-timers-timer-id-pause-json

func NewTimerPauseRequest added in v1.1.0

func NewTimerPauseRequest(timerID int64) TimerPauseRequest

NewTimerPauseRequest creates a new TimerPauseRequest with the provided timer ID. The ID is required to pause a timer.

func (TimerPauseRequest) HTTPRequest added in v1.1.0

func (t TimerPauseRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimerPauseRequest.

type TimerPauseRequestPath added in v1.1.0

type TimerPauseRequestPath struct {
	// ID is the unique identifier of the timer to be paused.
	ID int64
}

TimerPauseRequestPath contains the path parameters for pausing a timer.

type TimerPauseResponse added in v1.1.0

type TimerPauseResponse struct {
	// Timer represents the paused timer.
	Timer Timer `json:"timer"`
}

TimerPauseResponse represents the response body for pausing a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/put-projects-api-v3-me-timers-timer-id-pause-json

func TimerPause added in v1.1.0

func TimerPause(
	ctx context.Context,
	engine *twapi.Engine,
	req TimerPauseRequest,
) (*TimerPauseResponse, error)

TimerPause pauses a timer using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimerServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timerRequest := projects.NewTimerPauseRequest(12345)

	_, err = projects.TimerPause(ctx, engine, timerRequest)
	if err != nil {
		fmt.Printf("failed to pause timer: %s", err)
	} else {
		fmt.Println("timer paused!")
	}

}

func startTimerServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/me/timers", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/pause", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/complete", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/timers", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timers":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

timer paused!

func (*TimerPauseResponse) HandleHTTPResponse added in v1.1.0

func (t *TimerPauseResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimerPauseResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimerResumeRequest added in v1.1.0

type TimerResumeRequest struct {
	// Path contains the path parameters for the request.
	Path TimerResumeRequestPath `json:"-"`
}

TimerResumeRequest represents the request body for resuming a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/put-projects-api-v3-me-timers-timer-id-resume-json

func NewTimerResumeRequest added in v1.1.0

func NewTimerResumeRequest(timerID int64) TimerResumeRequest

NewTimerResumeRequest creates a new TimerResumeRequest with the provided timer ID. The ID is required to resume a timer.

func (TimerResumeRequest) HTTPRequest added in v1.1.0

func (t TimerResumeRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimerResumeRequest.

type TimerResumeRequestPath added in v1.1.0

type TimerResumeRequestPath struct {
	// ID is the unique identifier of the timer to be resumed.
	ID int64
}

TimerResumeRequestPath contains the path parameters for resuming a timer.

type TimerResumeResponse added in v1.1.0

type TimerResumeResponse struct {
	// Timer represents the resumed timer.
	Timer Timer `json:"timer"`
}

TimerResumeResponse represents the response body for resuming a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/put-projects-api-v3-me-timers-timer-id-resume-json

func TimerResume added in v1.1.0

func TimerResume(
	ctx context.Context,
	engine *twapi.Engine,
	req TimerResumeRequest,
) (*TimerResumeResponse, error)

TimerResume resumes a timer using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimerServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timerRequest := projects.NewTimerResumeRequest(12345)

	_, err = projects.TimerResume(ctx, engine, timerRequest)
	if err != nil {
		fmt.Printf("failed to resume timer: %s", err)
	} else {
		fmt.Println("timer resumed!")
	}

}

func startTimerServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/me/timers", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/pause", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/complete", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/timers", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timers":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

timer resumed!

func (*TimerResumeResponse) HandleHTTPResponse added in v1.1.0

func (t *TimerResumeResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimerResumeResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type TimerUpdateRequest added in v1.1.0

type TimerUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path TimerUpdateRequestPath `json:"-"`

	// Description is a brief summary of the timer's purpose.
	Description *string `json:"description,omitempty"`

	// Billable indicates whether the timer is billable.
	Billable *bool `json:"isBillable,omitempty"`

	// Running indicates whether the timer is currently running.
	Running *bool `json:"isRunning,omitempty"`

	// StopRunningTimers indicates whether to stop all running timers.
	StopRunningTimers *bool `json:"stopRunningTimers,omitempty"`

	// ProjectID is the unique identifier of the project associated with the
	// timer. The ProjectID must be provided.
	ProjectID *int64 `json:"projectId,omitempty"`

	// TaskID is the unique identifier of the task associated with the timer.
	TaskID *int64 `json:"taskId,omitempty"`
}

TimerUpdateRequest represents the request body for updating a timer. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/patch-projects-api-v3-time-timer-id-json

func NewTimerUpdateRequest added in v1.1.0

func NewTimerUpdateRequest(timerID int64) TimerUpdateRequest

NewTimerUpdateRequest creates a new TimerUpdateRequest with the provided timer ID. The ID is required to update a timer.

func (TimerUpdateRequest) HTTPRequest added in v1.1.0

func (t TimerUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the TimerUpdateRequest.

type TimerUpdateRequestPath added in v1.1.0

type TimerUpdateRequestPath struct {
	// ID is the unique identifier of the timer to be updated.
	ID int64
}

TimerUpdateRequestPath contains the path parameters for updating a timer.

type TimerUpdateResponse added in v1.1.0

type TimerUpdateResponse struct {
	// Timer represents the updated timer.
	Timer Timer `json:"timer"`
}

TimerUpdateResponse represents the response body for updating a timer.

https://apidocs.teamwork.com/docs/teamwork/v3/time-tracking/patch-projects-api-v3-time-timer-id-json

func TimerUpdate added in v1.1.0

func TimerUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req TimerUpdateRequest,
) (*TimerUpdateResponse, error)

TimerUpdate updates a timer using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startTimerServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	timerRequest := projects.NewTimerUpdateRequest(12345)
	timerRequest.Description = twapi.Ptr("This is an updated description.")

	_, err = projects.TimerUpdate(ctx, engine, timerRequest)
	if err != nil {
		fmt.Printf("failed to update timer: %s", err)
	} else {
		fmt.Println("timer updated!")
	}

}

func startTimerServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /projects/api/v3/me/timers", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/pause", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("PUT /projects/api/v3/me/timers/{id}/complete", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{}`)
	})
	mux.HandleFunc("DELETE /projects/api/v3/me/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("GET /projects/api/v3/timers/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timer":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/timers", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"timers":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

timer updated!

func (*TimerUpdateResponse) HandleHTTPResponse added in v1.1.0

func (t *TimerUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the TimerUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type User added in v0.3.0

type User struct {
	// ID is the unique identifier of the user.
	ID int64 `json:"id"`

	// FirstName is the first name of the user.
	FirstName string `json:"firstName"`

	// LastName is the last name of the user.
	LastName string `json:"lastName"`

	// Title is the title of the user (e.g. "Senior Developer").
	Title *string `json:"title"`

	// Email is the email address of the user.
	Email string `json:"email"`

	// Admin indicates whether the user is an administrator.
	Admin bool `json:"isAdmin"`

	// Type is the type of user. Possible values are "account", "collaborator" or "contact".
	Type string `json:"type"`

	// Cost is the hourly cost, to your company, to employ this user.
	Cost *twapi.Money `json:"userCost"`

	// Rate is the individual's hourly rate. This is what you charge for someone's
	// time on a project.
	Rate *twapi.Money `json:"userRate"`

	// Company is the client/company the user belongs to.
	Company twapi.Relationship `json:"company"`

	// JobRoles are the job roles assigned to the user.
	JobRoles []twapi.Relationship `json:"jobRoles,omitempty"`

	// Skills are the skills assigned to the user.
	Skills []twapi.Relationship `json:"skills,omitempty"`

	// Deleted indicates whether the user has been deleted.
	Deleted bool `json:"deleted"`

	// CreatedBy is the user who created this user.
	CreatedBy *twapi.Relationship `json:"createdBy"`

	// CreatedAt is the date and time when the user was created.
	CreatedAt time.Time `json:"createdAt"`

	// UpdatedBy is the user who last updated this user.
	UpdatedBy *twapi.Relationship `json:"updatedBy"`

	// UpdatedAt is the date and time when the user was last updated.
	UpdatedAt *time.Time `json:"updatedAt"`
}

User is an individual who has access to one or more projects within a Teamwork site, typically as a team member, collaborator, or administrator. Users can be assigned tasks, participate in discussions, log time, share files, and interact with other members depending on their permission levels. Each user has a unique profile that defines their role, visibility, and access to features and project data. Users can belong to clients/companies or teams within the system, and their permissions can be customized to control what actions they can perform or what information they can see.

More information can be found at: https://support.teamwork.com/projects/getting-started/people-overview

type UserCreateRequest added in v0.3.0

type UserCreateRequest struct {
	// FirstName is the first name of the user.
	FirstName string `json:"first-name"`

	// LastName is the last name of the user.
	LastName string `json:"last-name"`

	// Title is the title of the user (e.g. "Senior Developer").
	Title *string `json:"title,omitempty"`

	// Email is the email address of the user.
	Email string `json:"email-address"`

	// Admin indicates whether the user is an administrator. By default it is
	// false.
	Admin *bool `json:"administrator,omitempty"`

	// Type is the type of user. Possible values are "account", "collaborator" or
	// "contact". By default it is "account".
	Type *string `json:"user-type,omitempty"`

	// Company is the client/company the user belongs to. By default is the same
	// from the logged user creating the new user.
	CompanyID *int64 `json:"company-id,omitempty"`
}

UserCreateRequest represents the request body for creating a new user.

https://apidocs.teamwork.com/docs/teamwork/v1/people/post-people-json

func NewUserCreateRequest added in v0.3.0

func NewUserCreateRequest(firstName, lastName, email string) UserCreateRequest

NewUserCreateRequest creates a new UserCreateRequest with the provided name in a specific project.

func (UserCreateRequest) HTTPRequest added in v0.3.0

func (u UserCreateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the UserCreateRequest.

type UserCreateResponse added in v0.3.0

type UserCreateResponse struct {
	// ID is the unique identifier of the created user.
	ID LegacyNumber `json:"id"`
}

UserCreateResponse represents the response body for creating a new user.

https://apidocs.teamwork.com/docs/teamwork/v1/people/post-people-json

func UserCreate added in v0.3.0

func UserCreate(
	ctx context.Context,
	engine *twapi.Engine,
	req UserCreateRequest,
) (*UserCreateResponse, error)

UserCreate creates a new user using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startUserServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	userRequest := projects.NewUserCreateRequest("John", "Doe", "johndoe@example.com")

	userResponse, err := projects.UserCreate(ctx, engine, userRequest)
	if err != nil {
		fmt.Printf("failed to create user: %s", err)
	} else {
		fmt.Printf("created user with identifier %d\n", userResponse.ID)
	}

}

func startUserServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /people", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/me", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"people":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

created user with identifier 12345

func (*UserCreateResponse) HandleHTTPResponse added in v0.3.0

func (u *UserCreateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the UserCreateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type UserDeleteRequest added in v0.3.0

type UserDeleteRequest struct {
	// Path contains the path parameters for the request.
	Path UserDeleteRequestPath
}

UserDeleteRequest represents the request body for deleting a user.

https://apidocs.teamwork.com/docs/teamwork/v1/people/delete-people-id-json

func NewUserDeleteRequest added in v0.3.0

func NewUserDeleteRequest(userID int64) UserDeleteRequest

NewUserDeleteRequest creates a new UserDeleteRequest with the provided user ID.

func (UserDeleteRequest) HTTPRequest added in v0.3.0

func (u UserDeleteRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the UserDeleteRequest.

type UserDeleteRequestPath added in v0.3.0

type UserDeleteRequestPath struct {
	// ID is the unique identifier of the user to be deleted.
	ID int64
}

UserDeleteRequestPath contains the path parameters for deleting a user.

type UserDeleteResponse added in v0.3.0

type UserDeleteResponse struct{}

UserDeleteResponse represents the response body for deleting a user.

https://apidocs.teamwork.com/docs/teamwork/v1/people/delete-people-id-json

func UserDelete added in v0.3.0

func UserDelete(
	ctx context.Context,
	engine *twapi.Engine,
	req UserDeleteRequest,
) (*UserDeleteResponse, error)

UserDelete deletes a user using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startUserServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	_, err = projects.UserDelete(ctx, engine, projects.NewUserDeleteRequest(12345))
	if err != nil {
		fmt.Printf("failed to delete user: %s", err)
	} else {
		fmt.Println("user deleted!")
	}

}

func startUserServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /people", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/me", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"people":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

user deleted!

func (*UserDeleteResponse) HandleHTTPResponse added in v0.3.0

func (u *UserDeleteResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the UserDeleteResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type UserGetMeRequest added in v0.3.0

type UserGetMeRequest struct {
}

UserGetMeRequest represents the request body for loading the logged user.

https://apidocs.teamwork.com/docs/teamwork/v3/people/get-projects-api-v3-me-json

func NewUserGetMeRequest added in v0.3.0

func NewUserGetMeRequest() UserGetMeRequest

NewUserGetMeRequest creates a new UserGetMeRequest.

func (UserGetMeRequest) HTTPRequest added in v0.3.0

func (u UserGetMeRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the UserGetMeRequest.

type UserGetMeResponse added in v0.3.0

type UserGetMeResponse struct {
	User UserMe `json:"person"`
}

UserGetMeResponse contains all the information related to the logged user.

https://apidocs.teamwork.com/docs/teamwork/v3/people/get-projects-api-v3-me-json

func UserGetMe added in v0.3.0

func UserGetMe(
	ctx context.Context,
	engine *twapi.Engine,
	req UserGetMeRequest,
) (*UserGetMeResponse, error)

UserGetMe retrieves the logged user using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startUserServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	userResponse, err := projects.UserGetMe(ctx, engine, projects.NewUserGetMeRequest())
	if err != nil {
		fmt.Printf("failed to retrieve logged user: %s", err)
	} else {
		fmt.Printf("retrieved logged user with identifier %d\n", userResponse.User.ID)
	}

}

func startUserServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /people", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/me", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"people":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved logged user with identifier 12345

func (*UserGetMeResponse) HandleHTTPResponse added in v0.3.0

func (u *UserGetMeResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the UserGetMeResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type UserGetRequest added in v0.3.0

type UserGetRequest struct {
	// Path contains the path parameters for the request.
	Path UserGetRequestPath
}

UserGetRequest represents the request body for loading a single user.

https://apidocs.teamwork.com/docs/teamwork/v3/person/get-projects-api-v3-people-person-id-json

func NewUserGetRequest added in v0.3.0

func NewUserGetRequest(userID int64) UserGetRequest

NewUserGetRequest creates a new UserGetRequest with the provided user ID. The ID is required to load a user.

func (UserGetRequest) HTTPRequest added in v0.3.0

func (u UserGetRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the UserGetRequest.

type UserGetRequestPath added in v0.3.0

type UserGetRequestPath struct {
	// ID is the unique identifier of the user to be retrieved.
	ID int64 `json:"id"`
}

UserGetRequestPath contains the path parameters for loading a single user.

type UserGetResponse added in v0.3.0

type UserGetResponse struct {
	User User `json:"person"`
}

UserGetResponse contains all the information related to a user.

https://apidocs.teamwork.com/docs/teamwork/v3/person/get-projects-api-v3-people-person-id-json

func UserGet added in v0.3.0

func UserGet(
	ctx context.Context,
	engine *twapi.Engine,
	req UserGetRequest,
) (*UserGetResponse, error)

UserGet retrieves a single user using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startUserServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	userResponse, err := projects.UserGet(ctx, engine, projects.NewUserGetRequest(12345))
	if err != nil {
		fmt.Printf("failed to retrieve user: %s", err)
	} else {
		fmt.Printf("retrieved user with identifier %d\n", userResponse.User.ID)
	}

}

func startUserServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /people", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/me", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"people":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved user with identifier 12345

func (*UserGetResponse) HandleHTTPResponse added in v0.3.0

func (u *UserGetResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the UserGetResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type UserGroups added in v0.2.0

type UserGroups struct {
	UserIDs    []int64 `json:"userIds"`
	CompanyIDs []int64 `json:"companyIds"`
	TeamIDs    []int64 `json:"teamIds"`
}

UserGroups represents a collection of users, companies, and teams.

type UserListRequest added in v0.3.0

type UserListRequest struct {
	// Path contains the path parameters for the request.
	Path UserListRequestPath

	// Filters contains the filters for loading multiple users.
	Filters UserListRequestFilters
}

UserListRequest represents the request body for loading multiple users.

https://apidocs.teamwork.com/docs/teamwork/v3/people/get-projects-api-v3-people-json https://apidocs.teamwork.com/docs/teamwork/v3/people/get-projects-api-v3-projects-project-id-people-json

func NewUserListRequest added in v0.3.0

func NewUserListRequest() UserListRequest

NewUserListRequest creates a new UserListRequest with default values.

func (UserListRequest) HTTPRequest added in v0.3.0

func (u UserListRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the UserListRequest.

type UserListRequestFilters added in v0.3.0

type UserListRequestFilters struct {
	// SearchTerm is an optional search term to filter users by name or e-mail.
	SearchTerm string

	// Type is an optional filter to load only users of a specific type. Possible
	// values are "account", "collaborator" or "contact".
	Type string

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of users to retrieve per page. Defaults to 50.
	PageSize int64
}

UserListRequestFilters contains the filters for loading multiple users.

type UserListRequestPath added in v0.3.0

type UserListRequestPath struct {
	// ProjectID is the unique identifier of the project whose members are to be
	// retrieved.
	ProjectID int64
}

UserListRequestPath contains the path parameters for loading multiple users.

type UserListResponse added in v0.3.0

type UserListResponse struct {
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`
	Users []User `json:"people"`
	// contains filtered or unexported fields
}

UserListResponse contains information by multiple users matching the request filters.

https://apidocs.teamwork.com/docs/teamwork/v3/people/get-projects-api-v3-people-json https://apidocs.teamwork.com/docs/teamwork/v3/people/get-projects-api-v3-projects-project-id-people-json

func UserList added in v0.3.0

func UserList(
	ctx context.Context,
	engine *twapi.Engine,
	req UserListRequest,
) (*UserListResponse, error)

UserList retrieves multiple users using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startUserServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	usersRequest := projects.NewUserListRequest()
	usersRequest.Filters.SearchTerm = "John"

	usersResponse, err := projects.UserList(ctx, engine, usersRequest)
	if err != nil {
		fmt.Printf("failed to list users: %s", err)
	} else {
		for _, user := range usersResponse.Users {
			fmt.Printf("retrieved user with identifier %d\n", user.ID)
		}
	}

}

func startUserServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /people", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/me", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"people":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved user with identifier 12345
retrieved user with identifier 12346

func (*UserListResponse) HandleHTTPResponse added in v0.3.0

func (u *UserListResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the UserListResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

func (*UserListResponse) Iterate added in v0.3.0

func (u *UserListResponse) Iterate() *UserListRequest

Iterate returns the request set to the next page, if available. If there are no more pages, a nil request is returned.

func (*UserListResponse) SetRequest added in v0.3.0

func (u *UserListResponse) SetRequest(req UserListRequest)

SetRequest sets the request used to load this response. This is used for pagination purposes, so the Iterate method can return the next page.

type UserMe added in v1.1.0

type UserMe struct {
	User

	// SiteOwner indicates whether the user is the account owner. The site owner
	// has full control over the account and its settings.
	SiteOwner bool `json:"siteOwner"`

	// InOwnerCompany indicates whether the user is in the owner company. Users in
	// owner companies typically have different permissions.
	InOwnerCompany bool `json:"inOwnerCompany"`
}

UserMe represents an special type for the logged user.

type UserProjectRate added in v1.5.0

type UserProjectRate struct {
	// Project is the relationship to the project.
	Project twapi.Relationship `json:"project"`

	// UserRate is the monetary amount in the smallest currency unit
	// (e.g., cents). For example, €10.00 is represented as 1000.
	UserRate int64 `json:"userRate"`
}

UserProjectRate represents a user's project rate (used in list responses).

type UserRateHistory added in v1.5.0

type UserRateHistory struct {
	// Rate is the rate amount as a monetary amount in the smallest currency
	// unit (e.g., cents).
	Rate int64 `json:"rate"`

	// FromDate is the date from which this rate was effective.
	FromDate *time.Time `json:"fromDate"`

	// ToDate is the date until which this rate was effective.
	ToDate *time.Time `json:"toDate,omitempty"`

	// CreatedAt is the date when this rate was created.
	CreatedAt *time.Time `json:"createdAt"`

	// UpdatedAt is the date when this rate was last updated.
	UpdatedAt *time.Time `json:"updatedAt"`
}

UserRateHistory represents a historical rate entry for a user.

type UserUpdateRequest added in v0.3.0

type UserUpdateRequest struct {
	// Path contains the path parameters for the request.
	Path UserUpdateRequestPath `json:"-"`

	// FirstName is the first name of the user.
	FirstName *string `json:"first-name,omitempty"`

	// LastName is the last name of the user.
	LastName *string `json:"last-name,omitempty"`

	// Title is the title of the user (e.g. "Senior Developer").
	Title *string `json:"title,omitempty"`

	// Email is the email address of the user.
	Email *string `json:"email-address,omitempty"`

	// Admin indicates whether the user is an administrator. By default it is
	// false.
	Admin *bool `json:"administrator,omitempty"`

	// Type is the type of user. Possible values are "account", "collaborator" or
	// "contact". By default it is "account".
	Type *string `json:"user-type,omitempty"`

	// Company is the client/company the user belongs to. By default is the same
	// from the logged user creating the new user.
	CompanyID *int64 `json:"company-id,omitempty"`
}

UserUpdateRequest represents the request body for updating a user. Besides the identifier, all other fields are optional. When a field is not provided, it will not be modified.

https://apidocs.teamwork.com/docs/teamwork/v1/people/put-people-id-json

func NewUserUpdateRequest added in v0.3.0

func NewUserUpdateRequest(userID int64) UserUpdateRequest

NewUserUpdateRequest creates a new UserUpdateRequest with the provided user ID. The ID is required to update a user.

func (UserUpdateRequest) HTTPRequest added in v0.3.0

func (u UserUpdateRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the UserUpdateRequest.

type UserUpdateRequestPath added in v0.3.0

type UserUpdateRequestPath struct {
	// ID is the unique identifier of the user to be updated.
	ID int64
}

UserUpdateRequestPath contains the path parameters for updating a user.

type UserUpdateResponse added in v0.3.0

type UserUpdateResponse struct{}

UserUpdateResponse represents the response body for updating a user.

https://apidocs.teamwork.com/docs/teamwork/v1/people/put-people-id-json

func UserUpdate added in v0.3.0

func UserUpdate(
	ctx context.Context,
	engine *twapi.Engine,
	req UserUpdateRequest,
) (*UserUpdateResponse, error)

UserUpdate updates a user using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startUserServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	userRequest := projects.NewUserUpdateRequest(12345)
	userRequest.Title = twapi.Ptr("Software Engineer")

	_, err = projects.UserUpdate(ctx, engine, userRequest)
	if err != nil {
		fmt.Printf("failed to update user: %s", err)
	} else {
		fmt.Println("user updated!")
	}

}

func startUserServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /people", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK","id":"12345"}`)
	})
	mux.HandleFunc("PUT /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
			return
		}
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("DELETE /people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"STATUS":"OK"}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people/{id}", func(w http.ResponseWriter, r *http.Request) {
		if r.PathValue("id") != "12345" {
			http.Error(w, "Not Found", http.StatusNotFound)
			return
		}
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/me", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"person":{"id":12345}}`)
	})
	mux.HandleFunc("GET /projects/api/v3/people", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"people":[{"id":12345},{"id":12346}]}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

user updated!

func (*UserUpdateResponse) HandleHTTPResponse added in v0.3.0

func (u *UserUpdateResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the UserUpdateResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type Workload added in v1.3.0

type Workload struct {
	// Users is a list of users in the workload response.
	Users []WorkloadUser `json:"users"`
}

Workload is a visual representation of how tasks are distributed across team members, helping you understand who is overloaded, who has capacity, and how work is balanced within a project or across multiple projects. It takes into account assigned tasks, due dates, estimated time, and working hours to give managers and teams a clear picture of availability and resource allocation. By providing this insight, workload makes it easier to plan effectively, prevent burnout, and ensure that deadlines are met without placing too much pressure on any single person.

More information can be found at: https://support.teamwork.com/projects/workload/using-the-workload-planner

type WorkloadGetRequestSideload added in v1.3.0

type WorkloadGetRequestSideload string

WorkloadGetRequestSideload represents the related objects that can be included in the workload response to provide additional context.

const (
	WorkloadGetRequestSideloadUsers              WorkloadGetRequestSideload = "users"
	WorkloadGetRequestSideloadWorkingHours       WorkloadGetRequestSideload = "workingHours"
	WorkloadGetRequestSideloadWorkingHourEntries WorkloadGetRequestSideload = "workingHourEntries"
)

List of valid sideload options for the workload response.

type WorkloadRequest added in v1.3.0

type WorkloadRequest struct {
	// Filters contains the filters for loading the workload.
	Filters WorkloadRequestFilters
}

WorkloadRequest represents the request body for loading workload data.

https://apidocs.teamwork.com/docs/teamwork/v3/workload/get-projects-api-v3-workload-json

func NewWorkloadRequest added in v1.3.0

func NewWorkloadRequest(startDate, endDate twapi.Date) WorkloadRequest

NewWorkloadRequest creates a new WorkloadRequest with the provided start and end dates. These dates are required to load a workload.

func (WorkloadRequest) HTTPRequest added in v1.3.0

func (w WorkloadRequest) HTTPRequest(ctx context.Context, server string) (*http.Request, error)

HTTPRequest creates an HTTP request for the WorkloadRequest.

type WorkloadRequestFilters added in v1.3.0

type WorkloadRequestFilters struct {
	// StartDate is the start date for the workload. This is a required field.
	StartDate twapi.Date

	// EndDate is the end date for the workload. This is a required field.
	EndDate twapi.Date

	// UserIDs is a list of user IDs to filter the workload by.
	UserIDs []int64

	// UserCompanyIDs is a list of users' client/company IDs to filter the
	// workload by.
	UserCompanyIDs []int64

	// UserTeamIDs is a list of users' team IDs to filter the workload by.
	UserTeamIDs []int64

	// ProjectIDs is a list of project IDs to filter the workload by.
	ProjectIDs []int64

	// Include is a list of related objects to include in the response.
	Include []WorkloadGetRequestSideload

	// Page is the page number to retrieve. Defaults to 1.
	Page int64

	// PageSize is the number of users to retrieve per page. Defaults to 50.
	PageSize int64
}

WorkloadRequestFilters contains the filters for loading the workload.

type WorkloadResponse added in v1.3.0

type WorkloadResponse struct {
	// Meta contains metadata about the response, including pagination details.
	Meta struct {
		Page struct {
			HasMore bool `json:"hasMore"`
		} `json:"page"`
	} `json:"meta"`

	// Workload contains the workload data.
	Workload Workload `json:"workload"`

	// Included contains related objects included in the response.
	Included struct {
		// Users is a map of user IDs to User objects.
		//
		// The key is the string representation of the user ID.
		Users map[string]User `json:"users,omitempty"`

		// WorkingHours is a map of working hour IDs to their corresponding
		// working hour information.
		//
		// The key is the string representation of the working hour ID.
		WorkingHours map[string]struct {
			// ID is the unique identifier for the working hours entry.
			ID int64 `json:"id"`

			// Object is a relationship object that links to the user associated
			// with these working hours.
			//
			// This field helps identify which user's working hours are being
			// represented.
			Object twapi.Relationship `json:"object"`

			// Entries is a list of relationships to the working hour entries
			// associated with these working hours.
			//
			// Each entry in this list represents a specific day's working hours
			// for the user, including the number of task hours assigned for
			// that day.
			Entries []twapi.Relationship `json:"entries"`
		} `json:"workingHours,omitempty"`

		// WorkingHoursEntries is a map of working hour entry IDs to their
		// corresponding working hour entry information.
		//
		// The key is the string representation of the working hour entry ID.
		//
		// Note: Each working hour entry represents a specific day's working hours
		// for a user, including the number of task hours assigned for that day.
		// The "workingHour" field links back to the parent working hours object.
		// The "weekday" field indicates the day of the week (e.g., "Monday",
		// "Tuesday") for which these working hours apply.
		//
		// This structure allows you to see not only the overall working hours
		// for a user but also how those hours are distributed across different
		// days of the week, along with the specific task hours assigned for each
		// day.
		WorkingHoursEntries map[string]struct {
			// ID is the unique identifier for the working hour entry.
			ID int64 `json:"id"`

			// WorkingHour is a relationship object that links back to the
			// parent working hours object.
			//
			// This field helps identify which working hours entry this
			// particular day's working hours belong to.
			WorkingHour twapi.Relationship `json:"workingHour"`

			// Weekday indicates the day of the week (e.g., "Monday", "Tuesday") for
			// which these working hours apply.
			Weekday string `json:"weekday"`

			// TaskHours represents the number of task hours assigned for this
			// particular day.
			TaskHours float64 `json:"taskHours"`
		} `json:"workingHourEntries,omitempty"`
	} `json:"included"`
}

WorkloadResponse contains all the information related to a workload.

https://apidocs.teamwork.com/docs/teamwork/v3/workload/get-projects-api-v3-workload-json

func WorkloadGet added in v1.3.0

func WorkloadGet(
	ctx context.Context,
	engine *twapi.Engine,
	req WorkloadRequest,
) (*WorkloadResponse, error)

WorkloadGet retrieves a workload using the provided request and returns the response.

Example
package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	twapi "github.com/teamwork/twapi-go-sdk"
	"github.com/teamwork/twapi-go-sdk/projects"
	"github.com/teamwork/twapi-go-sdk/session"
)

func main() {
	address, stop, err := startWorkloadServer() // mock server for demonstration purposes
	if err != nil {
		fmt.Printf("failed to start server: %s", err)
		return
	}
	defer stop()

	ctx := context.Background()
	engine := twapi.NewEngine(session.NewBearerToken("your_token", fmt.Sprintf("http://%s", address)))

	workloadRequest := projects.NewWorkloadRequest(
		twapi.Date(time.Now().AddDate(0, 0, -7)),
		twapi.Date(time.Now()),
	)

	workloadResponse, err := projects.WorkloadGet(ctx, engine, workloadRequest)
	if err != nil {
		fmt.Printf("failed to get workload: %s", err)
	} else {
		for _, user := range workloadResponse.Workload.Users {
			fmt.Printf("retrieved user with identifier %d\n", user.ID)
		}
	}

}

func startWorkloadServer() (string, func(), error) {
	ln, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return "", nil, fmt.Errorf("failed to start server: %w", err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /projects/api/v3/workload", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Header().Set("Content-Type", "application/json")
		_, _ = fmt.Fprintln(w, `{"workload":{"users":[{"userId":12345},{"userId":12346}]}}`)
	})

	server := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != "Bearer your_token" {
				http.Error(w, "Unauthorized", http.StatusUnauthorized)
				return
			}
			r.URL.Path = strings.TrimSuffix(r.URL.Path, ".json")
			mux.ServeHTTP(w, r)
		}),
	}

	stop := make(chan struct{})
	go func() {
		_ = server.Serve(ln)
	}()
	go func() {
		<-stop
		_ = server.Shutdown(context.Background())
	}()

	return ln.Addr().String(), func() {
		close(stop)
	}, nil
}
Output:

retrieved user with identifier 12345
retrieved user with identifier 12346

func (*WorkloadResponse) HandleHTTPResponse added in v1.3.0

func (w *WorkloadResponse) HandleHTTPResponse(resp *http.Response) error

HandleHTTPResponse handles the HTTP response for the WorkloadResponse. If some unexpected HTTP status code is returned by the API, a twapi.HTTPError is returned.

type WorkloadUser added in v1.3.0

type WorkloadUser struct {
	// ID is the unique identifier for the user.
	ID int64 `json:"userId"`

	// Dates is a map of dates to their corresponding workload information for the
	// user.
	Dates map[twapi.Date]WorkloadUserDate `json:"dates"`
}

WorkloadUser represents a user in the workload response. It contains the user's ID and a map of dates with their corresponding workload information.

type WorkloadUserDate added in v1.3.0

type WorkloadUserDate struct {
	// Capacity is the user's capacity percentage for the day.
	Capacity float64 `json:"capacity"`

	// CapacityMinutes is the user's capacity in minutes for the day.
	CapacityMinutes int64 `json:"capacityMinutes"`

	// UnavailableDay indicates whether the user is unavailable on that date.
	UnavailableDay bool `json:"unavailableDay"`
}

WorkloadUserDate represents the workload information for a specific user on a specific date. It includes the user's capacity, capacity in minutes, and whether the user is unavailable on that date.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL