Categorygithub.com/stvoidit/megaplan/v3
package
3.3.0
Repository: https://github.com/stvoidit/megaplan.git
Documentation: pkg.go.dev

# README

Пример использования

Иниализация клиента + опция включения заголовка "Accept-Encoding":"gzip", ответ будет возвращаться сжатым, реализована декомпрессия тела ответа внутри вызова.

    import (
        "github.com/stvoidit/megaplan/v3"
    )
    const (
        domain = `https://yourdomain.ru`
        token  = `token`
    )
    func main() {
        client := megaplan.NewClient(domain, token, megaplan.OptionEnableAcceptEncodingGzip(true))
    }

Пример создания задачами

https://demo.megaplan.ru/api/v3/docs#entityTask Для удобства составления json для тела запроса есть функция megaplan.BuildQueryParams. Её единственное назначение - собрать параметры в правильном формате. Некоторые сущности требуют специального формата (например Дата и Время, Интервал, Дата, Сдвиг дат), то функция megaplan.BuildQueryParams корректно сформирует структуру этих сущностей.

func CreateTask(c *megaplan.ClientV3) {
    const endpoint = "/api/v3/task"
    var qp = megaplan.BuildQueryParams(
        megaplan.SetRawField("contentType", "Task"),
        megaplan.SetRawField("isUrgent", false),
        megaplan.SetRawField("isTemplate", false),
        megaplan.SetRawField("name", "library test"),
        megaplan.SetRawField("subject", "subject library test"),
        megaplan.SetRawField("statement", "statement library test"),
        megaplan.SetEntityField("owner", "Employee", 1000129),
        megaplan.SetEntityField("responsible", "Employee", 1000129),
        megaplan.SetEntityField("deadline", "DateOnly", time.Now().Add(time.Hour*72)),
        megaplan.SetEntityField("plannedWork", "DateInterval", time.Hour*13),
    )
    r, err := qp.ToReader()
    if err != nil {
        panic(err)
    }
    rc, err := c.DoRequestAPI(http.MethodPost, endpoint, nil, r)
    if err != nil {
        panic(err)
    }
    defer rc.Close()
    os.Stdout.ReadFrom(rc)
}

Пример запроса с параметрами URL

Так как параметры запроса на api "Мегаплан" передаются в нетипичном формате ("*?json=?"), то необходимо их экранировать через url.QueryEscape. Для удобства составления этих параметров можно так же использовать тип megaplan.QueryParams.

    func testGetWithFilters(c *megaplan.ClientV3) {
        const endpoint = "/api/v3/task"
        var requestedFiled = [...]string{
            "id",
            "name",
            "status",
            "deadline",
            "actualWork",
            "responsible",
            "timeCreated",
        }
        // параметры верхнего уровня
        var searchParams = megaplan.BuildQueryParams(
            megaplan.SetRawField("limit", 50),
            megaplan.SetRawField("onlyRequestedFields", true),
            megaplan.SetRawField("fields", requestedFiled),
        )

        // пример составления параметров без megaplan.BuildQueryParams (т.к. есть большая вложенность параметров)
        // megaplan.QueryParams - это просто алиас к типа megaplan.QueryParams, но с доп. методами,
        // поэтому для корректного составления json в параметрах URL необходимо передавать в DoRequestAPI именно megaplan.QueryParams
        now := time.Now()
        from := time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, time.Local)
        var filterParams = map[string]interface{}{
            "contentType": "TaskFilter",
            "id":          nil,
            "config": megaplan.QueryParams{
                "contentType": "FilterConfig",
                "termGroup": megaplan.QueryParams{
                    "contentType": "FilterTermGroup",
                    "join":        "and",
                    "terms": [...]megaplan.QueryParams{
                        {
                            "contentType": "FilterTermEnum",
                            "field":       "status",
                            "comparison":  "equals",
                            "value":       [...]string{"filter_any"},
                        },
                        {
                            "comparison":  "equals",
                            "field":       "responsible",
                            "contentType": "FilterTermRef",
                            "value": [...]megaplan.QueryParams{
                                {"id": 1000129, "contentType": "Employee"},
                            },
                        },
                        {
                            "comparison":  "equals",
                            "field":       "statusChangeTime",
                            "contentType": "FilterTermDate",
                            "value": megaplan.QueryParams{
                                "contentType": "IntervalDates",
                                "from":        megaplan.CreateEnity("DateOnly", from),
                                "to": megaplan.QueryParams{
                                    "contentType": "DateOnly",
                                    "year":        now.Year(),
                                    "month":       int(now.Month()) - 1,
                                    "day":         now.Day(),
                                },
                            },
                        },
                    },
                },
            },
        }
        searchParams["filter"] = filterParams

        {
            // вариант отправки через DoRequestAPI, внутри формируется корректный http.Request, если http.Response был сжат, то будет разархивирован
            rc, err := c.DoRequestAPI(http.MethodGet, endpoint, searchParams, nil)
            if err != nil {
                panic(err)
            }
            defer rc.Close()
            os.Stdout.ReadFrom(rc)
        }
        {
            // пример с использование Do, внучную собирается http.Request (добавляются необходимые заголовки, http.Response никак не обрабатывается перед возвратом)
            c.SetOptions(megaplan.OptionEnableAcceptEncodingGzip(false))
            request, err := http.NewRequest(http.MethodGet, domain, nil)
            if err != nil {
                panic(err)
            }
            request.URL.Path = endpoint
            request.URL.RawQuery = searchParams.QueryEscape() // параметры будут правильно экранированы
            response, err := c.Do(request)
            if err != nil {
                panic(err)
            }
            defer response.Body.Close()
            os.Stdout.ReadFrom(response.Body)
        }
    }

