feat: 备份账号增加 Onedrive (#1421)

This commit is contained in:
ssongliu 2023-06-23 23:06:13 +08:00 committed by GitHub
parent ae38239b47
commit 3fa4a240f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 644 additions and 60 deletions

View File

@ -8,15 +8,17 @@ type BackupOperate struct {
Bucket string `json:"bucket"`
AccessKey string `json:"accessKey"`
Credential string `json:"credential"`
BackupPath string `json:"backupPath"`
Vars string `json:"vars" validate:"required"`
}
type BackupInfo struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Type string `json:"type"`
Bucket string `json:"bucket"`
Vars string `json:"vars"`
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Type string `json:"type"`
Bucket string `json:"bucket"`
BackupPath string `json:"backupPath"`
Vars string `json:"vars"`
}
type BackupSearch struct {

View File

@ -6,6 +6,7 @@ type BackupAccount struct {
Bucket string `gorm:"type:varchar(256)" json:"bucket"`
AccessKey string `gorm:"type:varchar(256)" json:"accessKey"`
Credential string `gorm:"type:varchar(256)" json:"credential"`
BackupPath string `gorm:"type:varchar(256)" json:"backupPath"`
Vars string `gorm:"type:longText" json:"vars"`
}

View File

@ -4,6 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"strings"
@ -62,6 +65,7 @@ func (u *BackupService) List() ([]dto.BackupInfo, error) {
dtobas = append(dtobas, u.loadByType("MINIO", ops))
dtobas = append(dtobas, u.loadByType("COS", ops))
dtobas = append(dtobas, u.loadByType("KODO", ops))
dtobas = append(dtobas, u.loadByType("OneDrive", ops))
return dtobas, err
}
@ -96,7 +100,6 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return "", err
}
varMap["type"] = backup.Type
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp:
@ -105,8 +108,10 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential
case constant.OneDrive:
varMap["accessToken"] = backup.Credential
}
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
if err != nil {
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
}
@ -134,6 +139,12 @@ func (u *BackupService) Create(backupDto dto.BackupOperate) error {
if err := copier.Copy(&backup, &backupDto); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if backupDto.Type == constant.OneDrive {
if err := u.loadAccessToken(&backup); err != nil {
return err
}
}
if err := backupRepo.Create(&backup); err != nil {
return err
}
@ -145,7 +156,6 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil {
return nil, err
}
varMap["type"] = backupDto.Type
switch backupDto.Type {
case constant.Sftp:
varMap["username"] = backupDto.AccessKey
@ -154,7 +164,7 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
varMap["accessKey"] = backupDto.AccessKey
varMap["secretKey"] = backupDto.Credential
}
client, err := cloud_storage.NewCloudStorageClient(varMap)
client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap)
if err != nil {
return nil, err
}
@ -215,6 +225,15 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
upMap["bucket"] = req.Bucket
upMap["credential"] = req.Credential
upMap["vars"] = req.Vars
backup.Vars = req.Vars
if req.Type == constant.OneDrive {
if err := u.loadAccessToken(&backup); err != nil {
return err
}
upMap["credential"] = backup.Credential
upMap["vars"] = backup.Vars
}
if err := backupRepo.Update(req.ID, upMap); err != nil {
return err
}
@ -251,7 +270,6 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return nil, err
}
varMap["type"] = backup.Type
if backup.Type == "LOCAL" {
return nil, errors.New("not support")
}
@ -263,9 +281,11 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential
case constant.OneDrive:
varMap["accessToken"] = backup.Credential
}
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
if err != nil {
return nil, err
}
@ -286,6 +306,53 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
return dto.BackupInfo{Type: accountType}
}
func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
}
data := url.Values{}
data.Set("client_id", constant.OneDriveClientID)
data.Set("client_secret", constant.OneDriveClientSecret)
data.Set("grant_type", "authorization_code")
data.Set("code", varMap["code"].(string))
data.Set("redirect_uri", constant.OneDriveRedirectURI)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
if err != nil {
return fmt.Errorf("new http post client for access token failed, err: %v", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request for access token failed, err: %v", err)
}
delete(varMap, "code")
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read data from response body failed, err: %v", err)
}
defer resp.Body.Close()
token := map[string]interface{}{}
if err := json.Unmarshal(respBody, &token); err != nil {
return fmt.Errorf("unmarshal data from response body failed, err: %v", err)
}
accessToken, ok := token["refresh_token"].(string)
if !ok {
return errors.New("no such access token in response")
}
itemVars, err := json.Marshal(varMap)
if err != nil {
return fmt.Errorf("json marshal var map failed, err: %v", err)
}
backup.Credential = accessToken
backup.Vars = string(itemVars)
return nil
}
func loadLocalDir() (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {

View File

@ -100,9 +100,9 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
if err != nil {
return err
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
} else {
u.HandleRmExpired(backup.Type, "", &cronjob, nil)
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, nil)
}
}
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))

