Order & Sorting
This commit is contained in:
parent
6697358960
commit
af237110f9
File diff suppressed because it is too large
Load Diff
@ -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") {
|
||||
|
@ -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 (
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package db
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"reichard.io/imagini/graph/model"
|
||||
)
|
||||
@ -18,11 +19,17 @@ func (dbm *DBManager) Album(album *model.Album) (int64, error) {
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) Albums() ([]*model.Album, int64, error) {
|
||||
func (dbm *DBManager) Albums(userID string, filters *model.AlbumFilter, page *model.Page, order *model.Order) ([]*model.Album, model.PageResponse, error) {
|
||||
// Initial User Filter
|
||||
tx := dbm.db.Session(&gorm.Session{}).Model(&model.Album{}).Where("user_id == ?", userID)
|
||||
|
||||
// Dynamically Generate Base Query
|
||||
tx, pageResponse := dbm.generateBaseQuery(tx, filters, page, order)
|
||||
|
||||
// Acquire Results
|
||||
var foundAlbums []*model.Album
|
||||
var count int64
|
||||
err := dbm.db.Find(&foundAlbums).Count(&count).Error
|
||||
return foundAlbums, count, err
|
||||
err := tx.Find(&foundAlbums).Error
|
||||
return foundAlbums, pageResponse, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) DeleteAlbum(album *model.Album) error {
|
||||
|
@ -1,11 +1,13 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"reflect"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
// "gorm.io/gorm/logger"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
@ -30,6 +32,7 @@ func NewMgr(c *config.Config) *DBManager {
|
||||
if c.DBType == "SQLite" {
|
||||
dbLocation := path.Join(c.ConfigPath, "imagini.db")
|
||||
dbm.db, _ = gorm.Open(sqlite.Open(dbLocation), gormConfig)
|
||||
dbm.db = dbm.db.Debug()
|
||||
} else {
|
||||
log.Fatal("Unsupported Database")
|
||||
}
|
||||
@ -48,28 +51,9 @@ func NewMgr(c *config.Config) *DBManager {
|
||||
dbm.bootstrapDatabase()
|
||||
}
|
||||
|
||||
dbm.testFeatures()
|
||||
|
||||
return dbm
|
||||
}
|
||||
|
||||
func (dbm *DBManager) testFeatures() {
|
||||
|
||||
// Get Devices By UserID
|
||||
// var myDevices []model.Device
|
||||
// dbm.db.Debug().Where(&model.Device{UserID: "97589354-cd42-40e2-bc5e-7ba6badf89fa"}).Find(&myDevices)
|
||||
// fmt.Printf("Devices: %+v\n", myDevices)
|
||||
|
||||
// Get User by DeviceID
|
||||
// var myUser []model.User
|
||||
// dbm.db.Debug().Model(&model.User{}).
|
||||
// Select("users.*").
|
||||
// Joins("left join devices on users.id = devices.user_id").
|
||||
// Where("devices.id = ?", "4e9aa851-f25b-4330-91dc-c0f975e56fa1").
|
||||
// Find(&myUser)
|
||||
// fmt.Printf("User: %+v\n", myUser)
|
||||
}
|
||||
|
||||
func (dbm *DBManager) bootstrapDatabase() {
|
||||
log.Info("[query] Bootstrapping database.")
|
||||
|
||||
@ -88,46 +72,84 @@ func (dbm *DBManager) bootstrapDatabase() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
func (dbm *DBManager) QueryBuilder(dest interface{}, params []byte) (int64, error) {
|
||||
// TODO:
|
||||
// - Where Filters
|
||||
// - Sort Filters
|
||||
// - Paging Filters
|
||||
func (dbm *DBManager) generateBaseQuery(tx *gorm.DB, filter interface{}, page *model.Page, order *model.Order) (*gorm.DB, model.PageResponse) {
|
||||
tx = dbm.generateFilter(tx, filter)
|
||||
tx = dbm.generateOrder(tx, order, filter)
|
||||
tx, pageResponse := dbm.generatePage(tx, page)
|
||||
return tx, pageResponse
|
||||
}
|
||||
|
||||
objType := fmt.Sprintf("%T", dest)
|
||||
if objType == "*[]model.MediaItem" {
|
||||
// TODO: Validate MediaItem Type
|
||||
} else {
|
||||
// Return Error
|
||||
return 0, errors.New("Invalid type")
|
||||
func (dbm *DBManager) generateOrder(tx *gorm.DB, order *model.Order, filter interface{}) *gorm.DB {
|
||||
// Set Defaults
|
||||
orderBy := "created_at"
|
||||
orderDirection := model.OrderDirectionDesc
|
||||
|
||||
if order == nil {
|
||||
order = &model.Order{
|
||||
By: &orderBy,
|
||||
Direction: &orderDirection,
|
||||
}
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := dbm.db.Find(dest).Count(&count).Error
|
||||
return count, err
|
||||
if order.By == nil {
|
||||
order.By = &orderBy
|
||||
}
|
||||
|
||||
// Paging:
|
||||
// - Regular Pagination:
|
||||
// - /api/v1/MediaItems?page[limit]=50&page=2
|
||||
// - Meta Count Only
|
||||
// - /api/v1/MediaItems?page[limit]=0
|
||||
if order.Direction == nil {
|
||||
order.Direction = &orderDirection
|
||||
}
|
||||
|
||||
// Sorting:
|
||||
// - Ascending Sort:
|
||||
// - /api/v1/MediaItems?sort=created_at
|
||||
// - Descending Sort:
|
||||
// - /api/v1/MediaItems?sort=-created_at
|
||||
// Get Possible Values
|
||||
ptr := reflect.New(reflect.TypeOf(filter).Elem())
|
||||
v := reflect.Indirect(ptr)
|
||||
|
||||
// Filters:
|
||||
// - Greater Than / Less Than (created_at, updated_at, exif_date)
|
||||
// - /api/v1/MediaItems?filter[created_at]>=2020-01-01&filter[created_at]<=2021-01-01
|
||||
// - Long / Lat Range (latitude, longitude)
|
||||
// - /api/v1/MediaItems?filter[latitude]>=71.1827&filter[latitude]<=72.0000&filter[longitude]>=100.000&filter[longitude]<=101.0000
|
||||
// - Image / Video (media_type)
|
||||
// - /api/v1/MediaItems?filter[media_type]=Image
|
||||
// - Tags (tags)
|
||||
// - /api/v1/MediaItems?filter[tags]=id1,id2,id3
|
||||
// - Albums (albums)
|
||||
// - /api/v1/MediaItems?filter[albums]=id1
|
||||
isValid := false
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fieldName := v.Type().Field(i).Name
|
||||
if strcase.ToSnake(*order.By) == strcase.ToSnake(fieldName) {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isValid {
|
||||
tx = tx.Order(fmt.Sprintf("%s %s", strcase.ToSnake(*order.By), order.Direction.String()))
|
||||
}
|
||||
|
||||
return tx
|
||||
}
|
||||
|
||||
func (dbm *DBManager) generatePage(tx *gorm.DB, page *model.Page) (*gorm.DB, model.PageResponse) {
|
||||
// Set Defaults
|
||||
var count int64
|
||||
pageSize := 50
|
||||
pageNum := 1
|
||||
|
||||
if page == nil {
|
||||
page = &model.Page{
|
||||
Size: &pageSize,
|
||||
Page: &pageNum,
|
||||
}
|
||||
}
|
||||
|
||||
if page.Size == nil {
|
||||
page.Size = &pageSize
|
||||
}
|
||||
|
||||
if page.Page == nil {
|
||||
page.Page = &pageNum
|
||||
}
|
||||
|
||||
// Acquire Counts Before Pagination
|
||||
tx.Count(&count)
|
||||
|
||||
// Calculate Offset
|
||||
calculatedOffset := (*page.Page - 1) * *page.Size
|
||||
tx = tx.Limit(*page.Size).Offset(calculatedOffset)
|
||||
|
||||
return tx, model.PageResponse{
|
||||
Page: *page.Page,
|
||||
Size: *page.Size,
|
||||
Total: int(count),
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"reichard.io/imagini/graph/model"
|
||||
)
|
||||
@ -21,11 +22,17 @@ func (dbm *DBManager) Device(device *model.Device) (int64, error) {
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) Devices() ([]*model.Device, int64, error) {
|
||||
func (dbm *DBManager) Devices(userID string, filters *model.DeviceFilter, page *model.Page, order *model.Order) ([]*model.Device, model.PageResponse, error) {
|
||||
// Initial User Filter
|
||||
tx := dbm.db.Session(&gorm.Session{}).Model(&model.Device{}).Where("user_id == ?", userID)
|
||||
|
||||
// Dynamically Generate Base Query
|
||||
tx, pageResponse := dbm.generateBaseQuery(tx, filters, page, order)
|
||||
|
||||
// Acquire Results
|
||||
var foundDevices []*model.Device
|
||||
var count int64
|
||||
err := dbm.db.Find(&foundDevices).Count(&count).Error
|
||||
return foundDevices, count, err
|
||||
err := tx.Find(&foundDevices).Error
|
||||
return foundDevices, pageResponse, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) DeleteDevice(user *model.Device) error {
|
||||
|
@ -10,10 +10,13 @@ import (
|
||||
)
|
||||
|
||||
// Generic function used to generate filters for the DB
|
||||
func (dbm *DBManager) generateFilters(filter interface{}) (*gorm.DB, error) {
|
||||
tx := dbm.db.Session(&gorm.Session{}).Debug()
|
||||
func (dbm *DBManager) generateFilter(tx *gorm.DB, filter interface{}) *gorm.DB {
|
||||
ptr := reflect.ValueOf(filter)
|
||||
v := reflect.Indirect(ptr)
|
||||
|
||||
v := reflect.ValueOf(filter)
|
||||
if v == reflect.ValueOf(nil) {
|
||||
return tx
|
||||
}
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fieldName := strcase.ToSnake(v.Type().Field(i).Name)
|
||||
@ -45,7 +48,7 @@ func (dbm *DBManager) generateFilters(filter interface{}) (*gorm.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
return tx
|
||||
}
|
||||
|
||||
func generateStringFilter(tx *gorm.DB, fieldName string, filter *model.StringFilter) *gorm.DB {
|
||||
|
@ -2,6 +2,7 @@ package db
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"reichard.io/imagini/graph/model"
|
||||
)
|
||||
@ -18,14 +19,16 @@ func (dbm *DBManager) MediaItem(mediaItem *model.MediaItem) (int64, error) {
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) MediaItems(filters *model.MediaItemFilter) ([]*model.MediaItem, int64, error) {
|
||||
// Perform Filters
|
||||
tx, err := dbm.generateFilters(*filters)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// UserID, Filters, Sort, Page, Delete
|
||||
func (dbm *DBManager) MediaItems(userID string, filters *model.MediaItemFilter, page *model.Page, order *model.Order) ([]*model.MediaItem, model.PageResponse, error) {
|
||||
// Initial User Filter
|
||||
tx := dbm.db.Session(&gorm.Session{}).Model(&model.MediaItem{}).Where("user_id == ?", userID)
|
||||
|
||||
// Dynamically Generate Base Query
|
||||
tx, pageResponse := dbm.generateBaseQuery(tx, filters, page, order)
|
||||
|
||||
// Acquire Results
|
||||
var mediaItems []*model.MediaItem
|
||||
var count int64
|
||||
err = tx.Find(&mediaItems).Count(&count).Error
|
||||
return mediaItems, count, err
|
||||
err := tx.Find(&mediaItems).Error
|
||||
return mediaItems, pageResponse, err
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package db
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"reichard.io/imagini/graph/model"
|
||||
)
|
||||
@ -18,11 +19,17 @@ func (dbm *DBManager) Tag(tag *model.Tag) (int64, error) {
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) Tags() ([]*model.Tag, int64, error) {
|
||||
func (dbm *DBManager) Tags(userID string, filters *model.TagFilter, page *model.Page, order *model.Order) ([]*model.Tag, model.PageResponse, error) {
|
||||
// Initial User Filter
|
||||
tx := dbm.db.Session(&gorm.Session{}).Model(&model.Tag{}).Where("user_id == ?", userID)
|
||||
|
||||
// Dynamically Generate Base Query
|
||||
tx, pageResponse := dbm.generateBaseQuery(tx, filters, page, order)
|
||||
|
||||
// Acquire Results
|
||||
var foundTags []*model.Tag
|
||||
var count int64
|
||||
err := dbm.db.Find(&foundTags).Count(&count).Error
|
||||
return foundTags, count, err
|
||||
err := tx.Find(&foundTags).Error
|
||||
return foundTags, pageResponse, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) DeleteTag(tag *model.Tag) error {
|
||||
|
@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"reichard.io/imagini/graph/model"
|
||||
)
|
||||
@ -25,11 +26,17 @@ func (dbm *DBManager) User(user *model.User) (int64, error) {
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) Users() ([]*model.User, int64, error) {
|
||||
func (dbm *DBManager) Users(filters *model.UserFilter, page *model.Page, order *model.Order) ([]*model.User, model.PageResponse, error) {
|
||||
// Initial User Filter
|
||||
tx := dbm.db.Session(&gorm.Session{}).Model(&model.Tag{})
|
||||
|
||||
// Dynamically Generate Base Query
|
||||
tx, pageResponse := dbm.generateBaseQuery(tx, filters, page, order)
|
||||
|
||||
// Acquire Results
|
||||
var foundUsers []*model.User
|
||||
var count int64
|
||||
err := dbm.db.Find(&foundUsers).Count(&count).Error
|
||||
return foundUsers, count, err
|
||||
err := tx.Find(&foundUsers).Error
|
||||
return foundUsers, pageResponse, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) DeleteUser(user model.User) error {
|
||||
|
Reference in New Issue
Block a user