Merge branch 'master' of github.com:viktorstrate/photoview into favorites-checkobox-on-photos-and-album-page-viktorstrate/photoview#6
This commit is contained in:
commit
75e43aae80
|
@ -1,42 +0,0 @@
|
|||
name: API
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: api
|
||||
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
if [ -f Gopkg.toml ]; then
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
run: go build -v .
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
|
@ -0,0 +1,90 @@
|
|||
name: Docker builds
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 10 * * *" # everyday at 10am
|
||||
pull_request:
|
||||
branches: master
|
||||
push:
|
||||
branches: master
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and deploy docker images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
id: cache
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
DOCKER_USERNAME=viktorstrate
|
||||
DOCKER_IMAGE=viktorstrate/photoview
|
||||
DOCKER_PLATFORMS=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
VERSION=edge
|
||||
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/v}
|
||||
fi
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
VERSION=nightly
|
||||
fi
|
||||
|
||||
TAGS="--tag ${DOCKER_IMAGE}:${VERSION}"
|
||||
if [[ $VERSION =~ ^(([0-9]{1,3})\.[0-9]{1,3})\.[0-9]{1,3}$ ]]; then
|
||||
VERSION_MINOR=${BASH_REMATCH[1]}
|
||||
VERSION_MAJOR=${BASH_REMATCH[2]}
|
||||
TAGS="$TAGS --tag ${DOCKER_IMAGE}:latest --tag ${DOCKER_IMAGE}:${VERSION_MINOR} --tag ${DOCKER_IMAGE}:${VERSION_MAJOR}"
|
||||
fi
|
||||
|
||||
echo ::set-output name=docker_username::${DOCKER_USERNAME}
|
||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
||||
echo ::set-output name=version::${VERSION}
|
||||
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \
|
||||
--cache-from "type=local,src=/tmp/.buildx-cache" \
|
||||
--cache-to "type=local,dest=/tmp/.buildx-cache" \
|
||||
--build-arg VERSION=${VERSION} \
|
||||
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
|
||||
--build-arg VCS_REF=${GITHUB_SHA::8} \
|
||||
${TAGS} --file Dockerfile .
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v3
|
||||
|
||||
- name: Docker Buildx (build)
|
||||
run: |
|
||||
docker buildx build --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args }}
|
||||
|
||||
- name: Docker Login
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
echo "${DOCKER_PASSWORD}" | docker login --username "${{ steps.prepare.outputs.docker_username }}" --password-stdin
|
||||
|
||||
- name: Docker Buildx (push)
|
||||
if: success() && github.event_name != 'pull_request'
|
||||
run: |
|
||||
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }}
|
||||
|
||||
- name: Docker Check Manifest
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
run: |
|
||||
docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }}
|
||||
|
||||
- name: Clear
|
||||
if: always() && github.event_name != 'pull_request'
|
||||
run: |
|
||||
rm -f ${HOME}/.docker/config.json
|
|
@ -0,0 +1,40 @@
|
|||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test-api:
|
||||
name: Test API
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: api
|
||||
|
||||
steps:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
if [ -f Gopkg.toml ]; then
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
run: go build -v .
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
|
@ -1,5 +1,3 @@
|
|||
{
|
||||
"eslint.workingDirectories": [
|
||||
"ui", "api"
|
||||
]
|
||||
"eslint.workingDirectories": ["ui"]
|
||||
}
|
21
Dockerfile
21
Dockerfile
|
@ -1,5 +1,5 @@
|
|||
# Build UI
|
||||
FROM node:10 as ui
|
||||
FROM --platform=${BUILDPLATFORM:-linux/amd64} node:10 as ui
|
||||
|
||||
ARG API_ENDPOINT
|
||||
ENV API_ENDPOINT=${API_ENDPOINT}
|
||||
|
@ -20,7 +20,8 @@ COPY ui /app
|
|||
RUN npm run build -- --public-url $UI_PUBLIC_URL
|
||||
|
||||
# Build API
|
||||
FROM golang:alpine AS api
|
||||
FROM --platform=${BUILDPLATFORM:-linux/amd64} tonistiigi/xx:golang AS xgo
|
||||
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.14-alpine AS api
|
||||
|
||||
RUN mkdir -p /app
|
||||
WORKDIR /app
|
||||
|
@ -32,16 +33,20 @@ RUN go mod download
|
|||
# Copy api source
|
||||
COPY api /app
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o photoview .
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN go env
|
||||
RUN go build -v -o photoview .
|
||||
|
||||
# Copy api and ui to production environment
|
||||
FROM alpine:3.12
|
||||
|
||||
# Install darktable for converting RAW images
|
||||
RUN apk --no-cache add darktable
|
||||
|
||||
# Install ffmpeg for encoding videos
|
||||
RUN apk --no-cache add ffmpeg
|
||||
# Install darktable for converting RAW images, and ffmpeg for encoding videos
|
||||
# Ignore errors if packages are not supported for the specified platform
|
||||
RUN apk --no-cache add darktable; exit 0
|
||||
RUN apk --no-cache add ffmpeg; exit 0
|
||||
|
||||
COPY --from=ui /app/dist /ui
|
||||
COPY --from=api /app/database/migrations /database/migrations
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
[![License](https://img.shields.io/github/license/viktorstrate/photoview)](./LICENSE.md)
|
||||
[![GitHub contributors](https://img.shields.io/github/contributors/viktorstrate/photoview)](https://github.com/viktorstrate/photoview/graphs/contributors)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/viktorstrate/photoview)](https://hub.docker.com/r/viktorstrate/photoview)
|
||||
[![Docker Build Status](https://img.shields.io/docker/cloud/build/viktorstrate/photoview)](https://hub.docker.com/r/viktorstrate/photoview/builds)
|
||||
[![Docker Build Status](https://img.shields.io/github/workflow/status/viktorstrate/photoview/Docker%20builds?label=docker%20build)](https://hub.docker.com/r/viktorstrate/photoview/)
|
||||
|
||||
![screenshot](./screenshots/main-window.png)
|
||||
|
||||
|
@ -30,9 +30,11 @@ Password: **demo**
|
|||
|
||||
- **Closely tied to the file system**. The website presents the images found on the local filesystem of the server, directories are mapped to albums.
|
||||
- **User management**. Each user is created along with a path on the local filesystem, photos within that path can be accessed by that user.
|
||||
- **Photo sharing**. Photos and albums can easily be shared with other users or publicly with a unique URL.
|
||||
- **Made for photography**. The website is ment as a way to present photographies, and thus supports **RAW** file formats, and **EXIF** parsing.
|
||||
- **Sharing**. Albums, as well as individual media, can easily be shared with a public link, the link can optinally be password protected.
|
||||
- **Made for photography**. Photoview is built with photographers in mind, and thus supports **RAW** file formats, and **EXIF** parsing.
|
||||
- **Video support**. Many common video formats are supported. Videos will automatically be optimized for web.
|
||||
- **Performant**. Thumbnails are automatically generated and photos first load when they are visible on the screen. In full screen, thumbnails are displayed until the high resolution image has been fully loaded.
|
||||
- **Secure**. All media resources are protected with a cookie-token, all passwords are properly hashed, and the API uses a strict [CORS policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
|
||||
|
||||
## Why yet another self-hosted photo gallery
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
DROP TABLE IF EXISTS site_info;
|
||||
DROP TABLE IF EXISTS access_token;
|
||||
DROP TABLE IF EXISTS media_url;
|
||||
DROP TABLE IF EXISTS share_token;
|
||||
DROP TABLE IF EXISTS media;
|
||||
DROP TABLE IF EXISTS video_metadata;
|
||||
DROP TABLE IF EXISTS media_exif;
|
||||
DROP TABLE IF EXISTS album;
|
||||
DROP TABLE IF EXISTS user;
|
|
@ -0,0 +1,120 @@
|
|||
-- Users and authentication
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
user_id int NOT NULL AUTO_INCREMENT,
|
||||
username varchar(256) NOT NULL UNIQUE,
|
||||
password varchar(256),
|
||||
root_path varchar(512),
|
||||
admin boolean NOT NULL DEFAULT 0,
|
||||
|
||||
PRIMARY KEY (user_id)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS access_token (
|
||||
token_id int NOT NULL AUTO_INCREMENT,
|
||||
user_id int NOT NULL,
|
||||
value char(24) NOT NULL UNIQUE,
|
||||
expire timestamp NOT NULL,
|
||||
|
||||
PRIMARY KEY (token_id),
|
||||
FOREIGN KEY (user_id) REFERENCES user(user_id) ON DELETE CASCADE
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS site_info (
|
||||
initial_setup boolean NOT NULL DEFAULT TRUE
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Video related
|
||||
CREATE TABLE IF NOT EXISTS video_metadata (
|
||||
metadata_id int NOT NULL AUTO_INCREMENT,
|
||||
|
||||
width int(6) NOT NULL,
|
||||
height int(6) NOT NULL,
|
||||
duration double NOT NULL,
|
||||
codec varchar(128),
|
||||
framerate double,
|
||||
bitrate int(24),
|
||||
color_profile varchar(128),
|
||||
audio varchar(128),
|
||||
|
||||
PRIMARY KEY (metadata_id)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Media related
|
||||
CREATE TABLE IF NOT EXISTS album (
|
||||
album_id int NOT NULL AUTO_INCREMENT,
|
||||
title varchar(256) NOT NULL,
|
||||
parent_album int,
|
||||
owner_id int NOT NULL,
|
||||
path varchar(1024) NOT NULL,
|
||||
path_hash varchar(32) NOT NULL UNIQUE,
|
||||
|
||||
PRIMARY KEY (album_id),
|
||||
FOREIGN KEY (parent_album) REFERENCES album(album_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (owner_id) REFERENCES user(user_id) ON DELETE CASCADE
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS media_exif (
|
||||
exif_id int NOT NULL AUTO_INCREMENT,
|
||||
camera varchar(256),
|
||||
maker varchar(256),
|
||||
lens varchar(256),
|
||||
date_shot timestamp NULL,
|
||||
exposure varchar(256),
|
||||
aperture float,
|
||||
iso int(6),
|
||||
focal_length float,
|
||||
flash varchar(256),
|
||||
orientation int(1),
|
||||
exposure_program int(1),
|
||||
gps_latitude float,
|
||||
gps_longitude float,
|
||||
|
||||
PRIMARY KEY (exif_id)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS media (
|
||||
media_id int NOT NULL AUTO_INCREMENT,
|
||||
title varchar(256) NOT NULL,
|
||||
path varchar(1024) NOT NULL,
|
||||
path_hash varchar(32) NOT NULL UNIQUE,
|
||||
album_id int NOT NULL,
|
||||
exif_id int,
|
||||
date_shot datetime NOT NULL,
|
||||
date_imported datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
favorite boolean DEFAULT FALSE,
|
||||
media_type varchar(64) NOT NULL,
|
||||
video_metadata_id int,
|
||||
|
||||
PRIMARY KEY (media_id),
|
||||
FOREIGN KEY (album_id) REFERENCES album(album_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (exif_id) REFERENCES media_exif(exif_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (video_metadata_id) REFERENCES video_metadata(metadata_id) ON DELETE CASCADE
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS media_url (
|
||||
url_id int NOT NULL AUTO_INCREMENT,
|
||||
media_id int NOT NULL,
|
||||
media_name varchar(512) NOT NULL,
|
||||
width int NOT NULL,
|
||||
height int NOT NULL,
|
||||
purpose varchar(64) NOT NULL,
|
||||
content_type varchar(64) NOT NULL,
|
||||
file_size int NOT NULL,
|
||||
|
||||
PRIMARY KEY (url_id),
|
||||
FOREIGN KEY (media_id) REFERENCES media(media_id) ON DELETE CASCADE
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Public shares
|
||||
CREATE TABLE IF NOT EXISTS share_token (
|
||||
token_id int AUTO_INCREMENT,
|
||||
value char(24) NOT NULL UNIQUE,
|
||||
owner_id int NOT NULL,
|
||||
expire timestamp NULL DEFAULT NULL,
|
||||
password varchar(256),
|
||||
album_id int,
|
||||
media_id int,
|
||||
|
||||
PRIMARY KEY (token_id)
|
||||
-- CHECK (album_id IS NOT NULL OR media_id IS NOT NULL)
|
||||
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
@ -1,2 +0,0 @@
|
|||
DROP TABLE IF EXISTS user;
|
||||
DROP TABLE IF NOT EXISTS access_token;
|
|
@ -1,19 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS user (
|
||||
user_id int NOT NULL AUTO_INCREMENT,
|
||||
username varchar(256) NOT NULL UNIQUE,
|
||||
password varchar(256),
|
||||
root_path varchar(512),
|
||||
admin boolean NOT NULL DEFAULT 0,
|
||||
|
||||
PRIMARY KEY (user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS access_token (
|
||||
token_id int NOT NULL AUTO_INCREMENT,
|
||||
user_id int NOT NULL,
|
||||
value char(24) NOT NULL UNIQUE,
|
||||
expire timestamp NOT NULL,
|
||||
|
||||
PRIMARY KEY (token_id),
|
||||
FOREIGN KEY (user_id) REFERENCES user(user_id) ON DELETE CASCADE
|
||||
);
|
|
@ -1,4 +0,0 @@
|
|||
DROP TABLE IF EXISTS photo;
|
||||
DROP TABLE IF EXISTS album;
|
||||
DROP TABLE IF EXISTS photo_url;
|
||||
DROP TABLE IF EXISTS photo_exif;
|
|
@ -1,55 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS photo_exif (
|
||||
exif_id int NOT NULL AUTO_INCREMENT,
|
||||
camera varchar(256),
|
||||
maker varchar(256),
|
||||
lens varchar(256),
|
||||
dateShot timestamp NULL,
|
||||
exposure varchar(256),
|
||||
aperture float,
|
||||
iso int(6),
|
||||
focal_length float,
|
||||
flash varchar(256),
|
||||
orientation int(1),
|
||||
exposure_program int(1),
|
||||
|
||||
PRIMARY KEY (exif_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS album (
|
||||
album_id int NOT NULL AUTO_INCREMENT,
|
||||
title varchar(256) NOT NULL,
|
||||
parent_album int,
|
||||
owner_id int NOT NULL,
|
||||
path varchar(1024) NOT NULL,
|
||||
path_hash varchar(32) NOT NULL UNIQUE,
|
||||
|
||||
PRIMARY KEY (album_id),
|
||||
FOREIGN KEY (parent_album) REFERENCES album(album_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (owner_id) REFERENCES user(user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS photo (
|
||||
photo_id int NOT NULL AUTO_INCREMENT,
|
||||
title varchar(256) NOT NULL,
|
||||
path varchar(1024) NOT NULL,
|
||||
path_hash varchar(32) NOT NULL UNIQUE,
|
||||
album_id int NOT NULL,
|
||||
exif_id int,
|
||||
|
||||
PRIMARY KEY (photo_id),
|
||||
FOREIGN KEY (album_id) REFERENCES album(album_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (exif_id) REFERENCES photo_exif(exif_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS photo_url (
|
||||
url_id int NOT NULL AUTO_INCREMENT,
|
||||
photo_id int NOT NULL,
|
||||
photo_name varchar(512) NOT NULL,
|
||||
width int NOT NULL,
|
||||
height int NOT NULL,
|
||||
purpose varchar(64) NOT NULL,
|
||||
content_type varchar(64) NOT NULL,
|
||||
|
||||
PRIMARY KEY (url_id),
|
||||
FOREIGN KEY (photo_id) REFERENCES photo(photo_id) ON DELETE CASCADE
|
||||
);
|
|
@ -1 +0,0 @@
|
|||
DROP TABLE IF EXISTS site_info;
|
|
@ -1,3 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS site_info (
|
||||
initial_setup boolean NOT NULL DEFAULT TRUE
|
||||
);
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
DROP TABLE IF EXISTS share_token;
|
|
@ -1,12 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS share_token (
|
||||
token_id int AUTO_INCREMENT,
|
||||
value char(24) NOT NULL UNIQUE,
|
||||
owner_id int NOT NULL,
|
||||
expire timestamp NULL DEFAULT NULL,
|
||||
password varchar(256),
|
||||
album_id int,
|
||||
photo_id int,
|
||||
|
||||
PRIMARY KEY (token_id)
|
||||
-- CHECK (album_id IS NOT NULL OR photo_id IS NOT NULL)
|
||||
);
|
|
@ -1,9 +0,0 @@
|
|||
-- Migrate all tables in database to use utf8 for better language support
|
||||
ALTER TABLE access_token CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
||||
ALTER TABLE album CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
||||
ALTER TABLE photo CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
||||
ALTER TABLE photo_exif CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
||||
ALTER TABLE photo_url CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
||||
ALTER TABLE share_token CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
||||
ALTER TABLE site_info CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
||||
ALTER TABLE user CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
|
|
@ -1,9 +0,0 @@
|
|||
-- Migrate all tables in database to use utf8 for better language support
|
||||
ALTER TABLE access_token CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
ALTER TABLE album CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
ALTER TABLE photo CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
ALTER TABLE photo_exif CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
ALTER TABLE photo_url CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
ALTER TABLE share_token CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
ALTER TABLE site_info CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
ALTER TABLE user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
@ -1,2 +0,0 @@
|
|||
-- Add favorite attribute to photos
|
||||
ALTER TABLE photo DROP favorite
|
|
@ -1,2 +0,0 @@
|
|||
-- Add favorite attribute to photos
|
||||
ALTER TABLE photo ADD favorite BOOL DEFAULT false
|
|
@ -1,43 +0,0 @@
|
|||
-- Update database to hash indexed paths
|
||||
|
||||
CREATE PROCEDURE MigratePathHashIfNeeded()
|
||||
BEGIN
|
||||
|
||||
-- Add path hash for photo table if it doesn't exist
|
||||
IF NOT EXISTS( SELECT *
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'photo'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'path_hash') THEN
|
||||
|
||||
-- Remove unique index from photo.path
|
||||
ALTER TABLE photo DROP INDEX path;
|
||||
|
||||
-- Add path_hash and set it to the md5 hash based of the path attribute
|
||||
ALTER TABLE photo ADD path_hash varchar(32) AFTER path;
|
||||
UPDATE photo p SET path_hash = md5(p.path);
|
||||
ALTER TABLE photo MODIFY path_hash varchar(32) NOT NULL UNIQUE;
|
||||
|
||||
END IF;
|
||||
|
||||
-- Add path hash for album table if it doesn't exist
|
||||
IF NOT EXISTS( SELECT *
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'album'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'path_hash') THEN
|
||||
|
||||
-- Remove unique index from album.path
|
||||
ALTER TABLE album DROP INDEX path;
|
||||
|
||||
-- Add path_hash and set it to the md5 hash based of the path attribute
|
||||
ALTER TABLE album ADD path_hash varchar(32) AFTER path;
|
||||
UPDATE album a SET path_hash = md5(a.path);
|
||||
ALTER TABLE album MODIFY path_hash varchar(32) NOT NULL UNIQUE;
|
||||
|
||||
END IF;
|
||||
|
||||
END; -- MigratePathHashIfNeeded procedure end
|
||||
|
||||
CALL MigratePathHashIfNeeded();
|
||||
DROP PROCEDURE MigratePathHashIfNeeded;
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
ALTER TABLE media RENAME TO photo;
|
||||
ALTER TABLE media_url RENAME TO photo_url;
|
||||
ALTER TABLE media_exif RENAME TO photo_exif;
|
||||
|
||||
ALTER TABLE photo CHANGE COLUMN media_id photo_id int NOT NULL AUTO_INCREMENT;
|
||||
ALTER TABLE photo_url CHANGE COLUMN media_id photo_id int NOT NULL;
|
||||
ALTER TABLE photo_url CHANGE COLUMN media_name photo_name varchar(512) NOT NULL;
|
||||
ALTER TABLE share_token CHANGE COLUMN media_id photo_id int;
|
||||
|
||||
ALTER TABLE photo DROP COLUMN media_type;
|
||||
|
||||
ALTER TABLE photo
|
||||
DROP FOREIGN KEY photo_ibfk_3,
|
||||
DROP COLUMN video_metadata_id;
|
||||
|
||||
DROP TABLE video_metadata;
|
|
@ -1,31 +0,0 @@
|
|||
ALTER TABLE photo RENAME TO media;
|
||||
ALTER TABLE photo_url RENAME TO media_url;
|
||||
ALTER TABLE photo_exif RENAME TO media_exif;
|
||||
|
||||
ALTER TABLE media RENAME COLUMN photo_id TO media_id;
|
||||
|
||||
ALTER TABLE media_url
|
||||
RENAME COLUMN photo_id TO media_id,
|
||||
RENAME COLUMN photo_name TO media_name;
|
||||
|
||||
ALTER TABLE share_token RENAME COLUMN photo_id TO media_id;
|
||||
|
||||
CREATE TABLE video_metadata (
|
||||
metadata_id int NOT NULL AUTO_INCREMENT,
|
||||
|
||||
width int(6) NOT NULL,
|
||||
height int(6) NOT NULL,
|
||||
duration double NOT NULL,
|
||||
codec varchar(128),
|
||||
framerate double,
|
||||
bitrate int(24),
|
||||
color_profile varchar(128),
|
||||
audio varchar(128),
|
||||
|
||||
PRIMARY KEY (metadata_id)
|
||||
);
|
||||
|
||||
ALTER TABLE media
|
||||
ADD COLUMN media_type varchar(64) NOT NULL DEFAULT "photo",
|
||||
ADD COLUMN video_metadata_id int,
|
||||
ADD FOREIGN KEY (video_metadata_id) REFERENCES video_metadata(metadata_id);
|
18
api/go.mod
18
api/go.mod
|
@ -3,9 +3,9 @@ module github.com/viktorstrate/photoview/api
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.11.3
|
||||
github.com/99designs/gqlgen v0.12.1
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/agnivade/levenshtein v1.1.0 // indirect
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v1.13.1 // indirect
|
||||
|
@ -18,21 +18,15 @@ require (
|
|||
github.com/h2non/filetype v1.1.0
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/matryer/moq v0.0.0-20200607124540-4638a53893e6 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/urfave/cli v1.22.4 // indirect
|
||||
github.com/urfave/cli/v2 v2.2.0 // indirect
|
||||
github.com/vektah/dataloaden v0.3.0 // indirect
|
||||
github.com/vektah/gqlparser v1.3.1 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.0.1
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
||||
github.com/xor-gate/goexif2 v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/tools v0.0.0-20200622192924-4fd1c64487bf // indirect
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc // indirect
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.0.2
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
)
|
||||
|
|
81
api/go.sum
81
api/go.sum
|
@ -1,9 +1,5 @@
|
|||
github.com/99designs/gqlgen v0.10.2 h1:FfjCqIWejHDJeLpQTI0neoZo5vDO3sdo5oNCucet3A0=
|
||||
github.com/99designs/gqlgen v0.10.2/go.mod h1:aDB7oabSAyZ4kUHLEySsLxnWrBy3lA0A2gWKU+qoHwI=
|
||||
github.com/99designs/gqlgen v0.11.2 h1:qatIx2DY7YyaUIBd47ORY3Aj/+pJsPLoL7tyuuISJR0=
|
||||
github.com/99designs/gqlgen v0.11.2/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
|
||||
github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4=
|
||||
github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
|
||||
github.com/99designs/gqlgen v0.12.1 h1:Qfi6HDi6uDxGVKvz5kg8/5iP9YF2XqhwIoBKAt+Nt6M=
|
||||
github.com/99designs/gqlgen v0.12.1/go.mod h1:7zdGo6ry9u1YBp/qlb2uxSU5Mt2jQKLcBETQiKk+Bxo=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
|
@ -11,19 +7,21 @@ github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+s
|
|||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
|
||||
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
|
||||
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
|
||||
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
|
||||
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
|
@ -46,16 +44,8 @@ github.com/gorilla/mux v1.6.1 h1:KOwqsTYZdeuMacU7CxjMNYEKeBvLbxW+psodrbcEa3A=
|
|||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/h2non/filetype v1.0.10 h1:z+SJfnL6thYJ9kAST+6nPRXp1lMxnOVbMZHNYHMar0s=
|
||||
github.com/h2non/filetype v1.0.10/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU=
|
||||
github.com/h2non/filetype v1.0.12 h1:yHCsIe0y2cvbDARtJhGBTD2ecvqMSTvlIcph9En/Zao=
|
||||
github.com/h2non/filetype v1.0.12/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/h2non/filetype v1.1.0 h1:Or/gjocJrJRNK/Cri/TDEKFjAR+cfG6eK65NGYB6gBA=
|
||||
github.com/h2non/filetype v1.1.0/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
|
@ -73,19 +63,13 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/matryer/moq v0.0.0-20200607124540-4638a53893e6 h1:Cx1ZvZ3SQTli1nKee9qvJ/NJP3vt11s+ilM7NF3QSL8=
|
||||
github.com/matryer/moq v0.0.0-20200607124540-4638a53893e6/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
|
||||
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/nf/cr2 v0.0.0-20180623103828-4699471a17ed h1:QP63yO3XEt8tJ1DgBsNjbOyBGjy2eHy9ITK4Eisr9rg=
|
||||
github.com/nf/cr2 v0.0.0-20180623103828-4699471a17ed/go.mod h1:HazDB3gS/i//QXMMRmTAV7Ni9gAi4mDNTH2HjZ5aVgU=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
|
@ -117,22 +101,10 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.3 h1:FpNT6zq26xNpHZy08emi755QwzLPs6Pukqjlc7RfOMU=
|
||||
github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
|
||||
github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/gqlparser v1.2.0 h1:ntkSCX7F5ZJKl+HIVnmLaO269MruasVpNiMOjX9kgo0=
|
||||
github.com/vektah/gqlparser v1.2.0/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
|
||||
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
|
||||
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
|
||||
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
|
||||
|
@ -141,59 +113,44 @@ github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlV
|
|||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
|
||||
github.com/xor-gate/goexif2 v1.1.0 h1:OvTZ5iEvsDhRWFjV5xY3wT7uHFna28nSSP7ucau+cXQ=
|
||||
github.com/xor-gate/goexif2 v1.1.0/go.mod h1:eRjn3VSkAwpNpxEx/CGmd0zg0JFGL3akrSMxnJ581AY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw=
|
||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
|
||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200622192924-4fd1c64487bf h1:nXhK+swoyjE2slxjyxxa36VklQeCrnFyGuIZQGUsuxY=
|
||||
golang.org/x/tools v0.0.0-20200622192924-4fd1c64487bf/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.0.2 h1:DdxSfFnlqeawPIVbIQEI6LR6OQHQNR7tNgWb2mWuC4w=
|
||||
|
|
|
@ -87,10 +87,8 @@ type ComplexityRoot struct {
|
|||
}
|
||||
|
||||
MediaDownload struct {
|
||||
Height func(childComplexity int) int
|
||||
MediaURL func(childComplexity int) int
|
||||
Title func(childComplexity int) int
|
||||
URL func(childComplexity int) int
|
||||
Width func(childComplexity int) int
|
||||
}
|
||||
|
||||
MediaExif struct {
|
||||
|
@ -109,6 +107,7 @@ type ComplexityRoot struct {
|
|||
}
|
||||
|
||||
MediaURL struct {
|
||||
FileSize func(childComplexity int) int
|
||||
Height func(childComplexity int) int
|
||||
URL func(childComplexity int) int
|
||||
Width func(childComplexity int) int
|
||||
|
@ -472,12 +471,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Media.VideoWeb(childComplexity), true
|
||||
|
||||
case "MediaDownload.height":
|
||||
if e.complexity.MediaDownload.Height == nil {
|
||||
case "MediaDownload.mediaUrl":
|
||||
if e.complexity.MediaDownload.MediaURL == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.MediaDownload.Height(childComplexity), true
|
||||
return e.complexity.MediaDownload.MediaURL(childComplexity), true
|
||||
|
||||
case "MediaDownload.title":
|
||||
if e.complexity.MediaDownload.Title == nil {
|
||||
|
@ -486,20 +485,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.MediaDownload.Title(childComplexity), true
|
||||
|
||||
case "MediaDownload.url":
|
||||
if e.complexity.MediaDownload.URL == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.MediaDownload.URL(childComplexity), true
|
||||
|
||||
case "MediaDownload.width":
|
||||
if e.complexity.MediaDownload.Width == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.MediaDownload.Width(childComplexity), true
|
||||
|
||||
case "MediaEXIF.aperture":
|
||||
if e.complexity.MediaExif.Aperture == nil {
|
||||
break
|
||||
|
@ -584,6 +569,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.MediaExif.Media(childComplexity), true
|
||||
|
||||
case "MediaURL.fileSize":
|
||||
if e.complexity.MediaURL.FileSize == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.MediaURL.FileSize(childComplexity), true
|
||||
|
||||
case "MediaURL.height":
|
||||
if e.complexity.MediaURL.Height == nil {
|
||||
break
|
||||
|
@ -1410,13 +1402,13 @@ type MediaURL {
|
|||
width: Int!
|
||||
"Height of the image in pixels"
|
||||
height: Int!
|
||||
"The file size of the resource in bytes"
|
||||
fileSize: Int!
|
||||
}
|
||||
|
||||
type MediaDownload {
|
||||
title: String!
|
||||
width: Int!
|
||||
height: Int!
|
||||
url: String!
|
||||
mediaUrl: MediaURL!
|
||||
}
|
||||
|
||||
enum MediaType {
|
||||
|
@ -2971,7 +2963,7 @@ func (ec *executionContext) _MediaDownload_title(ctx context.Context, field grap
|
|||
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MediaDownload_width(ctx context.Context, field graphql.CollectedField, obj *models.MediaDownload) (ret graphql.Marshaler) {
|
||||
func (ec *executionContext) _MediaDownload_mediaUrl(ctx context.Context, field graphql.CollectedField, obj *models.MediaDownload) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
|
@ -2988,7 +2980,7 @@ func (ec *executionContext) _MediaDownload_width(ctx context.Context, field grap
|
|||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Width, nil
|
||||
return obj.MediaURL, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
|
@ -3000,77 +2992,9 @@ func (ec *executionContext) _MediaDownload_width(ctx context.Context, field grap
|
|||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(int)
|
||||
res := resTmp.(*models.MediaURL)
|
||||
fc.Result = res
|
||||
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MediaDownload_height(ctx context.Context, field graphql.CollectedField, obj *models.MediaDownload) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "MediaDownload",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Height, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(int)
|
||||
fc.Result = res
|
||||
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MediaDownload_url(ctx context.Context, field graphql.CollectedField, obj *models.MediaDownload) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "MediaDownload",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.URL, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(string)
|
||||
fc.Result = res
|
||||
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||
return ec.marshalNMediaURL2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMediaURL(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MediaEXIF_id(ctx context.Context, field graphql.CollectedField, obj *models.MediaEXIF) (ret graphql.Marshaler) {
|
||||
|
@ -3553,6 +3477,40 @@ func (ec *executionContext) _MediaURL_height(ctx context.Context, field graphql.
|
|||
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _MediaURL_fileSize(ctx context.Context, field graphql.CollectedField, obj *models.MediaURL) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "MediaURL",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.FileSize, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(int)
|
||||
fc.Result = res
|
||||
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_authorizeUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
@ -7325,18 +7283,8 @@ func (ec *executionContext) _MediaDownload(ctx context.Context, sel ast.Selectio
|
|||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "width":
|
||||
out.Values[i] = ec._MediaDownload_width(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "height":
|
||||
out.Values[i] = ec._MediaDownload_height(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "url":
|
||||
out.Values[i] = ec._MediaDownload_url(ctx, field, obj)
|
||||
case "mediaUrl":
|
||||
out.Values[i] = ec._MediaDownload_mediaUrl(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
|
@ -7429,6 +7377,11 @@ func (ec *executionContext) _MediaURL(ctx context.Context, sel ast.SelectionSet,
|
|||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "fileSize":
|
||||
out.Values[i] = ec._MediaURL_fileSize(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
|
|
@ -23,9 +23,7 @@ type Filter struct {
|
|||
|
||||
type MediaDownload struct {
|
||||
Title string `json:"title"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
URL string `json:"url"`
|
||||
MediaURL *MediaURL `json:"mediaUrl"`
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
|
|
|
@ -3,6 +3,7 @@ package models
|
|||
import (
|
||||
"database/sql"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/viktorstrate/photoview/api/utils"
|
||||
)
|
||||
|
@ -14,6 +15,8 @@ type Media struct {
|
|||
PathHash string
|
||||
AlbumId int
|
||||
ExifId *int
|
||||
DateShot time.Time
|
||||
DateImported time.Time
|
||||
Favorite bool
|
||||
Type MediaType
|
||||
VideoMetadataId *int
|
||||
|
@ -41,12 +44,13 @@ type MediaURL struct {
|
|||
Height int
|
||||
Purpose MediaPurpose
|
||||
ContentType string
|
||||
FileSize int
|
||||
}
|
||||
|
||||
func NewMediaFromRow(row *sql.Row) (*Media, error) {
|
||||
media := Media{}
|
||||
|
||||
if err := row.Scan(&media.MediaID, &media.Title, &media.Path, &media.PathHash, &media.AlbumId, &media.ExifId, &media.Favorite, &media.Type, &media.VideoMetadataId); err != nil {
|
||||
if err := row.Scan(&media.MediaID, &media.Title, &media.Path, &media.PathHash, &media.AlbumId, &media.ExifId, &media.DateShot, &media.DateImported, &media.Favorite, &media.Type, &media.VideoMetadataId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -58,7 +62,7 @@ func NewMediaFromRows(rows *sql.Rows) ([]*Media, error) {
|
|||
|
||||
for rows.Next() {
|
||||
var media Media
|
||||
if err := rows.Scan(&media.MediaID, &media.Title, &media.Path, &media.PathHash, &media.AlbumId, &media.ExifId, &media.Favorite, &media.Type, &media.VideoMetadataId); err != nil {
|
||||
if err := rows.Scan(&media.MediaID, &media.Title, &media.Path, &media.PathHash, &media.AlbumId, &media.ExifId, &media.DateShot, &media.DateImported, &media.Favorite, &media.Type, &media.VideoMetadataId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
medias = append(medias, &media)
|
||||
|
@ -84,7 +88,7 @@ func (p *MediaURL) URL() string {
|
|||
func NewMediaURLFromRow(row *sql.Row) (*MediaURL, error) {
|
||||
url := MediaURL{}
|
||||
|
||||
if err := row.Scan(&url.UrlID, &url.MediaId, &url.MediaName, &url.Width, &url.Height, &url.Purpose, &url.ContentType); err != nil {
|
||||
if err := row.Scan(&url.UrlID, &url.MediaId, &url.MediaName, &url.Width, &url.Height, &url.Purpose, &url.ContentType, &url.FileSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -96,7 +100,7 @@ func NewMediaURLFromRows(rows *sql.Rows) ([]*MediaURL, error) {
|
|||
|
||||
for rows.Next() {
|
||||
var url MediaURL
|
||||
if err := rows.Scan(&url.UrlID, &url.MediaId, &url.MediaName, &url.Width, &url.Height, &url.Purpose, &url.ContentType); err != nil {
|
||||
if err := rows.Scan(&url.UrlID, &url.MediaId, &url.MediaName, &url.Width, &url.Height, &url.Purpose, &url.ContentType, &url.FileSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
urls = append(urls, &url)
|
||||
|
|
|
@ -18,6 +18,8 @@ type MediaEXIF struct {
|
|||
Flash *string
|
||||
Orientation *int
|
||||
ExposureProgram *int
|
||||
GPSLatitude *float64
|
||||
GPSLonitude *float64
|
||||
}
|
||||
|
||||
func (exif *MediaEXIF) Media() *Media {
|
||||
|
@ -31,7 +33,7 @@ func (exif *MediaEXIF) ID() int {
|
|||
func NewMediaExifFromRow(row *sql.Row) (*MediaEXIF, error) {
|
||||
exif := MediaEXIF{}
|
||||
|
||||
if err := row.Scan(&exif.ExifID, &exif.Camera, &exif.Maker, &exif.Lens, &exif.DateShot, &exif.Exposure, &exif.Aperture, &exif.Iso, &exif.FocalLength, &exif.Flash, &exif.Orientation, &exif.ExposureProgram); err != nil {
|
||||
if err := row.Scan(&exif.ExifID, &exif.Camera, &exif.Maker, &exif.Lens, &exif.DateShot, &exif.Exposure, &exif.Aperture, &exif.Iso, &exif.FocalLength, &exif.Flash, &exif.Orientation, &exif.ExposureProgram, &exif.GPSLatitude, &exif.GPSLonitude); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -109,9 +109,7 @@ func (r *mediaResolver) Downloads(ctx context.Context, obj *models.Media) ([]*mo
|
|||
|
||||
downloads = append(downloads, &models.MediaDownload{
|
||||
Title: title,
|
||||
Width: url.Width,
|
||||
Height: url.Height,
|
||||
URL: url.URL(),
|
||||
MediaURL: url,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -195,13 +195,13 @@ type MediaURL {
|
|||
width: Int!
|
||||
"Height of the image in pixels"
|
||||
height: Int!
|
||||
"The file size of the resource in bytes"
|
||||
fileSize: Int!
|
||||
}
|
||||
|
||||
type MediaDownload {
|
||||
title: String!
|
||||
width: Int!
|
||||
height: Int!
|
||||
url: String!
|
||||
mediaUrl: MediaURL!
|
||||
}
|
||||
|
||||
enum MediaType {
|
||||
|
|
|
@ -11,27 +11,33 @@ import (
|
|||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
)
|
||||
|
||||
func CleanupMedia(db *sql.DB, albumId int, albumPhotos []*models.Media) []error {
|
||||
if len(albumPhotos) == 0 {
|
||||
return nil
|
||||
func CleanupMedia(db *sql.DB, albumId int, albumMedia []*models.Media) []error {
|
||||
albumMediaIds := make([]interface{}, len(albumMedia))
|
||||
for i, photo := range albumMedia {
|
||||
albumMediaIds[i] = photo.MediaID
|
||||
}
|
||||
|
||||
albumPhotoIds := make([]interface{}, len(albumPhotos))
|
||||
for i, photo := range albumPhotos {
|
||||
albumPhotoIds[i] = photo.MediaID
|
||||
}
|
||||
// Delete missing media
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
|
||||
// Delete missing photos
|
||||
// Select media from database that was not found on hard disk
|
||||
if len(albumMedia) > 0 {
|
||||
media_args := make([]interface{}, 0)
|
||||
media_args = append(media_args, albumId)
|
||||
media_args = append(media_args, albumPhotoIds...)
|
||||
media_args = append(media_args, albumMediaIds...)
|
||||
|
||||
media_questions := strings.Repeat("?,", len(albumPhotoIds))[:len(albumPhotoIds)*2-1]
|
||||
|
||||
rows, err := db.Query(
|
||||
media_questions := strings.Repeat("?,", len(albumMediaIds))[:len(albumMediaIds)*2-1]
|
||||
rows, err = db.Query(
|
||||
"SELECT media_id FROM media WHERE album_id = ? AND media_id NOT IN ("+media_questions+")",
|
||||
media_args...,
|
||||
)
|
||||
} else {
|
||||
rows, err = db.Query(
|
||||
"SELECT media_id FROM media WHERE album_id = ?",
|
||||
albumId,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return []error{errors.Wrap(err, "get media files to be deleted from database")}
|
||||
}
|
||||
|
@ -56,7 +62,7 @@ func CleanupMedia(db *sql.DB, albumId int, albumPhotos []*models.Media) []error
|
|||
}
|
||||
|
||||
if len(deleted_media_ids) > 0 {
|
||||
media_questions = strings.Repeat("?,", len(deleted_media_ids))[:len(deleted_media_ids)*2-1]
|
||||
media_questions := strings.Repeat("?,", len(deleted_media_ids))[:len(deleted_media_ids)*2-1]
|
||||
|
||||
if _, err := db.Exec("DELETE FROM media WHERE media_id IN ("+media_questions+")", deleted_media_ids...); err != nil {
|
||||
deleteErrors = append(deleteErrors, errors.Wrap(err, "delete old media from database"))
|
||||
|
|
|
@ -18,6 +18,21 @@ type PhotoDimensions struct {
|
|||
Height int
|
||||
}
|
||||
|
||||
func DecodeImage(imagePath string) (image.Image, error) {
|
||||
file, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to open file to decode image (%s)", imagePath)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
image, err := imaging.Decode(file, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode image (%s)", imagePath)
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func PhotoDimensionsFromRect(rect image.Rectangle) PhotoDimensions {
|
||||
return PhotoDimensions{
|
||||
Width: rect.Bounds().Max.X,
|
||||
|
@ -133,13 +148,7 @@ func (img *EncodeMediaData) EncodeHighRes(tx *sql.Tx, outputPath string) error {
|
|||
}
|
||||
|
||||
func EncodeThumbnail(inputPath string, outputPath string) (*PhotoDimensions, error) {
|
||||
inputFile, err := os.Open(inputPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer inputFile.Close()
|
||||
|
||||
inputImage, _, err := image.Decode(inputFile)
|
||||
inputImage, err := DecodeImage(inputPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -161,13 +170,7 @@ func (img *EncodeMediaData) photoImage(tx *sql.Tx) (image.Image, error) {
|
|||
return img._photoImage, nil
|
||||
}
|
||||
|
||||
photoFile, err := os.Open(img.media.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer photoFile.Close()
|
||||
|
||||
photoImg, _, err := image.Decode(photoFile)
|
||||
photoImg, err := DecodeImage(img.media.Path)
|
||||
if err != nil {
|
||||
return nil, utils.HandleError("image decoding", err)
|
||||
}
|
||||
|
@ -184,37 +187,6 @@ func (img *EncodeMediaData) photoImage(tx *sql.Tx) (image.Image, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if orientation == nil {
|
||||
defaultOrientation := 0
|
||||
orientation = &defaultOrientation
|
||||
}
|
||||
|
||||
switch *orientation {
|
||||
case 2:
|
||||
photoImg = imaging.FlipH(photoImg)
|
||||
break
|
||||
case 3:
|
||||
photoImg = imaging.Rotate180(photoImg)
|
||||
break
|
||||
case 4:
|
||||
photoImg = imaging.FlipV(photoImg)
|
||||
break
|
||||
case 5:
|
||||
photoImg = imaging.Transpose(photoImg)
|
||||
break
|
||||
case 6:
|
||||
photoImg = imaging.Rotate270(photoImg)
|
||||
break
|
||||
case 7:
|
||||
photoImg = imaging.Transverse(photoImg)
|
||||
break
|
||||
case 8:
|
||||
photoImg = imaging.Rotate90(photoImg)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
img._photoImage = photoImg
|
||||
return img._photoImage, nil
|
||||
}
|
||||
|
|
|
@ -77,8 +77,13 @@ func ScanEXIF(tx *sql.Tx, media *models.Media) (returnExif *models.MediaEXIF, re
|
|||
|
||||
date, err := exifTags.DateTime()
|
||||
if err == nil {
|
||||
valueNames = append(valueNames, "dateShot")
|
||||
valueNames = append(valueNames, "date_shot")
|
||||
exifValues = append(exifValues, date)
|
||||
|
||||
_, err := tx.Exec("UPDATE media SET date_shot = ? WHERE media_id = ?", date, media.MediaID)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Failed to update date_shot for media %s: %s", media.Title, err)
|
||||
}
|
||||
}
|
||||
|
||||
exposure, err := readRationalTag(exifTags, exif.ExposureTime, media)
|
||||
|
@ -148,6 +153,15 @@ func ScanEXIF(tx *sql.Tx, media *models.Media) (returnExif *models.MediaEXIF, re
|
|||
exifValues = append(exifValues, *exposureProgram)
|
||||
}
|
||||
|
||||
lat, long, err := exifTags.LatLong()
|
||||
if err == nil {
|
||||
valueNames = append(valueNames, "gps_latitude")
|
||||
exifValues = append(exifValues, lat)
|
||||
|
||||
valueNames = append(valueNames, "gps_longitude")
|
||||
exifValues = append(exifValues, long)
|
||||
}
|
||||
|
||||
if len(valueNames) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
|
@ -265,16 +266,21 @@ func getMediaType(path string) (*MediaType, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func isPathMedia(path string, cache *AlbumScannerCache) bool {
|
||||
mediaType, err := cache.GetMediaType(path)
|
||||
func isPathMedia(mediaPath string, cache *AlbumScannerCache) bool {
|
||||
mediaType, err := cache.GetMediaType(mediaPath)
|
||||
if err != nil {
|
||||
ScannerError("%s (%s)", err, path)
|
||||
ScannerError("%s (%s)", err, mediaPath)
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore hidden files
|
||||
if path.Base(mediaPath)[0:1] == "." {
|
||||
return false
|
||||
}
|
||||
|
||||
if mediaType != nil {
|
||||
// Make sure file isn't empty
|
||||
fileStats, err := os.Stat(path)
|
||||
fileStats, err := os.Stat(mediaPath)
|
||||
if err != nil || fileStats.Size() == 0 {
|
||||
return false
|
||||
}
|
||||
|
@ -282,6 +288,6 @@ func isPathMedia(path string, cache *AlbumScannerCache) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
log.Printf("File is not a supported media %s\n", path)
|
||||
log.Printf("File is not a supported media %s\n", mediaPath)
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -128,8 +128,13 @@ func processPhoto(tx *sql.Tx, imageData *EncodeMediaData, photoCachePath *string
|
|||
return false, err
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
photo.MediaID, highres_name, photoDimensions.Width, photoDimensions.Height, models.PhotoHighRes, "image/jpeg")
|
||||
fileStats, err := os.Stat(baseImagePath)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "reading file stats of highres photo")
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type, file_size) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
photo.MediaID, highres_name, photoDimensions.Width, photoDimensions.Height, models.PhotoHighRes, "image/jpeg", fileStats.Size())
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "could not insert highres media url (%d, %s)", photo.MediaID, photo.Title)
|
||||
}
|
||||
|
@ -187,7 +192,12 @@ func processPhoto(tx *sql.Tx, imageData *EncodeMediaData, photoCachePath *string
|
|||
return false, errors.Wrap(err, "could not create thumbnail cached image")
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)", photo.MediaID, thumbnail_name, thumbSize.Width, thumbSize.Height, models.PhotoThumbnail, "image/jpeg")
|
||||
fileStats, err := os.Stat(thumbOutputPath)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "reading file stats of thumbnail photo")
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type, file_size) VALUES (?, ?, ?, ?, ?, ?, ?)", photo.MediaID, thumbnail_name, thumbSize.Width, thumbSize.Height, models.PhotoThumbnail, "image/jpeg", fileStats.Size())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -250,7 +260,12 @@ func saveOriginalPhotoToDB(tx *sql.Tx, photo *models.Media, imageData *EncodeMed
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)", photo.MediaID, original_image_name, photoDimensions.Width, photoDimensions.Height, models.MediaOriginal, contentType)
|
||||
fileStats, err := os.Stat(photo.Path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading file stats of original photo")
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type, file_size) VALUES (?, ?, ?, ?, ?, ?, ?)", photo.MediaID, original_image_name, photoDimensions.Width, photoDimensions.Height, models.MediaOriginal, contentType, fileStats.Size())
|
||||
if err != nil {
|
||||
log.Printf("Could not insert original photo url: %d, %s\n", photo.MediaID, photoName)
|
||||
return err
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -56,8 +57,13 @@ func processVideo(tx *sql.Tx, mediaData *EncodeMediaData, videoCachePath *string
|
|||
return false, errors.Wrapf(err, "failed to read metadata for encoded web-video (%s)", video.Title)
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
video.MediaID, web_video_name, webMetadata.Width, webMetadata.Height, models.VideoWeb, "video/mp4")
|
||||
fileStats, err := os.Stat(webVideoPath)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "reading file stats of web-optimized video")
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type, file_size) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
video.MediaID, web_video_name, webMetadata.Width, webMetadata.Height, models.VideoWeb, "video/mp4", fileStats.Size())
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to insert encoded web-video into database (%s)", video.Title)
|
||||
}
|
||||
|
@ -83,8 +89,13 @@ func processVideo(tx *sql.Tx, mediaData *EncodeMediaData, videoCachePath *string
|
|||
return false, errors.Wrap(err, "get dimensions of video thumbnail image")
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
video.MediaID, video_thumb_name, thumbDimensions.Width, thumbDimensions.Height, models.VideoThumbnail, "image/jpeg")
|
||||
fileStats, err := os.Stat(thumbImagePath)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "reading file stats of video thumbnail")
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type, file_size) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
video.MediaID, video_thumb_name, thumbDimensions.Width, thumbDimensions.Height, models.VideoThumbnail, "image/jpeg", fileStats.Size())
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to insert video thumbnail image into database (%s)", video.Title)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package scanner
|
|||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -40,7 +41,12 @@ func ScanMedia(tx *sql.Tx, mediaPath string, albumId int, cache *AlbumScannerCac
|
|||
mediaTypeText = "photo"
|
||||
}
|
||||
|
||||
result, err := tx.Exec("INSERT INTO media (title, path, path_hash, album_id, media_type) VALUES (?, ?, MD5(path), ?, ?)", mediaName, mediaPath, albumId, mediaTypeText)
|
||||
stat, err := os.Stat(mediaPath)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
result, err := tx.Exec("INSERT INTO media (title, path, path_hash, album_id, media_type, date_shot) VALUES (?, ?, MD5(path), ?, ?, ?)", mediaName, mediaPath, albumId, mediaTypeText, stat.ModTime())
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not insert media into database")
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Chrome",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceRoot}/src"
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,7 @@
|
|||
"license": "GPL-3.0",
|
||||
"description": "UI app for Photoview",
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "^7.10.2",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"apollo-cache-inmemory": "^1.6.6",
|
||||
"apollo-client": "^2.6.10",
|
||||
"apollo-link": "^1.2.14",
|
||||
|
@ -12,24 +12,24 @@
|
|||
"apollo-link-error": "^1.1.13",
|
||||
"apollo-link-http": "^1.5.17",
|
||||
"apollo-link-ws": "^1.0.20",
|
||||
"babel-plugin-styled-components": "^1.10.7",
|
||||
"babel-plugin-styled-components": "^1.11.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"downloadjs": "^1.4.7",
|
||||
"graphql": "^15.1.0",
|
||||
"graphql-tag": "^2.10.3",
|
||||
"graphql": "^15.3.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.13.1",
|
||||
"react-apollo": "^3.1.5",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-lazyload": "^2.6.8",
|
||||
"react-lazyload": "^2.6.9",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-spring": "^8.0.27",
|
||||
"semantic-ui-css": "^2.4.1",
|
||||
"semantic-ui-react": "^0.88.2",
|
||||
"semantic-ui-react": "^1.2.0",
|
||||
"styled-components": "^5.1.1",
|
||||
"subscriptions-transport-ws": "^0.9.16",
|
||||
"subscriptions-transport-ws": "^0.9.18",
|
||||
"url-join": "^4.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -37,16 +37,16 @@
|
|||
"build": "parcel build src/index.html --no-source-maps"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.2",
|
||||
"@babel/plugin-transform-runtime": "^7.10.1",
|
||||
"@babel/core": "^7.11.1",
|
||||
"@babel/plugin-transform-runtime": "^7.11.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-graphql-tag": "^2.5.0",
|
||||
"babel-plugin-graphql-tag": "^3.0.0",
|
||||
"babel-plugin-transform-semantic-ui-react-imports": "^1.4.1",
|
||||
"eslint": "^7.2.0",
|
||||
"eslint-plugin-react": "^7.20.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.4",
|
||||
"eslint": "^7.7.0",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"eslint-plugin-react-hooks": "^4.1.0",
|
||||
"husky": "^4.2.5",
|
||||
"lint-staged": "^10.2.10",
|
||||
"lint-staged": "^10.2.11",
|
||||
"parcel-plugin-sw-cache": "^0.3.1",
|
||||
"prettier": "^2.0.5",
|
||||
"react-router-prop-types": "^1.0.4"
|
||||
|
|
|
@ -21,9 +21,8 @@ const adminQuery = gql`
|
|||
const Container = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
/* margin-right: 500px; */
|
||||
/* display: grid;
|
||||
grid-template-columns: 80px 1fr 500px; */
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const SideMenu = styled.div`
|
||||
|
@ -31,6 +30,18 @@ const SideMenu = styled.div`
|
|||
width: 80px;
|
||||
left: 0;
|
||||
padding-top: 70px;
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
position: fixed;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
bottom: 0;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
|
|
|
@ -31,6 +31,8 @@ const photoQuery = gql`
|
|||
}
|
||||
highRes {
|
||||
url
|
||||
width
|
||||
height
|
||||
}
|
||||
videoWeb {
|
||||
url
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
import React, { useState } from 'react'
|
||||
import gql from 'graphql-tag'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import RouterProps from 'react-router-prop-types'
|
||||
import React, { useState } from 'react'
|
||||
import { useQuery } from 'react-apollo'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import RouterProps from 'react-router-prop-types'
|
||||
import { Form, Header, Icon, Input, Message } from 'semantic-ui-react'
|
||||
import styled from 'styled-components'
|
||||
import { getSharePassword, saveSharePassword } from '../../authentication'
|
||||
import AlbumSharePage from './AlbumSharePage'
|
||||
import MediaSharePage from './MediaSharePage'
|
||||
import { useQuery } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import {
|
||||
Container,
|
||||
Header,
|
||||
Form,
|
||||
Button,
|
||||
Input,
|
||||
Icon,
|
||||
Message,
|
||||
} from 'semantic-ui-react'
|
||||
import { saveSharePassword, getSharePassword } from '../../authentication'
|
||||
|
||||
const shareTokenQuery = gql`
|
||||
query SharePageToken($token: String!, $password: String) {
|
||||
|
@ -61,9 +53,12 @@ const shareTokenQuery = gql`
|
|||
}
|
||||
downloads {
|
||||
title
|
||||
mediaUrl {
|
||||
url
|
||||
width
|
||||
height
|
||||
fileSize
|
||||
}
|
||||
}
|
||||
highRes {
|
||||
url
|
||||
|
|
|
@ -9,7 +9,7 @@ const Container = styled.div`
|
|||
position: relative;
|
||||
`
|
||||
|
||||
const AlbumBoxes = ({ loading, error, albums, getCustomLink }) => {
|
||||
const AlbumBoxes = ({ error, albums, getCustomLink }) => {
|
||||
if (error) return <div>Error {error.message}</div>
|
||||
|
||||
let albumElements = []
|
||||
|
@ -28,12 +28,7 @@ const AlbumBoxes = ({ loading, error, albums, getCustomLink }) => {
|
|||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{/* <Loader active={loading}>Loading albums</Loader> */}
|
||||
{albumElements}
|
||||
</Container>
|
||||
)
|
||||
return <Container>{albumElements}</Container>
|
||||
}
|
||||
|
||||
AlbumBoxes.propTypes = {
|
||||
|
|
|
@ -21,6 +21,15 @@ const Title = styled.h1`
|
|||
font-weight: 400;
|
||||
padding: 2px 12px;
|
||||
flex-grow: 1;
|
||||
min-width: 245px;
|
||||
|
||||
@media (max-width: 400px) {
|
||||
min-width: auto;
|
||||
|
||||
& span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Logo = styled.img`
|
||||
|
|
|
@ -12,6 +12,10 @@ const Container = styled.div`
|
|||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 500px;
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
export let MessageState = {
|
||||
|
|
|
@ -15,6 +15,11 @@ const Gallery = styled.div`
|
|||
min-height: 200px;
|
||||
position: relative;
|
||||
margin: -4px;
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
/* Compensate for tab bar on mobile */
|
||||
margin-bottom: 76px;
|
||||
}
|
||||
`
|
||||
|
||||
const PhotoFiller = styled.div`
|
||||
|
|
|
@ -4,15 +4,11 @@ import PropTypes from 'prop-types'
|
|||
const getProtectedUrl = url => {
|
||||
const imgUrl = new URL(url)
|
||||
|
||||
if (localStorage.getItem('token') == null) {
|
||||
// Get share token if not authorized
|
||||
|
||||
const tokenRegex = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
|
||||
if (tokenRegex) {
|
||||
const token = tokenRegex[1]
|
||||
imgUrl.searchParams.set('token', token)
|
||||
}
|
||||
}
|
||||
|
||||
return imgUrl.href
|
||||
}
|
||||
|
@ -20,65 +16,20 @@ const getProtectedUrl = url => {
|
|||
/**
|
||||
* An image that needs authorization to load
|
||||
*/
|
||||
export const ProtectedImage = ({ src, ...props }) => {
|
||||
// const [imgSrc, setImgSrc] = useState(null)
|
||||
|
||||
// useEffect(() => {
|
||||
// if (imageCache[src]) return
|
||||
|
||||
// const fetchController = new AbortController()
|
||||
// let canceled = false
|
||||
|
||||
// setImgSrc('')
|
||||
|
||||
// const imgUrl = new URL(src)
|
||||
// const fetchHeaders = {}
|
||||
|
||||
// if (localStorage.getItem('token') == null) {
|
||||
// // Get share token if not authorized
|
||||
|
||||
// const tokenRegex = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
|
||||
// if (tokenRegex) {
|
||||
// const token = tokenRegex[1]
|
||||
// imgUrl.searchParams.set('token', token)
|
||||
|
||||
// const tokenPassword = sessionStorage.getItem(`share-token-pw-${token}`)
|
||||
// if (tokenPassword) {
|
||||
// fetchHeaders['TokenPassword'] = tokenPassword
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// fetchProtectedImage(imgUrl.href, {
|
||||
// signal: fetchController.signal,
|
||||
// headers: fetchHeaders,
|
||||
// })
|
||||
// .then(newSrc => {
|
||||
// if (!canceled) {
|
||||
// setImgSrc(newSrc)
|
||||
// }
|
||||
// })
|
||||
// .catch(error => {
|
||||
// console.log('Fetch image error', error.message)
|
||||
// })
|
||||
|
||||
// return function cleanup() {
|
||||
// canceled = true
|
||||
// fetchController.abort()
|
||||
// }
|
||||
// }, [src])
|
||||
|
||||
return (
|
||||
<img {...props} src={getProtectedUrl(src)} crossOrigin="use-credentials" />
|
||||
export const ProtectedImage = ({ src, ...props }) => (
|
||||
<img
|
||||
key={src}
|
||||
{...props}
|
||||
src={getProtectedUrl(src)}
|
||||
crossOrigin="use-credentials"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
ProtectedImage.propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export const ProtectedVideo = ({ media, ...props }) => {
|
||||
return (
|
||||
export const ProtectedVideo = ({ media, ...props }) => (
|
||||
<video
|
||||
{...props}
|
||||
controls
|
||||
|
@ -89,7 +40,6 @@ export const ProtectedVideo = ({ media, ...props }) => {
|
|||
<source src={getProtectedUrl(media.videoWeb.url)} type="video/mp4" />
|
||||
</video>
|
||||
)
|
||||
}
|
||||
|
||||
ProtectedVideo.propTypes = {
|
||||
media: PropTypes.object.isRequired,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import { ProtectedImage, ProtectedVideo } from '../ProtectedMedia'
|
||||
|
|
|
@ -18,6 +18,7 @@ const OverlayButton = styled.button`
|
|||
height: 64px;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { createContext } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import { Icon } from 'semantic-ui-react'
|
||||
|
||||
const SidebarContainer = styled.div`
|
||||
width: 28vw;
|
||||
|
@ -17,8 +18,23 @@ const SidebarContainer = styled.div`
|
|||
@media (max-width: 700px) {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
max-width: calc(100vw - 85px);
|
||||
transform: translateX(100vw);
|
||||
/* full height - header - tabbar */
|
||||
height: calc(100% - 60px - 80px);
|
||||
max-width: min(calc(100vw - 85px), 400px);
|
||||
${({ highlighted }) => `right: ${highlighted ? 0 : -100}%;`}
|
||||
padding-top: 45px;
|
||||
}
|
||||
|
||||
transition: right 200ms ease-in-out;
|
||||
`
|
||||
|
||||
const SidebarDismissButton = styled(Icon)`
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
|
||||
@media (min-width: 700px) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -46,8 +62,14 @@ class Sidebar extends React.Component {
|
|||
{this.props.children}
|
||||
<SidebarContext.Consumer>
|
||||
{value => (
|
||||
<SidebarContainer>
|
||||
<SidebarContainer highlighted={value.content != null}>
|
||||
{value.content}
|
||||
<SidebarDismissButton
|
||||
name="angle double right"
|
||||
size="big"
|
||||
link
|
||||
onClick={() => this.setState({ content: null })}
|
||||
/>
|
||||
<div style={{ height: 100 }}></div>
|
||||
</SidebarContainer>
|
||||
)}
|
||||
|
|
|
@ -14,9 +14,12 @@ const downloadQuery = gql`
|
|||
id
|
||||
downloads {
|
||||
title
|
||||
mediaUrl {
|
||||
url
|
||||
width
|
||||
height
|
||||
fileSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,10 +174,14 @@ const SidebarDownload = ({ photo }) => {
|
|||
}
|
||||
|
||||
let downloadRows = downloads.map(x => (
|
||||
<DownloadTableRow key={x.url} onClick={() => downloadPhoto(x.url)}>
|
||||
<DownloadTableRow
|
||||
key={x.mediaUrl.url}
|
||||
onClick={() => downloadPhoto(x.mediaUrl.url)}
|
||||
>
|
||||
<Table.Cell>{`${x.title}`}</Table.Cell>
|
||||
<Table.Cell>{`${x.width} x ${x.height}`}</Table.Cell>
|
||||
<Table.Cell>{extractExtension(x.url)}</Table.Cell>
|
||||
<Table.Cell>{`${x.mediaUrl.width} x ${x.mediaUrl.height}`}</Table.Cell>
|
||||
<Table.Cell>{`${formatBytes(x.mediaUrl.fileSize)}`}</Table.Cell>
|
||||
<Table.Cell>{extractExtension(x.mediaUrl.url)}</Table.Cell>
|
||||
</DownloadTableRow>
|
||||
))
|
||||
|
||||
|
@ -187,6 +194,7 @@ const SidebarDownload = ({ photo }) => {
|
|||
<Table.Row>
|
||||
<Table.HeaderCell>Name</Table.HeaderCell>
|
||||
<Table.HeaderCell>Dimensions</Table.HeaderCell>
|
||||
<Table.HeaderCell>Size</Table.HeaderCell>
|
||||
<Table.HeaderCell>Type</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
|
Loading…
Reference in New Issue