Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/pkg/db/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ var (
pgOnce sync.Once
)

type OwnerInfo struct {
DisplayName string `json:"display_name"`
Email string `json:"email"`
PhotoLink string `json:"photo_link"`
}

func GetPGClient() (*pgxpool.Config, *pgxpool.Pool) {
pgOnce.Do(func() {
poolconfig, err := connectPostgres()
Expand Down
101 changes: 101 additions & 0 deletions api/pkg/handlers/account.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package handlers

import (
"context"
"database/sql"
"errors"
"net/http"

"github.com/blackmamoth/cloudmesh/pkg/config"
"github.com/blackmamoth/cloudmesh/pkg/db"
"github.com/blackmamoth/cloudmesh/pkg/middlewares"
"github.com/blackmamoth/cloudmesh/pkg/providers"
"github.com/blackmamoth/cloudmesh/pkg/utils"
"github.com/blackmamoth/cloudmesh/repository"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"go.uber.org/zap"
Expand All @@ -31,6 +35,10 @@ type AccountDetail struct {
UsedStorage int64 `json:"used_storage"`
}

type UnlinkAccountValidation struct {
AccountID string `validate:"required,uuid" json:"account_id"`
}

func NewAccountHandler(
connPool *pgxpool.Pool,
authMiddleware *middlewares.AuthMiddleware,
Expand All @@ -48,6 +56,8 @@ func (h *AccountHandler) RegisterRoutes() *chi.Mux {

r.Get("/get-accounts", h.getAccounts)

r.Delete("/unlink-account", h.unlinkAccount)

return r
}

Expand Down Expand Up @@ -153,6 +163,97 @@ func (h *AccountHandler) getAccounts(w http.ResponseWriter, r *http.Request) {
})
}

func (h *AccountHandler) unlinkAccount(w http.ResponseWriter, r *http.Request) {
payload, ok := utils.ParseAndValidate[UnlinkAccountValidation](w, r, true)
if !ok {
return
}

accountID, err := db.PGUUID(payload.AccountID)
if err != nil {
config.LOGGER.Error("failed to parse account uuid", zap.Error(err))
utils.SendAPIErrorResponse(
w,
http.StatusBadRequest,
errors.New("could not validate user credentials"),
)
}

userID, ok := r.Context().Value(middlewares.UserKey).(string)
if !ok {
config.LOGGER.Error("invalid userid", zap.Any("user_id_received", userID))
utils.SendAPIErrorResponse(
w,
http.StatusBadRequest,
errors.New("could not validate user credentials"),
)

return
}

conn, err := h.connPool.Acquire(r.Context())
if err != nil {
config.LOGGER.Error("failed to acquire new connection from connection pool", zap.Error(err))
utils.SendAPIErrorResponse(
w,
http.StatusInternalServerError,
errors.New("failed to process your request, please try again later"),
)

return
}
defer conn.Release()

queries := repository.New(conn)

_, err = queries.GetLinkedAccount(r.Context(), repository.GetLinkedAccountParams{
UserID: userID,
AccountID: *accountID,
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
utils.SendAPIErrorResponse(
w,
http.StatusBadRequest,
"account does not exist or does not belong to the user",
)
} else {
config.LOGGER.Error("failed to fetch linked account", zap.String("user_id", userID), zap.String("account_id", accountID.String()), zap.Error(err))
utils.SendAPIErrorResponse(w, http.StatusInternalServerError, "failed to fetch your account details")
}
return
}

err = utils.WithTransaction(r.Context(), conn, func(ctx context.Context, tx pgx.Tx) error {
qtx := repository.New(conn).WithTx(tx)

return qtx.DeleteLinkedAccount(ctx, repository.DeleteLinkedAccountParams{
AccountID: *accountID,
UserID: userID,
})
})
if err != nil {
config.LOGGER.Error(
"failed to delete linked account",
zap.String("user_id", userID),
zap.String("account_id", accountID.String()),
zap.Error(err),
)
utils.SendAPIErrorResponse(
w,
http.StatusInternalServerError,
errors.New("your account could not be deleted"),
)
return
}

utils.SendAPIResponse(
w,
http.StatusOK,
map[string]string{"message": "Your account was successfully unlinked"},
)
}

func (h *AccountHandler) groupAccountsByProvider(
accounts []AccountDetail,
) map[string][]AccountDetail {
Expand Down
16 changes: 8 additions & 8 deletions api/pkg/handlers/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ type FilesHandler struct {
}

type GetFilesValidation struct {
Provider string `validate:"omitempty,oneof=google dropbox" json:"provider"`
ParentFolder string `validate:"omitempty" json:"parent_folder"`
Search string `validate:"omitempty" json:"search"`
SortOn string `validate:"omitempty" json:"sort_on"`
SortBy string `validate:"omitempty" json:"sort_by"`
Limit int32 `validate:"omitempty" json:"limit"`
Offset int32 `validate:"omitempty" json:"offset"`
ContentSearch bool `validate:"omitempty" json:"content_search"`
Provider string `validate:"omitempty,oneof=google dropbox microsoft" json:"provider"`
ParentFolder string `validate:"omitempty" json:"parent_folder"`
Search string `validate:"omitempty" json:"search"`
SortOn string `validate:"omitempty" json:"sort_on"`
SortBy string `validate:"omitempty" json:"sort_by"`
Limit int32 `validate:"omitempty" json:"limit"`
Offset int32 `validate:"omitempty" json:"offset"`
ContentSearch bool `validate:"omitempty" json:"content_search"`
}

type UploadFilesValidation struct {
Expand Down
74 changes: 60 additions & 14 deletions api/pkg/providers/dropbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,23 @@ type DropboxAccountInfo struct {
}

type DropboxListFolderEntries struct {
ID string `json:"id"`
Tag string `json:".tag,omitempty"`
Name string `json:"name"`
PathDisplay string `json:"path_display"`
PathLower string `json:"path_lower"`
ClientModified time.Time `json:"client_modified"`
ServerModified time.Time `json:"server_modified"`
ContentHash string `json:"content_hash"`
Revision string `json:"rev"`
Size int `json:"size"`
ID string `json:"id"`
Tag string `json:".tag,omitempty"`
Name string `json:"name"`
PathDisplay string `json:"path_display"`
PathLower string `json:"path_lower"`
ClientModified time.Time `json:"client_modified"`
ServerModified time.Time `json:"server_modified"`
ContentHash string `json:"content_hash"`
Revision string `json:"rev"`
Size int `json:"size"`
SharingInfo *DropboxSharingInfo `json:"sharing_info,omitempty"`
}

type DropboxSharingInfo struct {
ReadOnly bool `json:"read_only,omitempty"`
ParentSharedFolderID string `json:"parent_shared_folder_id,omitempty"`
ModifiedBy string `json:"modified_by,omitempty"`
}

type DropboxListFolderResponse struct {
Expand Down Expand Up @@ -305,6 +312,16 @@ func (p *DropboxProvider) SyncFiles(
config.LOGGER.Error("failed to validate access token", zap.Error(err))
}

accountUser, err := queries.GetAccountUserByID(ctx, accountID)
if err != nil {
config.LOGGER.Error(
"failed to fetch account user",
zap.String("account_id", accountID.String()),
zap.Error(err),
)
return err
}

for {
dropboxResponse, err := p.getDropboxFolderList(
ctx,
Expand All @@ -327,6 +344,7 @@ func (p *DropboxProvider) SyncFiles(
files, providerFileIDs := p.convertToSyncedItemSlice(
dropboxResponse.Entries,
accountID,
accountUser,
syncDetails.LastSyncedAt.Valid,
)

Expand Down Expand Up @@ -379,7 +397,9 @@ func (p *DropboxProvider) getDropboxFolderList(
accessToken, refreshToken, cursor string,
) (*DropboxListFolderResponse, error) {
url := DROPBOX_API_BASE_URL + "/2/files/list_folder"
reqBody := []byte(`{"path": "", "recursive": true, "include_deleted": true}`)
reqBody := []byte(
`{"path": "", "recursive": true, "include_deleted": true, "include_has_explicit_shared_members": true}`,
)

if cursor != "" {
url = url + "/continue"
Expand Down Expand Up @@ -778,7 +798,17 @@ func (p *DropboxProvider) UploadFiles(
return err
}

files, _ := p.convertToSyncedItemSlice(results, accountID, false)
accountUser, err := queries.GetAccountUserByID(ctx, accountID)
if err != nil {
config.LOGGER.Error(
"failed to fetch account user",
zap.String("account_id", accountID.String()),
zap.Error(err),
)
return err
}

files, _ := p.convertToSyncedItemSlice(results, accountID, accountUser, false)

_, err = p.bulkInsertSyncedItems(ctx, conn, queries, []string{}, accountID, files, "")
if err != nil {
Expand Down Expand Up @@ -1487,6 +1517,7 @@ func (p *DropboxProvider) bulkInsertSyncedItems(
func (p *DropboxProvider) convertToSyncedItemSlice(
entries []DropboxListFolderEntries,
accountID pgtype.UUID,
accountUser repository.GetAccountUserByIDRow,
isValidLastSyncedData bool,
) ([]repository.AddSyncedItemsParams, []string) {
syncedItems := []repository.AddSyncedItemsParams{}
Expand All @@ -1499,8 +1530,22 @@ func (p *DropboxProvider) convertToSyncedItemSlice(

parentFolder := path.Dir(entry.PathDisplay)

if entry.Name == "bgnet_usl_c_1.pdf" {
config.LOGGER.Info("is_deleted", zap.String("value", entry.Tag))
var ownerInfoJSON []byte
if entry.SharingInfo == nil {
ownerInfo := map[string]string{
"display_name": accountUser.Name,
"email": accountUser.Email,
"photo_link": accountUser.AvatarUrl.String,
}

var err error
ownerInfoJSON, err = json.Marshal(ownerInfo)
if err != nil {
config.LOGGER.Error("failed to marshal owner info", zap.Error(err))
ownerInfoJSON = []byte("{}")
}
} else {
ownerInfoJSON = []byte("{}")
}

syncedItems = append(syncedItems, repository.AddSyncedItemsParams{
Expand All @@ -1515,6 +1560,7 @@ func (p *DropboxProvider) convertToSyncedItemSlice(
IsFolder: entry.Tag == "folder",
ContentHash: db.PGTextField(entry.ContentHash),
IsTrashed: db.PGBool(entry.Tag == "deleted"),
OwnerInfo: ownerInfoJSON,
CreatedTime: db.PGTimestamptzField(time.Time{}),
ModifiedTime: db.PGTimestamptzField(entry.ClientModified),
ThumbnailLink: db.PGTextField(""),
Expand Down
23 changes: 21 additions & 2 deletions api/pkg/providers/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func (p *GoogleProvider) SyncFiles(
fileList, err := driveService.Files.
List().
Q(query).
Fields("files(id, name, size, mimeType, createdTime, modifiedTime, thumbnailLink, fullFileExtension, parents, webViewLink, webContentLink, iconLink, sha256Checksum, trashed)").
Fields("files(id, name, size, mimeType, createdTime, modifiedTime, thumbnailLink, fullFileExtension, parents, webViewLink, webContentLink, iconLink, sha256Checksum, trashed,owners,ownedByMe)").
PageToken(pageToken).
PageSize(1000).
Do()
Expand Down Expand Up @@ -1070,6 +1070,24 @@ func (p *GoogleProvider) convertToSyncedItemSlice(
parentFolder = file.Parents[0]
}

var ownerInfoJSON []byte
if len(file.Owners) > 0 {
ownerInfo := map[string]string{
"display_name": file.Owners[0].DisplayName,
"email": file.Owners[0].EmailAddress,
"photo_link": file.Owners[0].PhotoLink,
}

var err error
ownerInfoJSON, err = json.Marshal(ownerInfo)
if err != nil {
config.LOGGER.Error("failed to marshal owner info", zap.Error(err))
ownerInfoJSON = []byte("{}")
}
} else {
ownerInfoJSON = []byte("{}")
}

syncedItems = append(syncedItems, repository.AddSyncedItemsParams{
AccountID: accountID,
ProviderFileID: file.Id,
Expand All @@ -1082,6 +1100,7 @@ func (p *GoogleProvider) convertToSyncedItemSlice(
ParentFolder: db.PGTextField(parentFolder),
IsFolder: isFolder,
IsTrashed: db.PGBool(file.Trashed),
OwnerInfo: ownerInfoJSON,
ContentHash: db.PGTextField(file.Sha256Checksum),
ThumbnailLink: db.PGTextField(file.ThumbnailLink),
PreviewLink: db.PGTextField(previewLink),
Expand Down Expand Up @@ -1208,7 +1227,7 @@ func (p *GoogleProvider) CreateFolder(
}

folder, err := driveService.Files.Create(&driveFolder).
Fields("id, name, size, mimeType, createdTime, modifiedTime, thumbnailLink, fullFileExtension, parents, webViewLink, webContentLink, iconLink, sha256Checksum, trashed").
Fields("id, name, size, mimeType, createdTime, modifiedTime, thumbnailLink, fullFileExtension, parents, webViewLink, webContentLink, iconLink, sha256Checksum, trashed, owners, ownedByMe").
Do()
if err != nil {
logFields = append(logFields, zap.Error(err))
Expand Down
Loading
Loading