View File

@ -33,16 +33,16 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
return
}
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script)
u.HandleRmExpired("LOCAL", "", cronjob, nil)
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "curl":
if len(cronjob.URL) == 0 {
return
}
message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL))
u.HandleRmExpired("LOCAL", "", cronjob, nil)
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "ntp":
err = u.handleNtpSync()
u.HandleRmExpired("LOCAL", "", cronjob, nil)
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
case "website":
record.File, err = u.handleBackup(cronjob, record.StartTime)
case "database":
@ -142,19 +142,25 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
if err != nil {
return "", err
}
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
return "", err
}
}
u.HandleRmExpired(backup.Type, localDir, cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, cronjob, client)
if backup.Type == "LOCAL" || cronjob.KeepLocal {
return fmt.Sprintf("%s/%s/%s/%s", localDir, cronjob.Type, cronjob.Name, fileName), nil
return fmt.Sprintf("%s/%s", backupDir, fileName), nil
} else {
return fmt.Sprintf("%s/%s", itemFileDir, fileName), nil
}
return fmt.Sprintf("%s/%s/%s", cronjob.Type, cronjob.Name, fileName), nil
}
}
func (u *CronjobService) HandleRmExpired(backType, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
func (u *CronjobService) HandleRmExpired(backType, backupPath, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
if len(records) > int(cronjob.RetainCopies) {
@ -163,7 +169,7 @@ func (u *CronjobService) HandleRmExpired(backType, localDir string, cronjob *mod
files := strings.Split(records[i].File, ",")
for _, file := range files {
if backType != "LOCAL" {
_, _ = backClient.Delete(strings.ReplaceAll(file, localDir+"/", ""))
_, _ = backClient.Delete(backupPath + "/" + strings.TrimPrefix(file, localDir+"/"))
_ = os.Remove(file)
} else {
_ = os.Remove(file)
@ -270,12 +276,11 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
}
record.DetailName = dbName
record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = itemFileDir
}
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
@ -287,12 +292,22 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
}
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
}
@ -358,7 +373,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
}()
}
wg.Wait()
u.HandleRmExpired("LOCAL", "", cronjob, nil)
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
return strings.Join(filePaths, ","), nil
}
@ -399,10 +414,10 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
}
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
record.FileDir = backupDir
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
record.Source = backup.Type
record.FileDir = strings.ReplaceAll(backupDir, localDir+"/", "")
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
}
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
@ -420,11 +435,21 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
}()
}
if len(backup.BackupPath) != 0 {
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
itemFileDir = itemPath + itemFileDir
}
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
return paths, err
}
}
if backup.Type == "LOCAL" || cronjob.KeepLocal {
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
} else {
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
}
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
return paths, nil
}

View File

