package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. import ( "bytes" "context" "errors" "io" "io/ioutil" "net/http" "os" "path" "strings" "time" "github.com/gabriel-vasile/mimetype" "github.com/google/uuid" "github.com/h2non/bimg" log "github.com/sirupsen/logrus" "reichard.io/imagini/graph/generated" "reichard.io/imagini/graph/model" ) func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewMediaItem) (*model.MediaItem, error) { // Acquire Context userID, _, err := getContextIDs(ctx) if err != nil { return nil, err } // File header placeholder fileHeader := make([]byte, 64) // Copy headers into the buffer if _, err := input.File.File.Read(fileHeader); err != nil { log.Error("[upload] Failed to read file header:", err) return nil, errors.New("Upload Failed") } // Determine media type fileMime := mimetype.Detect(fileHeader) contentType := fileMime.String() var isVideo bool if strings.HasPrefix(contentType, "image/") { isVideo = false } else if strings.HasPrefix(contentType, "video/") { // TODO log.Error("[upload] Video unsupported at this time") return nil, errors.New("Upload Failed") // isVideo = true } else { log.Error("[upload] File is neither an image or video") return nil, errors.New("Upload Failed") } // Derive Folder & File Path mediaItemID := uuid.New().String() fileName := mediaItemID + fileMime.Extension() folderPath := path.Join("/" + r.Config.DataPath + "/media/" + userID) os.MkdirAll(folderPath, 0700) filePath := path.Join(folderPath + "/" + fileName) // Create File f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0600) if err != nil { log.Error("[upload] Unable to open file handle:", err) return nil, errors.New("Upload Failed") } defer f.Close() // Concat File concatFile := io.MultiReader(bytes.NewReader(fileHeader), input.File.File) // Copy file _, err = io.Copy(f, concatFile) if err != nil { log.Error("[upload] Unable to copy file:", err) return nil, errors.New("Upload Failed") } // Load Image f.Seek(0, io.SeekStart) buffer, err := ioutil.ReadAll(f) newImage := bimg.NewImage(buffer) // Create MediaItem from EXIF meta, err := newImage.Metadata() if err != nil { log.Error("[upload] Unable to extract metadata:", err) return nil, errors.New("Upload Failed") } mediaItem := mediaItemFromEXIF(meta.EXIF) // Determine Image Size imageSize, err := newImage.Size() if err != nil { log.Error("[upload] Unable to extract dimension data:", err) return nil, errors.New("Upload Failed") } if meta.Orientation > 4 { mediaItem.Width = imageSize.Height mediaItem.Height = imageSize.Width } else { mediaItem.Width = imageSize.Width mediaItem.Height = imageSize.Height } // Add Additional MediaItem Fields mediaItem.ID = mediaItemID mediaItem.UserID = userID mediaItem.IsVideo = isVideo mediaItem.FileName = fileName mediaItem.OrigName = input.File.Filename // Create MediaItem in DB err = r.DB.CreateMediaItem(mediaItem) if err != nil { log.Error("[upload] Unable to populate create file in DB:", err) return nil, errors.New("Upload Failed") } // Success return mediaItem, nil } func (r *mutationResolver) CreateAlbum(ctx context.Context, input model.NewAlbum) (*model.Album, error) { // Acquire Context userID, _, err := getContextIDs(ctx) if err != nil { return nil, err } album := &model.Album{ Name: input.Name, UserID: userID, } err = r.DB.CreateAlbum(album) if err != nil { return nil, err } return album, nil } func (r *mutationResolver) CreateTag(ctx context.Context, input model.NewTag) (*model.Tag, error) { // Acquire Context userID, _, err := getContextIDs(ctx) if err != nil { return nil, err } tag := &model.Tag{ Name: input.Name, UserID: userID, } err = r.DB.CreateTag(tag) if err != nil { return nil, err } return tag, nil } func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) { user := &model.User{ Email: input.Email, Username: input.Username, FirstName: input.FirstName, LastName: input.LastName, Role: input.Role, AuthType: input.AuthType, Password: input.Password, } err := r.DB.CreateUser(user) if err != nil { return nil, err } return user, nil } func (r *queryResolver) Login(ctx context.Context, user string, password string, deviceID *string) (*model.AuthResponse, error) { // 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)} refreshCookie := http.Cookie{Name: "RefreshToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)} http.SetCookie(*resp, &accessCookie) http.SetCookie(*resp, &refreshCookie) // Do Login foundUser, success := r.Auth.AuthenticateUser(user, password) if !success { return &model.AuthResponse{Result: model.AuthResultFailure}, nil } // Upsert Device foundDevice := model.Device{UserID: foundUser.ID} if deviceID != nil { parsedDeviceID, err := uuid.Parse(*deviceID) if err != nil { return &model.AuthResponse{Result: model.AuthResultFailure}, nil } foundDevice.ID = parsedDeviceID.String() count, err := r.DB.Device(&foundDevice) if count != 1 || err != nil { return &model.AuthResponse{Result: model.AuthResultFailure}, nil } } else { foundDevice.Type = deriveDeviceType(req) err := r.DB.CreateDevice(&foundDevice) if err != nil { return &model.AuthResponse{Result: model.AuthResultFailure}, nil } } // Create Tokens accessToken, err := r.Auth.CreateJWTAccessToken(foundUser, foundDevice) if err != nil { return &model.AuthResponse{Result: model.AuthResultFailure}, nil } refreshToken, err := r.Auth.CreateJWTRefreshToken(foundUser, foundDevice) if err != nil { return &model.AuthResponse{Result: model.AuthResultFailure}, nil } // Set appropriate cookies (TODO: Only for web!) accessCookie = http.Cookie{Name: "AccessToken", Value: accessToken, Path: "/", HttpOnly: false} refreshCookie = http.Cookie{Name: "RefreshToken", Value: refreshToken, Path: "/", HttpOnly: false} http.SetCookie(*resp, &accessCookie) http.SetCookie(*resp, &refreshCookie) // Only for iOS & Android (TODO: Remove for web! Only cause affected by CORS during development) (*resp).Header().Set("X-Imagini-AccessToken", accessToken) (*resp).Header().Set("X-Imagini-RefreshToken", refreshToken) return &model.AuthResponse{Result: model.AuthResultSuccess, Device: &foundDevice}, nil } func (r *queryResolver) Logout(ctx context.Context) (*model.AuthResponse, error) { // 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)} refreshCookie := http.Cookie{Name: "RefreshToken", Path: "/", HttpOnly: true, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour)} http.SetCookie(*resp, &accessCookie) http.SetCookie(*resp, &refreshCookie) return &model.AuthResponse{Result: model.AuthResultSuccess}, nil } 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) if err != nil { return nil, errors.New("Invalid ID Format") } foundMediaItem := &model.MediaItem{ID: mediaItemID.String(), UserID: userID} count, err := r.DB.MediaItem(foundMediaItem) if err != nil { return nil, errors.New("DB Error") } else if count != 1 { return nil, errors.New("MediaItem Not Found") } return foundMediaItem, nil } 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) if err != nil { return nil, errors.New("Invalid ID Format") } foundDevice := &model.Device{ID: deviceID.String(), UserID: userID} count, err := r.DB.Device(foundDevice) if err != nil { return nil, errors.New("DB Error") } else if count != 1 { return nil, errors.New("Device Not Found") } return foundDevice, nil } 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) if err != nil { return nil, errors.New("Invalid ID Format") } foundAlbum := &model.Album{ID: albumID.String(), UserID: userID} count, err := r.DB.Album(foundAlbum) if err != nil { return nil, errors.New("DB Error") } else if count != 1 { return nil, errors.New("Album Not Found") } return foundAlbum, nil } 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") } foundUser := &model.User{ID: userID.String()} count, err := r.DB.User(foundUser) if err != nil { return nil, errors.New("DB Error") } else if count != 1 { return nil, errors.New("User Not Found") } return foundUser, nil } 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(), UserID: userID} count, err := r.DB.Tag(foundTag) if err != nil { return nil, errors.New("DB Error") } else if count != 1 { return nil, errors.New("Tag Not Found") } return foundTag, nil } 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} count, err := r.DB.User(foundUser) if err != nil || count != 1 { return nil, errors.New("DB Error") } return foundUser, nil } 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, 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, Page: &pageResponse, }, nil } 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 } resp, pageResponse, err := r.DB.Devices(userID, filter, page, order) if err != nil { return nil, errors.New("DB Error") } return &model.DeviceResponse{ Data: resp, Page: &pageResponse, }, nil } 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, Page: &pageResponse, }, nil } 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, Page: &pageResponse, }, nil } 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, Page: &pageResponse, }, nil } // Mutation returns generated.MutationResolver implementation. func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver }