Add structs and helper methods
This commit is contained in:
parent
a45e190092
commit
47b404ffdd
|
@ -20,6 +20,8 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DownloadFile downloads a file and returns its contents
|
||||
|
@ -38,3 +40,15 @@ func DownloadFile(url string) (buf *bytes.Buffer, err error) {
|
|||
_, err = buf.ReadFrom(resp.Body)
|
||||
return
|
||||
}
|
||||
|
||||
// DoPost makes a form encoded post request
|
||||
func DoPost(url string, form url.Values) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
hc := http.Client{}
|
||||
return hc.Do(req)
|
||||
}
|
||||
|
|
|
@ -17,34 +17,187 @@
|
|||
package microsoft_todo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
)
|
||||
|
||||
const apiScopes = `tasks.read%20tasks.read.shared`
|
||||
|
||||
type Migration struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type apiTokenResponse struct {
|
||||
TokenType string `json:"token_type"`
|
||||
Scope string `json:"scope"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
ExtExpiresIn int `json:"ext_expires_in"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type task struct {
|
||||
ID string `json:"id"`
|
||||
OdataEtag string `json:"@odata.etag"`
|
||||
Importance string `json:"importance"`
|
||||
IsReminderOn bool `json:"isReminderOn"`
|
||||
Status string `json:"status"`
|
||||
Title string `json:"title"`
|
||||
CreatedDateTime time.Time `json:"createdDateTime"`
|
||||
LastModifiedDateTime time.Time `json:"lastModifiedDateTime"`
|
||||
Body struct {
|
||||
Content string `json:"content"`
|
||||
ContentType string `json:"contentType"`
|
||||
} `json:"body"`
|
||||
}
|
||||
|
||||
type tasksResponse struct {
|
||||
OdataContext string `json:"@odata.context"`
|
||||
Value []*task `json:"value"`
|
||||
}
|
||||
|
||||
type list struct {
|
||||
ID string `json:"id"`
|
||||
OdataEtag string `json:"@odata.etag"`
|
||||
DisplayName string `json:"displayName"`
|
||||
IsOwner bool `json:"isOwner"`
|
||||
IsShared bool `json:"isShared"`
|
||||
WellknownListName string `json:"wellknownListName"`
|
||||
tasks []*task `json:"-"` // This field does not exist in the api, we're just using it to return a structure with everything at once
|
||||
}
|
||||
|
||||
type listsResponse struct {
|
||||
OdataContext string `json:"@odata.context"`
|
||||
Value []*list `json:"value"`
|
||||
}
|
||||
|
||||
func (m *Migration) AuthURL() string {
|
||||
return "https://login.microsoftonline.com/common/oauth2/v2.0/authorize" +
|
||||
"?client_id=" + config.MigrationMicrosoftTodoClientID.GetString() +
|
||||
"&response_type=code" +
|
||||
"&redirect_uri=" + config.MigrationMicrosoftTodoRedirectURL.GetString() +
|
||||
"&response_mode=query" +
|
||||
"&scope=tasks.read%20tasks.read.shared"
|
||||
"&scope=" + apiScopes
|
||||
}
|
||||
|
||||
func (m *Migration) Name() string {
|
||||
return "microsoft-todo"
|
||||
}
|
||||
|
||||
func getMicrosoftTodoData(token string) {
|
||||
func getMicrosoftGraphAuthToken(code string) (accessToken string, err error) {
|
||||
|
||||
form := url.Values{
|
||||
"client_id": []string{config.MigrationMicrosoftTodoClientID.GetString()},
|
||||
"client_secret": []string{config.MigrationMicrosoftTodoClientSecret.GetString()},
|
||||
"scope": []string{apiScopes},
|
||||
"code": []string{code},
|
||||
"redirect_uri": []string{config.MigrationMicrosoftTodoRedirectURL.GetString()},
|
||||
"grant_type": []string{"authorization_code"},
|
||||
}
|
||||
resp, err := migration.DoPost("https://login.microsoftonline.com/common/oauth2/v2.0/token", form)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode > 399 {
|
||||
buf := &bytes.Buffer{}
|
||||
_, _ = buf.ReadFrom(resp.Body)
|
||||
return "", fmt.Errorf("got http status %d while trying to get token, error was %s", resp.StatusCode, buf.String())
|
||||
}
|
||||
|
||||
token := &apiTokenResponse{}
|
||||
err = json.NewDecoder(resp.Body).Decode(token)
|
||||
return token.AccessToken, err
|
||||
}
|
||||
|
||||
func getMicrosoftGraphAuthToken(code string) (token string, err error) {
|
||||
func makeAuthenticatedGetRequest(token, urlPart string, v interface{}, urlParams url.Values) error {
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://graph.microsoft.com/v1.0/me/todo/"+urlPart, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.URL.RawQuery = urlParams.Encode()
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 399 {
|
||||
return fmt.Errorf("Microsoft Graph API Error: Status Code: %d, Response was: %s", resp.StatusCode, buf.String())
|
||||
}
|
||||
|
||||
// If the response is an empty json array, we need to exit here, otherwise this breaks the json parser since it
|
||||
// expects a null for an empty slice
|
||||
str := buf.String()
|
||||
if str == "[]" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(buf.Bytes(), v)
|
||||
}
|
||||
|
||||
func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Migration) Migrate(user *user.User) error {
|
||||
panic("implement me")
|
||||
func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.NamespaceWithLists, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Migration) Migrate(user *user.User) (err error) {
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Start Microsoft Todo migration for user %d", user.ID)
|
||||
log.Debugf("[Microsoft Todo Migration] Getting Microsoft Graph api token")
|
||||
|
||||
token, err := getMicrosoftGraphAuthToken(m.Code)
|
||||
if err != nil {
|
||||
log.Debugf("[Microsoft Todo Migration] Error getting auth token: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Got Microsoft Graph api token")
|
||||
log.Debugf("[Microsoft Todo Migration] Retrieving Microsoft Todo data")
|
||||
|
||||
todoData, err := getMicrosoftTodoData(token)
|
||||
if err != nil {
|
||||
log.Debugf("[Microsoft Todo Migration] Error getting Microsoft Todo data: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Got Microsoft Todo data")
|
||||
log.Debugf("[Microsoft Todo Migration] Start converting Microsoft Todo data")
|
||||
|
||||
vikunjaStructure, err := convertMicrosoftTodoData(todoData)
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Done converting Microsoft Todo data")
|
||||
log.Debugf("[Microsoft Todo Migration] Creating new structure")
|
||||
|
||||
err = migration.InsertFromStructure(vikunjaStructure, user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Created new structure")
|
||||
log.Debugf("[Microsoft Todo Migration] Microsoft Todo migration done for user %d", user.ID)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,13 +18,10 @@ package todoist
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
|
@ -229,17 +226,6 @@ func (m *Migration) AuthURL() string {
|
|||
"&state=" + utils.MakeRandomString(32)
|
||||
}
|
||||
|
||||
func doPost(url string, form url.Values) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
hc := http.Client{}
|
||||
return hc.Do(req)
|
||||
}
|
||||
|
||||
func convertTodoistToVikunja(sync *sync) (fullVikunjaHierachie []*models.NamespaceWithLists, err error) {
|
||||
|
||||
newNamespace := &models.NamespaceWithLists{
|
||||
|
@ -451,7 +437,7 @@ func getAccessTokenFromAuthToken(authToken string) (accessToken string, err erro
|
|||
"code": []string{authToken},
|
||||
"redirect_uri": []string{config.MigrationTodoistRedirectURL.GetString()},
|
||||
}
|
||||
resp, err := doPost("https://todoist.com/oauth/access_token", form)
|
||||
resp, err := migration.DoPost("https://todoist.com/oauth/access_token", form)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -503,7 +489,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
|
|||
"sync_token": []string{"*"},
|
||||
"resource_types": []string{"[\"all\"]"},
|
||||
}
|
||||
resp, err := doPost("https://api.todoist.com/sync/v8/sync", form)
|
||||
resp, err := migration.DoPost("https://api.todoist.com/sync/v8/sync", form)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue