Order & Sorting

This commit is contained in:
2021-02-08 19:42:20 -05:00
parent 6697358960
commit af237110f9
12 changed files with 727 additions and 604 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,61 @@
package graph
import (
"context"
"errors"
"net/http"
"strings"
"time"
"github.com/dsoprea/go-exif/v3"
exifcommon "github.com/dsoprea/go-exif/v3/common"
"github.com/google/uuid"
"reichard.io/imagini/graph/model"
)
func getContextHTTP(ctx context.Context) (*http.ResponseWriter, *http.Request, error) {
authContext := ctx.Value("auth").(*model.AuthContext)
resp := authContext.AuthResponse
if resp == nil {
return nil, nil, errors.New("Context Error")
}
req := authContext.AuthRequest
if resp == nil {
return nil, nil, errors.New("Context Error")
}
return resp, req, nil
}
func getContextIDs(ctx context.Context) (string, string, error) {
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
uid, ok := accessToken.Get("sub")
if !ok {
return "", "", errors.New("Context Error")
}
did, ok := accessToken.Get("sub")
if !ok {
return "", "", errors.New("Context Error")
}
userID, err := uuid.Parse(uid.(string))
if err != nil {
return "", "", errors.New("Context Error")
}
deviceID, err := uuid.Parse(did.(string))
if err != nil {
return "", "", errors.New("Context Error")
}
return userID.String(), deviceID.String(), nil
}
func deriveDeviceType(r *http.Request) model.DeviceType {
userAgent := strings.ToLower(r.Header.Get("User-Agent"))
if strings.Contains(userAgent, "ios-imagini") {

View File

@@ -27,8 +27,8 @@ type AlbumFilter struct {
}
type AlbumResponse struct {
Data []*Album `json:"data" `
PageInfo *PageInfo `json:"pageInfo" `
Data []*Album `json:"data" `
Page *PageResponse `json:"page" `
}
type AuthResponse struct {
@@ -66,8 +66,8 @@ type DeviceFilter struct {
}
type DeviceResponse struct {
Data []*Device `json:"data" `
PageInfo *PageInfo `json:"pageInfo" `
Data []*Device `json:"data" `
Page *PageResponse `json:"page" `
}
type DeviceTypeFilter struct {
@@ -127,8 +127,8 @@ type MediaItemFilter struct {
}
type MediaItemResponse struct {
Data []*MediaItem `json:"data" `
PageInfo *PageInfo `json:"pageInfo" `
Data []*MediaItem `json:"data" `
Page *PageResponse `json:"page" `
}
type NewAlbum struct {
@@ -155,8 +155,18 @@ type NewUser struct {
Password *string `json:"password" `
}
type PageInfo struct {
Count int `json:"count" `
type Order struct {
By *string `json:"by" `
Direction *OrderDirection `json:"direction" `
}
type Page struct {
Size *int `json:"size" `
Page *int `json:"page" `
}
type PageResponse struct {
Size int `json:"size" `
Page int `json:"page" `
Total int `json:"total" `
}
@@ -193,8 +203,8 @@ type TagFilter struct {
}
type TagResponse struct {
Data []*Tag `json:"data" `
PageInfo *PageInfo `json:"pageInfo" `
Data []*Tag `json:"data" `
Page *PageResponse `json:"page" `
}
type TimeFilter struct {
@@ -233,8 +243,8 @@ type UserFilter struct {
}
type UserResponse struct {
Data []*User `json:"data" `
PageInfo *PageInfo `json:"pageInfo" `
Data []*User `json:"data" `
Page *PageResponse `json:"page" `
}
type AuthResult string
@@ -372,6 +382,47 @@ func (e DeviceType) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type OrderDirection string
const (
OrderDirectionAsc OrderDirection = "ASC"
OrderDirectionDesc OrderDirection = "DESC"
)
var AllOrderDirection = []OrderDirection{
OrderDirectionAsc,
OrderDirectionDesc,
}
func (e OrderDirection) IsValid() bool {
switch e {
case OrderDirectionAsc, OrderDirectionDesc:
return true
}
return false
}
func (e OrderDirection) String() string {
return string(e)
}
func (e *OrderDirection) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = OrderDirection(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid OrderDirection", str)
}
return nil
}
func (e OrderDirection) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type Role string
const (

View File

@@ -32,6 +32,11 @@ enum AuthType {
LDAP
}
enum OrderDirection {
ASC
DESC
}
# ------------------------------------------------------------
# ---------------------- Authentication ----------------------
# ------------------------------------------------------------
@@ -267,39 +272,49 @@ input NewAlbum {
name: String!
}
input Page {
size: Int
page: Int
}
input Order {
by: String
direction: OrderDirection
}
# ------------------------------------------------------------
# ------------------------ Responses -------------------------
# ------------------------------------------------------------
type PageInfo {
count: Int!
type PageResponse {
size: Int!
page: Int!
total: Int!
}
type MediaItemResponse {
data: [MediaItem]
pageInfo: PageInfo!
page: PageResponse!
}
type UserResponse {
data: [User]
pageInfo: PageInfo!
page: PageResponse!
}
type DeviceResponse {
data: [Device]
pageInfo: PageInfo!
page: PageResponse!
}
type TagResponse {
data: [Tag]
pageInfo: PageInfo!
page: PageResponse!
}
type AlbumResponse {
data: [Album]
pageInfo: PageInfo!
page: PageResponse!
}
# ------------------------------------------------------------
@@ -313,62 +328,52 @@ type Query {
password: String!
deviceID: ID
): AuthResponse!
logout: AuthResponse! @hasMinRole(role: User)
logout: AuthResponse! @hasMinRole(role: User)
# Single Item
mediaItem(
id: ID!
delete: Boolean
): MediaItem! @hasMinRole(role: User)
): MediaItem! @hasMinRole(role: User)
device(
id: ID!
delete: Boolean
): Device! @hasMinRole(role: User)
): Device! @hasMinRole(role: User)
album(
id: ID!
delete: Boolean
): Album! @hasMinRole(role: User)
): Album! @hasMinRole(role: User)
user(
id: ID!
delete: Boolean
): User! @hasMinRole(role: Admin) # TODO: Delete All User Content
): User! @hasMinRole(role: Admin)
tag(
id: ID!
delete: Boolean
): Tag! @hasMinRole(role: User)
me(delete: Boolean): User! @hasMinRole(role: User)
): Tag! @hasMinRole(role: User)
me: User! @hasMinRole(role: User)
# All
mediaItems(
delete: Boolean
filter: MediaItemFilter
count: Int
page: Int
): MediaItemResponse! @hasMinRole(role: User)
page: Page
order: Order
): MediaItemResponse! @hasMinRole(role: User)
devices(
delete: Boolean
filter: DeviceFilter
count: Int
page: Int
): DeviceResponse! @hasMinRole(role: User)
page: Page
order: Order
): DeviceResponse! @hasMinRole(role: User)
albums(
delete: Boolean
filter: AlbumFilter
count: Int
page: Int
): AlbumResponse! @hasMinRole(role: User)
page: Page
order: Order
): AlbumResponse! @hasMinRole(role: User)
tags(
delete: Boolean
filter: TagFilter
count: Int
page: Int
): TagResponse! @hasMinRole(role: User)
page: Page
order: Order
): TagResponse! @hasMinRole(role: User)
users(
delete: Boolean
filter: UserFilter
count: Int
page: Int
): UserResponse! @hasMinRole(role: Admin) # TODO: Delete All User Content
page: Page
order: Order
): UserResponse! @hasMinRole(role: Admin)
}
type Mutation {

View File

@@ -21,12 +21,10 @@ import (
)
func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewMediaItem) (*model.MediaItem, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
userID, ok := accessToken.Get("sub")
if !ok {
return nil, errors.New("Upload Failed")
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
// File header placeholder
@@ -52,7 +50,7 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
// Derive Folder & File Path
mediaItemID := uuid.New().String()
fileName := mediaItemID + fileMime.Extension()
folderPath := path.Join("/" + r.Config.DataPath + "/media/" + userID.(string))
folderPath := path.Join("/" + r.Config.DataPath + "/media/" + userID)
os.MkdirAll(folderPath, 0700)
filePath := path.Join(folderPath + "/" + fileName)
@@ -83,7 +81,7 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
// Add Additional MediaItem Fields
mediaItem.ID = mediaItemID
mediaItem.UserID = userID.(string)
mediaItem.UserID = userID
mediaItem.IsVideo = isVideo
mediaItem.FileName = fileName
mediaItem.OrigName = input.File.Filename
@@ -99,20 +97,18 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
}
func (r *mutationResolver) CreateAlbum(ctx context.Context, input model.NewAlbum) (*model.Album, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
userID, ok := accessToken.Get("sub")
if !ok {
return nil, errors.New("Upload Failed")
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
album := &model.Album{
Name: input.Name,
UserID: userID.(string),
UserID: userID,
}
err := r.DB.CreateAlbum(album)
err = r.DB.CreateAlbum(album)
if err != nil {
return nil, err
}
@@ -121,20 +117,18 @@ func (r *mutationResolver) CreateAlbum(ctx context.Context, input model.NewAlbum
}
func (r *mutationResolver) CreateTag(ctx context.Context, input model.NewTag) (*model.Tag, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
userID, ok := accessToken.Get("sub")
if !ok {
return nil, errors.New("Upload Failed")
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
tag := &model.Tag{
Name: input.Name,
UserID: userID.(string),
UserID: userID,
}
err := r.DB.CreateTag(tag)
err = r.DB.CreateTag(tag)
if err != nil {
return nil, err
}
@@ -162,10 +156,11 @@ func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser)
}
func (r *queryResolver) Login(ctx context.Context, user string, password string, deviceID *string) (*model.AuthResponse, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
resp := authContext.AuthResponse
req := authContext.AuthRequest
// Acquire Context
resp, req, err := getContextHTTP(ctx)
if err != nil {
return nil, err
}
// Clear All Cookies By Default
accessCookie := http.Cookie{Name: "AccessToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)}
@@ -219,9 +214,11 @@ func (r *queryResolver) Login(ctx context.Context, user string, password string,
}
func (r *queryResolver) Logout(ctx context.Context) (*model.AuthResponse, error) {
// Set Cookie From Context
authContext := ctx.Value("auth").(*model.AuthContext)
resp := authContext.AuthResponse
// Acquire Context
resp, _, err := getContextHTTP(ctx)
if err != nil {
return nil, err
}
// Clear All Cookies
accessCookie := http.Cookie{Name: "AccessToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)}
@@ -232,13 +229,11 @@ func (r *queryResolver) Logout(ctx context.Context) (*model.AuthResponse, error)
return &model.AuthResponse{Result: model.AuthResultSuccess}, nil
}
func (r *queryResolver) MediaItem(ctx context.Context, id string, delete *bool) (*model.MediaItem, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
userID, ok := accessToken.Get("sub")
if !ok {
return nil, errors.New("Context Error")
func (r *queryResolver) MediaItem(ctx context.Context, id string) (*model.MediaItem, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
mediaItemID, err := uuid.Parse(id)
@@ -246,7 +241,7 @@ func (r *queryResolver) MediaItem(ctx context.Context, id string, delete *bool)
return nil, errors.New("Invalid ID Format")
}
foundMediaItem := &model.MediaItem{ID: mediaItemID.String(), UserID: userID.(string)}
foundMediaItem := &model.MediaItem{ID: mediaItemID.String(), UserID: userID}
count, err := r.DB.MediaItem(foundMediaItem)
if err != nil {
return nil, errors.New("DB Error")
@@ -256,13 +251,11 @@ func (r *queryResolver) MediaItem(ctx context.Context, id string, delete *bool)
return foundMediaItem, nil
}
func (r *queryResolver) Device(ctx context.Context, id string, delete *bool) (*model.Device, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
userID, ok := accessToken.Get("sub")
if !ok {
return nil, errors.New("Context Error")
func (r *queryResolver) Device(ctx context.Context, id string) (*model.Device, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
deviceID, err := uuid.Parse(id)
@@ -270,7 +263,7 @@ func (r *queryResolver) Device(ctx context.Context, id string, delete *bool) (*m
return nil, errors.New("Invalid ID Format")
}
foundDevice := &model.Device{ID: deviceID.String(), UserID: userID.(string)}
foundDevice := &model.Device{ID: deviceID.String(), UserID: userID}
count, err := r.DB.Device(foundDevice)
if err != nil {
return nil, errors.New("DB Error")
@@ -280,13 +273,11 @@ func (r *queryResolver) Device(ctx context.Context, id string, delete *bool) (*m
return foundDevice, nil
}
func (r *queryResolver) Album(ctx context.Context, id string, delete *bool) (*model.Album, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
userID, ok := accessToken.Get("sub")
if !ok {
return nil, errors.New("Context Error")
func (r *queryResolver) Album(ctx context.Context, id string) (*model.Album, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
albumID, err := uuid.Parse(id)
@@ -294,7 +285,7 @@ func (r *queryResolver) Album(ctx context.Context, id string, delete *bool) (*mo
return nil, errors.New("Invalid ID Format")
}
foundAlbum := &model.Album{ID: albumID.String(), UserID: userID.(string)}
foundAlbum := &model.Album{ID: albumID.String(), UserID: userID}
count, err := r.DB.Album(foundAlbum)
if err != nil {
return nil, errors.New("DB Error")
@@ -304,7 +295,7 @@ func (r *queryResolver) Album(ctx context.Context, id string, delete *bool) (*mo
return foundAlbum, nil
}
func (r *queryResolver) User(ctx context.Context, id string, delete *bool) (*model.User, error) {
func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) {
userID, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("Invalid ID Format")
@@ -320,13 +311,19 @@ func (r *queryResolver) User(ctx context.Context, id string, delete *bool) (*mod
return foundUser, nil
}
func (r *queryResolver) Tag(ctx context.Context, id string, delete *bool) (*model.Tag, error) {
func (r *queryResolver) Tag(ctx context.Context, id string) (*model.Tag, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
tagID, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("Invalid ID Format")
}
foundTag := &model.Tag{ID: tagID.String()}
foundTag := &model.Tag{ID: tagID.String(), UserID: userID}
count, err := r.DB.Tag(foundTag)
if err != nil {
return nil, errors.New("DB Error")
@@ -336,16 +333,14 @@ func (r *queryResolver) Tag(ctx context.Context, id string, delete *bool) (*mode
return foundTag, nil
}
func (r *queryResolver) Me(ctx context.Context, delete *bool) (*model.User, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
userID, ok := accessToken.Get("sub")
if !ok {
return nil, errors.New("Context Error")
func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
foundUser := &model.User{ID: userID.(string)}
foundUser := &model.User{ID: userID}
count, err := r.DB.User(foundUser)
if err != nil || count != 1 {
return nil, errors.New("DB Error")
@@ -353,88 +348,82 @@ func (r *queryResolver) Me(ctx context.Context, delete *bool) (*model.User, erro
return foundUser, nil
}
func (r *queryResolver) MediaItems(ctx context.Context, delete *bool, filter *model.MediaItemFilter, count *int, page *int) (*model.MediaItemResponse, error) {
resp, totalCount, err := r.DB.MediaItems(filter)
func (r *queryResolver) MediaItems(ctx context.Context, filter *model.MediaItemFilter, page *model.Page, order *model.Order) (*model.MediaItemResponse, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, errors.New("Context Error")
return nil, err
}
resp, pageResponse, err := r.DB.MediaItems(userID, filter, page, order)
if err != nil {
return nil, errors.New("DB Error")
}
return &model.MediaItemResponse{
Data: resp,
PageInfo: &model.PageInfo{
Count: int(totalCount),
Page: 0,
Total: int(totalCount),
},
Page: &pageResponse,
}, nil
}
func (r *queryResolver) Devices(ctx context.Context, delete *bool, filter *model.DeviceFilter, count *int, page *int) (*model.DeviceResponse, error) {
// Get Context
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken := *authContext.AccessToken
userID, ok := accessToken.Get("sub")
if !ok {
return nil, errors.New("Context Error")
func (r *queryResolver) Devices(ctx context.Context, filter *model.DeviceFilter, page *model.Page, order *model.Order) (*model.DeviceResponse, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
_ = userID
resp, totalCount, err := r.DB.Devices()
resp, pageResponse, err := r.DB.Devices(userID, filter, page, order)
if err != nil {
return nil, errors.New("DB Error")
}
return &model.DeviceResponse{
Data: resp,
PageInfo: &model.PageInfo{
Count: int(totalCount),
Page: 0,
Total: int(totalCount),
},
Page: &pageResponse,
}, nil
}
func (r *queryResolver) Albums(ctx context.Context, delete *bool, filter *model.AlbumFilter, count *int, page *int) (*model.AlbumResponse, error) {
// TODO: User Specific
resp, totalCount, err := r.DB.Albums()
func (r *queryResolver) Albums(ctx context.Context, filter *model.AlbumFilter, page *model.Page, order *model.Order) (*model.AlbumResponse, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
resp, pageResponse, err := r.DB.Albums(userID, filter, page, order)
if err != nil {
return nil, errors.New("Context Error")
}
return &model.AlbumResponse{
Data: resp,
PageInfo: &model.PageInfo{
Count: int(totalCount),
Page: 0,
Total: int(totalCount),
},
Page: &pageResponse,
}, nil
}
func (r *queryResolver) Tags(ctx context.Context, delete *bool, filter *model.TagFilter, count *int, page *int) (*model.TagResponse, error) {
resp, totalCount, err := r.DB.Tags()
func (r *queryResolver) Tags(ctx context.Context, filter *model.TagFilter, page *model.Page, order *model.Order) (*model.TagResponse, error) {
// Acquire Context
userID, _, err := getContextIDs(ctx)
if err != nil {
return nil, err
}
resp, pageResponse, err := r.DB.Tags(userID, filter, page, order)
if err != nil {
return nil, errors.New("Context Error")
}
return &model.TagResponse{
Data: resp,
PageInfo: &model.PageInfo{
Count: int(totalCount),
Page: 0,
Total: int(totalCount),
},
Page: &pageResponse,
}, nil
}
func (r *queryResolver) Users(ctx context.Context, delete *bool, filter *model.UserFilter, count *int, page *int) (*model.UserResponse, error) {
resp, totalCount, err := r.DB.Users()
func (r *queryResolver) Users(ctx context.Context, filter *model.UserFilter, page *model.Page, order *model.Order) (*model.UserResponse, error) {
resp, pageResponse, err := r.DB.Users(filter, page, order)
if err != nil {
return nil, errors.New("Context Error")
}
return &model.UserResponse{
Data: resp,
PageInfo: &model.PageInfo{
Count: int(totalCount),
Page: 0,
Total: int(totalCount),
},
Page: &pageResponse,
}, nil
}