@ -7,7 +7,12 @@ const (
S3 = "S3"
OSS = "OSS"
Sftp = "SFTP"
OneDrive = "OneDrive"
MinIo = "MINIO"
Cos = "COS"
Kodo = "KODO"
OneDriveClientID = "5446cfe3-4c79-47a0-ae25-fc645478e2d9"
OneDriveClientSecret = "ITh8Q~0UKJNXAvz6HE~pd3DTnGJOgDEEpnDOJbqY"
OneDriveRedirectURI = "http://localhost/login/authorized"
)

View File

@ -31,6 +31,7 @@ func Init() {
migrations.AddBindAndAllowIPs,
migrations.UpdateCronjobWithSecond,
migrations.UpdateWebsite,
migrations.AddBackupAccountDir,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View File

@ -390,3 +390,13 @@ var UpdateWebsite = &gormigrate.Migration{
return nil
},
}
var AddBackupAccountDir = &gormigrate.Migration{
ID: "20200620-add-backup-dir",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.BackupAccount{}); err != nil {
return err
}
return nil
},
}

View File

@ -0,0 +1,327 @@
package client
import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
"golang.org/x/oauth2"
)
type oneDriveClient struct {
Vars map[string]interface{}
client odsdk.Client
}
func NewOneDriveClient(vars map[string]interface{}) (*oneDriveClient, error) {
token := ""
if _, ok := vars["accessToken"]; ok {
token = vars["accessToken"].(string)
} else {
return nil, constant.ErrInvalidParams
}
ctx := context.Background()
newToken, err := refreshToken(token)
if err != nil {
return nil, err
}
_ = global.DB.Model(&model.Group{}).Where("type = ?", "OneDrive").Updates(map[string]interface{}{"credential": newToken}).Error
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: newToken},
)
tc := oauth2.NewClient(ctx, ts)
client := odsdk.NewClient(tc)
return &oneDriveClient{client: *client}, nil
}
func (onedrive oneDriveClient) ListBuckets() ([]interface{}, error) {
return nil, nil
}
func (onedrive oneDriveClient) Exist(path string) (bool, error) {
path = "/" + strings.TrimPrefix(path, "/")
fileID, err := onedrive.loadIDByPath(path)
if err != nil {
return false, err
}
return len(fileID) != 0, nil
}
func (onedrive oneDriveClient) Delete(path string) (bool, error) {
path = "/" + strings.TrimPrefix(path, "/")
fileID, err := onedrive.loadIDByPath(path)
if err != nil {
return false, err
}
if err := onedrive.client.DriveItems.Delete(context.Background(), "", fileID); err != nil {
return false, err
}
return true, nil
}
func (onedrive oneDriveClient) Upload(src, target string) (bool, error) {
target = "/" + strings.TrimPrefix(target, "/")
if _, err := onedrive.loadIDByPath(path.Dir(target)); err != nil {
if !strings.Contains(err.Error(), "itemNotFound") {
return false, err
}
if err := onedrive.createFolder(path.Dir(target)); err != nil {
return false, fmt.Errorf("create dir before upload failed, err: %v", err)
}
}
ctx := context.Background()
file, err := os.Open(src)
if err != nil {
return false, err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return false, err
}
if fileInfo.IsDir() {
return false, errors.New("Only file is allowed to be uploaded here.")
}
fileName := fileInfo.Name()
fileSize := fileInfo.Size()
folderID, err := onedrive.loadIDByPath(path.Dir(target))
if err != nil {
return false, err
}
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), fileName)
sessionCreationRequestInside := NewUploadSessionCreationRequest{
ConflictBehavior: "rename",
}
sessionCreationRequest := struct {
Item NewUploadSessionCreationRequest `json:"item"`
DeferCommit bool `json:"deferCommit"`
}{sessionCreationRequestInside, false}
sessionCreationReq, err := onedrive.client.NewRequest("POST", apiURL, sessionCreationRequest)
if err != nil {
return false, err
}
var sessionCreationResp *NewUploadSessionCreationResponse
err = onedrive.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp)
if err != nil {
return false, fmt.Errorf("session creation failed %w", err)
}
fileSessionUploadUrl := sessionCreationResp.UploadURL
sizePerSplit := int64(3200 * 1024)
buffer := make([]byte, 3200*1024)
splitCount := fileSize / sizePerSplit
if fileSize%sizePerSplit != 0 {
splitCount += 1
}
bfReader := bufio.NewReader(file)
var fileUploadResp *UploadSessionUploadResponse
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
length, err := bfReader.Read(buffer)
if err != nil {
return false, err
}
if int64(length) < sizePerSplit {
bufferLast := buffer[:length]
buffer = bufferLast
}
sessionFileUploadReq, err := onedrive.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
if err != nil {
return false, err
}
if err := onedrive.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
return false, err
}
}
if fileUploadResp.Id == "" {
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
}
return true, nil
}
func (onedrive oneDriveClient) Download(src, target string) (bool, error) {
src = "/" + strings.TrimPrefix(src, "/")
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/root:%s", src), nil)
if err != nil {
return false, fmt.Errorf("new request for file id failed, err: %v", err)
}
var driveItem *odsdk.DriveItem
if err := onedrive.client.Do(context.Background(), req, false, &driveItem); err != nil {
return false, fmt.Errorf("do request for file id failed, err: %v", err)
}
resp, err := http.Get(driveItem.DownloadURL)
if err != nil {
return false, err
}
defer resp.Body.Close()
out, err := os.Create(target)
if err != nil {
return false, err
}
defer out.Close()
buffer := make([]byte, 2*1024*1024)
_, err = io.CopyBuffer(out, resp.Body, buffer)
if err != nil {
return false, err
}
return true, nil
}
func (onedrive *oneDriveClient) ListObjects(prefix string) ([]interface{}, error) {
prefix = "/" + strings.TrimPrefix(prefix, "/")
folderID, err := onedrive.loadIDByPath(prefix)
if err != nil {
return nil, err
}
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/items/%s/children", folderID), nil)
if err != nil {
return nil, fmt.Errorf("new request for delete failed, err: %v", err)
}
var driveItems *odsdk.OneDriveDriveItemsResponse
if err := onedrive.client.Do(context.Background(), req, false, &driveItems); err != nil {
return nil, fmt.Errorf("do request for delete failed, err: %v", err)
}
for _, item := range driveItems.DriveItems {
return nil, fmt.Errorf("id: %v, name: %s \n", item.Id, item.Name)
}
var itemList []interface{}
for _, item := range driveItems.DriveItems {
itemList = append(itemList, item.Name)
}
return itemList, nil
}
func (onedrive *oneDriveClient) loadIDByPath(path string) (string, error) {
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/root:%s", path), nil)
if err != nil {
return "", fmt.Errorf("new request for file id failed, err: %v", err)
}
var driveItem *odsdk.DriveItem
if err := onedrive.client.Do(context.Background(), req, false, &driveItem); err != nil {
return "", fmt.Errorf("do request for file id failed, err: %v", err)
}
return driveItem.Id, nil
}
func refreshToken(oldToken string) (string, error) {
data := url.Values{}
data.Set("client_id", constant.OneDriveClientID)
data.Set("client_secret", constant.OneDriveClientSecret)
data.Set("grant_type", "refresh_token")
data.Set("refresh_token", oldToken)
data.Set("redirect_uri", constant.OneDriveRedirectURI)
client := &http.Client{}
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
if err != nil {
return "", fmt.Errorf("new http post client for access token failed, err: %v", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("request for access token failed, err: %v", err)
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("read data from response body failed, err: %v", err)
}
defer resp.Body.Close()
tokenMap := map[string]interface{}{}
if err := json.Unmarshal(respBody, &tokenMap); err != nil {
return "", fmt.Errorf("unmarshal data from response body failed, err: %v", err)
}
accessToken, ok := tokenMap["access_token"].(string)
if !ok {
return "", errors.New("no such access token in response")
}
return accessToken, nil
}
func (onedrive *oneDriveClient) createFolder(parent string) error {
if _, err := onedrive.loadIDByPath(path.Dir(parent)); err != nil {
if !strings.Contains(err.Error(), "itemNotFound") {
return err
}
_ = onedrive.createFolder(path.Dir(parent))
}
item2, err := onedrive.loadIDByPath(path.Dir(parent))
if err != nil {
return err
}
if _, err := onedrive.client.DriveItems.CreateNewFolder(context.Background(), "", item2, path.Base(parent)); err != nil {
return err
}
return nil
}
type NewUploadSessionCreationRequest struct {
ConflictBehavior string `json:"@microsoft.graph.conflictBehavior,omitempty"`
}
type NewUploadSessionCreationResponse struct {
UploadURL string `json:"uploadUrl"`
ExpirationDateTime string `json:"expirationDateTime"`
}
type UploadSessionUploadResponse struct {
ExpirationDateTime string `json:"expirationDateTime"`
NextExpectedRanges []string `json:"nextExpectedRanges"`
DriveItem
}
type DriveItem struct {
Name string `json:"name"`
Id string `json:"id"`
DownloadURL string `json:"@microsoft.graph.downloadUrl"`
Description string `json:"description"`
Size int64 `json:"size"`
WebURL string `json:"webUrl"`
}
func (onedrive *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOffset, grandTotalSize int64, byteReader *bytes.Reader) (*http.Request, error) {
apiUrl, err := onedrive.client.BaseURL.Parse(absoluteUrl)
if err != nil {
return nil, err
}
absoluteUrl = apiUrl.String()
contentLength := byteReader.Size()
req, err := http.NewRequest("PUT", absoluteUrl, byteReader)
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
preliminaryLength := grandOffset
preliminaryRange := grandOffset + contentLength - 1
if preliminaryRange >= grandTotalSize {
preliminaryRange = grandTotalSize - 1
preliminaryLength = preliminaryRange - grandOffset + 1
}
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", preliminaryLength, preliminaryRange, grandTotalSize))
return req, err
}

View File

@ -14,24 +14,23 @@ type CloudStorageClient interface {
Download(src, target string) (bool, error)
}
func NewCloudStorageClient(vars map[string]interface{}) (CloudStorageClient, error) {
if vars["type"] == constant.S3 {
func NewCloudStorageClient(backupType string, vars map[string]interface{}) (CloudStorageClient, error) {
switch backupType {
case constant.S3:
return client.NewS3Client(vars)
}
if vars["type"] == constant.OSS {
case constant.OSS:
return client.NewOssClient(vars)
}
if vars["type"] == constant.Sftp {
case constant.Sftp:
return client.NewSftpClient(vars)
}
if vars["type"] == constant.MinIo {
case constant.MinIo:
return client.NewMinIoClient(vars)
}
if vars["type"] == constant.Cos {
case constant.Cos:
return client.NewCosClient(vars)
}
if vars["type"] == constant.Kodo {
case constant.Kodo:
return client.NewKodoClient(vars)
case constant.OneDrive:
return client.NewOneDriveClient(vars)
default:
return nil, constant.ErrNotSupportType
}
return nil, constant.ErrNotSupportType
}

View File

@ -7,6 +7,7 @@ export namespace Backup {
accessKey: string;
bucket: string;
credential: string;
backupPath: string;
vars: string;
varsJson: object;
createdAt: Date;
@ -17,6 +18,7 @@ export namespace Backup {
accessKey: string;
bucket: string;
credential: string;
backupPath: string;
vars: string;
}
export interface RecordDownload {

View File

@ -1,9 +1,9 @@
@font-face {
font-family: "panel"; /* Project id 3575356 */
src: url('iconfont.woff2?t=1684465849452') format('woff2'),
url('iconfont.woff?t=1684465849452') format('woff'),
url('iconfont.ttf?t=1684465849452') format('truetype'),
url('iconfont.svg?t=1684465849452#panel') format('svg');
src: url('iconfont.woff2?t=1687338712846') format('woff2'),
url('iconfont.woff?t=1687338712846') format('woff'),
url('iconfont.ttf?t=1687338712846') format('truetype'),
url('iconfont.svg?t=1687338712846#panel') format('svg');
}
.panel {
@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale;
}
.p-onedrive:before {
content: "\e601";
}
.p-caidan:before {
content: "\e61d";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,13 @@
"css_prefix_text": "p-",
"description": "",
"glyphs": [
{
"icon_id": "13015332",
"name": "onedrive",
"font_class": "onedrive",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "7708032",
"name": "菜单",

View File

@ -14,6 +14,8 @@
/>
<missing-glyph />
<glyph glyph-name="onedrive" unicode="&#58881;" d="M597.333333 320s188.885333 129.152 192.96 128.874667A280.021333 280.021333 0 0 1 285.141333 533.333333h2.858667zM405.674667 499.968A222.869333 222.869333 0 0 1 288 533.333333h-2.858667a224 224 0 0 1-180.885333-352L405.333333 213.333333l188.437334 173.973334zM790.293333 448.874667a180.373333 180.373333 0 0 1-12.288 0.448 181.461333 181.461333 0 0 1-72.149333-14.933334l-112.085333-47.146666L725.333333 234.666667l212.906667-53.696a182.016 182.016 0 0 1-147.946667 267.904zM779.285333 276.181333l-46.464 27.733334-106.538666 63.808-32.512 19.477333-85.738667-36.096-164.266667-69.077333-73.642666-30.933334-165.866667-69.76A223.658667 223.658667 0 0 1 288 85.333333h490.005333a181.994667 181.994667 0 0 1 160.234667 95.637334z" horiz-adv-x="1024" />
<glyph glyph-name="caidan" unicode="&#58909;" d="M896 663.272727h-744.727273a34.909091 34.909091 0 0 0 0 69.818182h744.727273a34.909091 34.909091 0 0 0 0-69.818182zM896 11.636364h-744.727273a34.909091 34.909091 0 0 0 0 69.818181h744.727273a34.909091 34.909091 0 0 0 0-69.818181zM709.818182 337.454545h-558.545455a34.909091 34.909091 0 0 0 0 69.818182h558.545455a34.909091 34.909091 0 0 0 0-69.818182z" horiz-adv-x="1024" />
<glyph glyph-name="yanzhengma1" unicode="&#59204;" d="M544 627h124c17.673 0 32-14.327 32-32 0-17.673-14.327-32-32-32H544v-116a32.5 32.5 0 0 0-0.056-1.916C670.218 429.588 768 321.96299999999997 768 191.5 768 50.39099999999996 653.609-64 512.5-64 371.391-64 257 50.39099999999996 257 191.5c0 130.12 97.27 237.523 223.064 253.46A32.488 32.488 0 0 0 480 447V800c0 17.673 14.327 32 32 32 17.673 0 32-14.327 32-32v-45h192c17.673 0 32-14.327 32-32 0-17.673-14.327-32-32-32H544v-64z m-31.5-627C618.263 0 704 85.73699999999997 704 191.5S618.263 383 512.5 383 321 297.26300000000003 321 191.5 406.737 0 512.5 0z" horiz-adv-x="1024" />

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -989,6 +989,11 @@ const message = {
S3: 'Amazon S3',
MINIO: 'MINIO',
SFTP: 'SFTP',
OneDrive: 'Microsoft OneDrive',
backupDir: 'Backup dir',
isCN: 'Domestic version',
code: 'Auth code',
loadCode: 'Acquire',
COS: 'Tencent COS',
KODO: 'Qiniu Kodo',
domainHelper: 'The accelerated domain name must contain http:// or https://',

View File

@ -967,6 +967,11 @@ const message = {
S3: '亚马逊 S3 云存储',
MINIO: 'MINIO',
SFTP: 'SFTP',
OneDrive: '微软 OneDrive',
backupDir: '备份路径',
isCN: '国内版',
code: '授权码',
loadCode: '获取',
COS: '腾讯云 COS',
KODO: '七牛云 Kodo',
domainHelper: '加速域名必须包含 http:// 或者 https://',

View File

@ -66,6 +66,9 @@
<el-form-item label="Bucket">
{{ s3Data.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ s3Data.backupPath }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, s3Data.createdAt) }}
</el-form-item>
@ -102,6 +105,9 @@
<el-form-item label="Bucket">
{{ ossData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ ossData.backupPath }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, ossData.createdAt) }}
</el-form-item>
@ -139,6 +145,9 @@
<el-form-item label="Bucket">
{{ cosData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ cosData.backupPath }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, cosData.createdAt) }}
</el-form-item>
@ -149,6 +158,47 @@
</el-button>
</el-alert>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div>
<svg-icon style="font-size: 7px" iconName="p-onedrive"></svg-icon>
<span style="font-size: 14px; font-weight: 500">&nbsp;{{ $t('setting.OneDrive') }}</span>
<div style="float: right">
<el-button
round
plain
:disabled="oneDriveData.id === 0"
@click="onOpenDialog('edit', 'SFTP', oneDriveData)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button round :disabled="oneDriveData.id === 0" @click="onDelete(oneDriveData)">
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<el-divider class="devider" />
<div v-if="oneDriveData.id !== 0" style="margin-left: 20px">
<el-form-item :label="$t('setting.backupDir')">
{{ oneDriveData.backupPath }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, oneDriveData.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
<el-button
size="large"
round
plain
type="primary"
@click="onOpenDialog('create', 'OneDrive')"
>
{{ $t('setting.createBackupAccount', [$t('setting.OneDrive')]) }}
</el-button>
</el-alert>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div>
<svg-icon style="font-size: 7px" iconName="p-qiniuyun"></svg-icon>
@ -175,6 +225,9 @@
<el-form-item label="Bucket">
{{ kodoData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ kodoData.backupPath }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, kodoData.createdAt) }}
</el-form-item>
@ -185,8 +238,6 @@
</el-button>
</el-alert>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div>
<svg-icon style="font-size: 7px" iconName="p-minio"></svg-icon>
@ -212,6 +263,9 @@
<el-form-item label="Bucket">
{{ minioData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ minioData.backupPath }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, minioData.createdAt) }}
</el-form-item>
@ -222,6 +276,8 @@
</el-button>
</el-alert>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div>
<svg-icon style="font-size: 7px" iconName="p-SFTP"></svg-icon>
@ -251,6 +307,9 @@
<el-form-item :label="$t('setting.path')">
{{ sftpData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ sftpData.backupPath }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, sftpData.createdAt) }}
</el-form-item>
@ -284,6 +343,7 @@ const localData = ref<Backup.BackupInfo>({
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
dir: '',
@ -296,6 +356,7 @@ const ossData = ref<Backup.BackupInfo>({
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
region: '',
@ -309,6 +370,7 @@ const minioData = ref<Backup.BackupInfo>({
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
region: '',
@ -322,6 +384,7 @@ const sftpData = ref<Backup.BackupInfo>({
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
address: '',
@ -329,12 +392,26 @@ const sftpData = ref<Backup.BackupInfo>({
},
createdAt: new Date(),
});
const oneDriveData = ref<Backup.BackupInfo>({
id: 0,
type: 'OneDrive',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
redirectURI: '',
},
createdAt: new Date(),
});
const s3Data = ref<Backup.BackupInfo>({
id: 0,
type: 'S3',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
region: '',
@ -348,6 +425,7 @@ const cosData = ref<Backup.BackupInfo>({
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
region: '',
@ -360,6 +438,7 @@ const kodoData = ref<Backup.BackupInfo>({
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
domain: '',
@ -396,6 +475,9 @@ const search = async () => {
case 'KODO':
kodoData.value = bac;
break;
case 'OneDrive':
oneDriveData.value = bac;
break;
}
}
};

View File

@ -45,6 +45,21 @@
>
<el-input show-password clearable v-model.trim="dialogData.rowData!.credential" />
</el-form-item>
<el-form-item v-if="dialogData.rowData!.type === 'OneDrive'">
<el-checkbox v-model="dialogData.rowData!.varsJson['isCN']" :label="$t('setting.isCN')" />
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'OneDrive'"
:label="$t('setting.code')"
prop="varsJson.code"
:rules="Rules.requiredInput"
>
<el-input clearable v-model.trim="dialogData.rowData!.varsJson['code']">
<template #append>
<el-button @click="jumpAzure">{{ $t('setting.loadCode') }}</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'S3' || dialogData.rowData!.type === 'COS'"
label="Region"
@ -136,6 +151,13 @@
<el-input v-model="dialogData.rowData!.bucket" />
</el-form-item>
</div>
<el-form-item
v-if="dialogData.rowData!.type !== 'LOCAL'"
:label="$t('setting.backupDir')"
prop="backupPath"
>
<el-input clearable v-model.trim="dialogData.rowData!.backupPath" />
</el-form-item>
</el-col>
</el-row>
</el-form>
@ -206,6 +228,16 @@ const handleClose = () => {
drawerVisiable.value = false;
};
const jumpAzure = () => {
let commonUrl =
'response_type=code&client_id=5446cfe3-4c79-47a0-ae25-fc645478e2d9&redirect_uri=http://localhost/login/authorized&scope=offline_access+Files.ReadWrite.All+User.Read';
if (!dialogData.value.rowData!.varsJson['isCN']) {
window.open('https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + commonUrl, '_blank');
} else {
window.open('https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?' + commonUrl, '_blank');
}
};
const loadDir = async (path: string) => {
dialogData.value.rowData!.varsJson['dir'] = path;
};

12
go.mod
View File

@ -22,6 +22,7 @@ require (
github.com/go-gormigrate/gormigrate/v2 v2.0.2
github.com/go-playground/validator/v10 v10.14.0
github.com/go-sql-driver/mysql v1.6.0
github.com/goh-chunlin/go-onedrive v1.1.1
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
@ -51,6 +52,7 @@ require (
github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f
golang.org/x/crypto v0.9.0
golang.org/x/net v0.10.0
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783
golang.org/x/sys v0.8.0
golang.org/x/text v0.9.0
gopkg.in/ini.v1 v1.67.0
@ -113,7 +115,7 @@ require (
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
@ -141,6 +143,7 @@ require (
github.com/gorilla/mux v1.8.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/h2non/filetype v1.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@ -208,7 +211,7 @@ require (
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.3 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect
@ -229,7 +232,7 @@ require (
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel v1.15.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 // indirect
@ -237,13 +240,12 @@ require (
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
go.opentelemetry.io/otel/metric v0.27.0 // indirect
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.15.1 // indirect
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/image v0.5.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/time v0.1.0 // indirect

20
go.sum
View File

@ -299,8 +299,8 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@ -349,6 +349,8 @@ github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goh-chunlin/go-onedrive v1.1.1 h1:HGtHk5iG0MZ92zYUtaY04czfZPBIJUr12UuFc+PW8m4=
github.com/goh-chunlin/go-onedrive v1.1.1/go.mod h1:N8qIGHD7tryO734epiBKk5oXcpGwxKET/u3LuBHciTs=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
@ -461,6 +463,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaW
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -827,8 +831,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
@ -915,8 +920,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 h1:SLme4Po
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII=
go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4=
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8=
go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 h1:imIM3vRDMyZK1ypQlQlO+brE22I9lRhJsBDXpDWjlz8=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 h1:WPpPsAAs8I2rA47v5u0558meKmmwm1Dj99ZbqCV8sZ8=
@ -933,8 +938,8 @@ go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB
go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE=
go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE=
go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc=
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY=
go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c=
go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ=
@ -1015,6 +1020,7 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk=