Enable GraphQL! #1

Merged
evan merged 8 commits from graphql into master 2021-02-09 00:44:20 +00:00
12 changed files with 727 additions and 604 deletions
Showing only changes of commit af237110f9 - Show all commits

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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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