Add structs and helper methods

This commit is contained in:
kolaente 2020-12-17 23:38:37 +01:00
parent a45e190092
commit 47b404ffdd
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
3 changed files with 174 additions and 21 deletions

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}