Чтение ответа

С появлением дженериков улучшена функция для чтения ответов от api. Внутри функции есть проверка на Content-Type, если это не json, то в 99% это html с ошибкой. В этом случае функция вернет ошибку с текстом в виде html строки.

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/stvoidit/megaplan/v3"
)

const (
	DOMAIN      = `https://example.ru`
	TOKEN       = `TOKEN`
	ACCOUNTINFO = `/api/v3/accountInfo`
)

type AccountInfo struct {
	ID                string         `json:"id"`
	ContentType       string         `json:"contentType"`
	PermanentHost     string         `json:"permanentHost"`
	AccountName       string         `json:"accountName"`
	BuildVersion      string         `json:"buildVersion"`
	SystemProductName string         `json:"systemProductName"`
	TarifId           string         `json:"tarifId"`
	LicenceEndDate    map[string]any `json:"licenceEndDate"`
	MobileEndDate     map[string]any `json:"mobileEndDate"`
	PaidToDate        map[string]any `json:"paidToDate"`
	LicenseExpired    bool           `json:"licenseExpired"`
	MegamailDomain    string         `json:"megamailDomain"`
	DaysRemaining     int            `json:"daysRemaining"`
	TimeCreated       string         `json:"timeCreated"`
}

func (ai AccountInfo) String() string {
	var sb strings.Builder
	e := json.NewEncoder(&sb)
	e.SetIndent("", "  ")
	e.Encode(&ai)
	return sb.String()
}

func main() {
	c := megaplan.NewClient(DOMAIN, TOKEN,
		megaplan.OptionEnableAcceptEncodingGzip(true),
		megaplan.OptionInsecureSkipVerify(true))
	res, err := c.DoRequestAPI(http.MethodGet, ACCOUNTINFO, nil, nil)
	if err != nil {
		panic(err)
	}
    // Вы можете указать типа как "any", если вам нужно стандартное поведение json.Decode - возврат в виде map[string]any
	body, err := megaplan.ParseResponse[AccountInfo](res)
	if err != nil {
		panic(err)
	}
	fmt.Println(body)
}

! Про типы и сущности "мегаплана" !

* не актуально с появлением дженериков

Многие реализации библиотек для API "Мегаплана" пытаются строго типизировать и описать полностью сущности, которыми оперирует "Мегаплан". Однако это подход влечет за собой обязанность этих библиотек поддерживать согласованность с версиями "Мегаплана", а так же каким-то образом поддерживать кастомные варианты полей. Данная библиотека является просто оберткой для использования API v3 и включает минимальное кол-во вспомогательных функций для составления запросов и парсинга ответов.

В силу специфики строения сущностей "Мегаплана" некоторые типы могут некорретно собираться функцией megaplan.BuildQueryParams, поэтому выше даны примере, как можно "дособрать" необходимые объекты.