From e2049bd6e51cc9d18affc7f3f559ad9347de94d1 Mon Sep 17 00:00:00 2001
From: viktorstrate <viktorstrate@gmail.com>
Date: Sat, 1 Feb 2020 14:52:27 +0100
Subject: [PATCH] Add sql tables for photos and albums

---
 api/database/migrations/0001_init.down.sql    |    2 -
 api/database/migrations/0001_user.down.sql    |    2 +
 .../{0001_init.up.sql => 0001_user.up.sql}    |    6 +-
 api/database/migrations/0002_photo.down.sql   |    3 +
 api/database/migrations/0002_photo.up.sql     |   33 +
 api/graphql/generated.go                      | 1301 ++++++++++++++++-
 api/graphql/models/generated.go               |   32 +
 api/graphql/models/user.go                    |   12 +-
 api/graphql/resolver.go                       |   13 +
 api/graphql/schema.graphql                    |   53 +-
 10 files changed, 1440 insertions(+), 17 deletions(-)
 delete mode 100644 api/database/migrations/0001_init.down.sql
 create mode 100644 api/database/migrations/0001_user.down.sql
 rename api/database/migrations/{0001_init.up.sql => 0001_user.up.sql} (73%)
 create mode 100644 api/database/migrations/0002_photo.down.sql
 create mode 100644 api/database/migrations/0002_photo.up.sql

diff --git a/api/database/migrations/0001_init.down.sql b/api/database/migrations/0001_init.down.sql
deleted file mode 100644
index ae8f926..0000000
--- a/api/database/migrations/0001_init.down.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-DROP TABLE IF EXISTS users;
-DROP TABLE IF NOT EXISTS access_tokens;
diff --git a/api/database/migrations/0001_user.down.sql b/api/database/migrations/0001_user.down.sql
new file mode 100644
index 0000000..9aae951
--- /dev/null
+++ b/api/database/migrations/0001_user.down.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS user;
+DROP TABLE IF NOT EXISTS access_token;
diff --git a/api/database/migrations/0001_init.up.sql b/api/database/migrations/0001_user.up.sql
similarity index 73%
rename from api/database/migrations/0001_init.up.sql
rename to api/database/migrations/0001_user.up.sql
index ffe95ee..62d0060 100644
--- a/api/database/migrations/0001_init.up.sql
+++ b/api/database/migrations/0001_user.up.sql
@@ -1,4 +1,4 @@
-CREATE TABLE IF NOT EXISTS users (
+CREATE TABLE IF NOT EXISTS user (
   user_id int NOT NULL AUTO_INCREMENT,
   username varchar(255) NOT NULL UNIQUE,
   password varchar(255) NOT NULL,
@@ -8,12 +8,12 @@ CREATE TABLE IF NOT EXISTS users (
   PRIMARY KEY (user_id)
 );
 
-CREATE TABLE IF NOT EXISTS access_tokens (
+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 users(user_id)
+  FOREIGN KEY (user_id) REFERENCES user(user_id)
 );
diff --git a/api/database/migrations/0002_photo.down.sql b/api/database/migrations/0002_photo.down.sql
new file mode 100644
index 0000000..4c8187b
--- /dev/null
+++ b/api/database/migrations/0002_photo.down.sql
@@ -0,0 +1,3 @@
+DROP TABLE IF EXISTS photo;
+DROP TABLE IF EXISTS album;
+DROP TABLE IF EXISTS photo_url;
diff --git a/api/database/migrations/0002_photo.up.sql b/api/database/migrations/0002_photo.up.sql
new file mode 100644
index 0000000..ec0f754
--- /dev/null
+++ b/api/database/migrations/0002_photo.up.sql
@@ -0,0 +1,33 @@
+CREATE TABLE IF NOT EXISTS photo_url (
+  url_id int NOT NULL AUTO_INCREMENT,
+  width int NOT NULL,
+  height int NOT NULL,
+
+  PRIMARY KEY (url_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(512) NOT NULL,
+
+  PRIMARY KEY (album_id),
+  FOREIGN KEY (parent_album) REFERENCES album(album_id),
+  FOREIGN KEY (owner_id) REFERENCES user(user_id)
+);
+
+CREATE TABLE IF NOT EXISTS photo (
+  photo_id int NOT NULL AUTO_INCREMENT,
+  title varchar(256) NOT NULL,
+  path varchar(512) NOT NULL,
+  original_url int NOT NULL,
+  thumbnail_url int NOT NULL,
+  album_id int NOT NULL,
+  -- exif_id int NOT NULL,
+
+  PRIMARY KEY (photo_id),
+  FOREIGN KEY (original_url) REFERENCES photo_url(url_id),
+  FOREIGN KEY (thumbnail_url) REFERENCES photo_url(url_id)
+);
\ No newline at end of file
diff --git a/api/graphql/generated.go b/api/graphql/generated.go
index d4db914..05e33ac 100644
--- a/api/graphql/generated.go
+++ b/api/graphql/generated.go
@@ -45,6 +45,16 @@ type DirectiveRoot struct {
 }
 
 type ComplexityRoot struct {
+	Album struct {
+		ID          func(childComplexity int) int
+		Owner       func(childComplexity int) int
+		ParentAlbum func(childComplexity int) int
+		Path        func(childComplexity int) int
+		Photos      func(childComplexity int) int
+		SubAlbums   func(childComplexity int) int
+		Title       func(childComplexity int) int
+	}
+
 	AuthorizeResult struct {
 		Status  func(childComplexity int) int
 		Success func(childComplexity int) int
@@ -56,9 +66,28 @@ type ComplexityRoot struct {
 		RegisterUser  func(childComplexity int, username string, password string, rootPath string) int
 	}
 
+	Photo struct {
+		Album     func(childComplexity int) int
+		ID        func(childComplexity int) int
+		Original  func(childComplexity int) int
+		Path      func(childComplexity int) int
+		Thumbnail func(childComplexity int) int
+		Title     func(childComplexity int) int
+	}
+
+	PhotoURL struct {
+		Height func(childComplexity int) int
+		URL    func(childComplexity int) int
+		Width  func(childComplexity int) int
+	}
+
 	Query struct {
-		MyUser func(childComplexity int) int
-		Users  func(childComplexity int) int
+		Album    func(childComplexity int, id *string) int
+		MyAlbums func(childComplexity int) int
+		MyPhotos func(childComplexity int) int
+		MyUser   func(childComplexity int) int
+		Photo    func(childComplexity int, id string) int
+		Users    func(childComplexity int) int
 	}
 
 	User struct {
@@ -76,6 +105,10 @@ type MutationResolver interface {
 type QueryResolver interface {
 	Users(ctx context.Context) ([]*models.User, error)
 	MyUser(ctx context.Context) (*models.User, error)
+	MyAlbums(ctx context.Context) ([]*models.Album, error)
+	Album(ctx context.Context, id *string) (*models.Album, error)
+	MyPhotos(ctx context.Context) ([]*models.Photo, error)
+	Photo(ctx context.Context, id string) (*models.Photo, error)
 }
 
 type executableSchema struct {
@@ -93,6 +126,55 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 	_ = ec
 	switch typeName + "." + field {
 
+	case "Album.id":
+		if e.complexity.Album.ID == nil {
+			break
+		}
+
+		return e.complexity.Album.ID(childComplexity), true
+
+	case "Album.owner":
+		if e.complexity.Album.Owner == nil {
+			break
+		}
+
+		return e.complexity.Album.Owner(childComplexity), true
+
+	case "Album.parentAlbum":
+		if e.complexity.Album.ParentAlbum == nil {
+			break
+		}
+
+		return e.complexity.Album.ParentAlbum(childComplexity), true
+
+	case "Album.path":
+		if e.complexity.Album.Path == nil {
+			break
+		}
+
+		return e.complexity.Album.Path(childComplexity), true
+
+	case "Album.photos":
+		if e.complexity.Album.Photos == nil {
+			break
+		}
+
+		return e.complexity.Album.Photos(childComplexity), true
+
+	case "Album.subAlbums":
+		if e.complexity.Album.SubAlbums == nil {
+			break
+		}
+
+		return e.complexity.Album.SubAlbums(childComplexity), true
+
+	case "Album.title":
+		if e.complexity.Album.Title == nil {
+			break
+		}
+
+		return e.complexity.Album.Title(childComplexity), true
+
 	case "AuthorizeResult.status":
 		if e.complexity.AuthorizeResult.Status == nil {
 			break
@@ -138,6 +220,95 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Mutation.RegisterUser(childComplexity, args["username"].(string), args["password"].(string), args["rootPath"].(string)), true
 
+	case "Photo.album":
+		if e.complexity.Photo.Album == nil {
+			break
+		}
+
+		return e.complexity.Photo.Album(childComplexity), true
+
+	case "Photo.id":
+		if e.complexity.Photo.ID == nil {
+			break
+		}
+
+		return e.complexity.Photo.ID(childComplexity), true
+
+	case "Photo.original":
+		if e.complexity.Photo.Original == nil {
+			break
+		}
+
+		return e.complexity.Photo.Original(childComplexity), true
+
+	case "Photo.path":
+		if e.complexity.Photo.Path == nil {
+			break
+		}
+
+		return e.complexity.Photo.Path(childComplexity), true
+
+	case "Photo.thumbnail":
+		if e.complexity.Photo.Thumbnail == nil {
+			break
+		}
+
+		return e.complexity.Photo.Thumbnail(childComplexity), true
+
+	case "Photo.title":
+		if e.complexity.Photo.Title == nil {
+			break
+		}
+
+		return e.complexity.Photo.Title(childComplexity), true
+
+	case "PhotoURL.height":
+		if e.complexity.PhotoURL.Height == nil {
+			break
+		}
+
+		return e.complexity.PhotoURL.Height(childComplexity), true
+
+	case "PhotoURL.url":
+		if e.complexity.PhotoURL.URL == nil {
+			break
+		}
+
+		return e.complexity.PhotoURL.URL(childComplexity), true
+
+	case "PhotoURL.width":
+		if e.complexity.PhotoURL.Width == nil {
+			break
+		}
+
+		return e.complexity.PhotoURL.Width(childComplexity), true
+
+	case "Query.album":
+		if e.complexity.Query.Album == nil {
+			break
+		}
+
+		args, err := ec.field_Query_album_args(context.TODO(), rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Query.Album(childComplexity, args["id"].(*string)), true
+
+	case "Query.myAlbums":
+		if e.complexity.Query.MyAlbums == nil {
+			break
+		}
+
+		return e.complexity.Query.MyAlbums(childComplexity), true
+
+	case "Query.myPhotos":
+		if e.complexity.Query.MyPhotos == nil {
+			break
+		}
+
+		return e.complexity.Query.MyPhotos(childComplexity), true
+
 	case "Query.myUser":
 		if e.complexity.Query.MyUser == nil {
 			break
@@ -145,6 +316,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Query.MyUser(childComplexity), true
 
+	case "Query.photo":
+		if e.complexity.Query.Photo == nil {
+			break
+		}
+
+		args, err := ec.field_Query_photo_args(context.TODO(), rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Query.Photo(childComplexity, args["id"].(string)), true
+
 	case "Query.users":
 		if e.complexity.Query.Users == nil {
 			break
@@ -247,9 +430,20 @@ var parsedSchema = gqlparser.MustLoadSchema(
 scalar Time
 
 type Query {
+  "List of registered users, must be admin to call"
   users: [User!]! @isAdmin
-
+  "Information about the currently logged in user"
   myUser: User
+
+  "List of albums owned by the logged in user"
+  myAlbums: [Album]
+  "Get album by id, user must own the album or be admin"
+  album(id: ID): Album
+
+  "List of photos owned by the logged in user"
+  myPhotos: [Photo]
+  "Get photo by id, user must own the photo or be admin"
+  photo(id: ID!): Photo
 }
 
 type Mutation {
@@ -272,11 +466,49 @@ type User {
   id: ID!
   username: String!
   #albums: [Album]
-  # Local filepath for the user's photos
+  "Local filepath for the user's photos"
   rootPath: String! @isAdmin
   admin: Boolean!
   #shareTokens: [ShareToken]
 }
+
+type Album {
+  id: ID!
+  title: String
+  photos: [Photo]
+  subAlbums: [Album]
+  parentAlbum: Album
+  owner: User!
+  path: String
+
+  # shares: [ShareToken]
+}
+
+type PhotoURL {
+  "URL for previewing the image"
+  url: String
+  "Width of the image in pixels"
+  width: Int
+  "Height of the image in pixels"
+  height: Int
+}
+
+type Photo {
+  id: ID!
+  title: String
+  "Local filepath for the photo"
+  path: String
+  "URL to display the photo in full resolution"
+  original: PhotoURL
+  "URL to display the photo in a smaller resolution"
+  thumbnail: PhotoURL
+  "The album that holds the photo"
+  album: Album!
+  # exif: PhotoEXIF
+
+  # shares: [ShareToken]
+  # downloads: [PhotoDownload]
+}
 `},
 )
 
@@ -350,6 +582,34 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs
 	return args, nil
 }
 
+func (ec *executionContext) field_Query_album_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	var err error
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := rawArgs["id"]; ok {
+		arg0, err = ec.unmarshalOID2ᚖstring(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["id"] = arg0
+	return args, nil
+}
+
+func (ec *executionContext) field_Query_photo_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	var err error
+	args := map[string]interface{}{}
+	var arg0 string
+	if tmp, ok := rawArgs["id"]; ok {
+		arg0, err = ec.unmarshalNID2string(ctx, tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["id"] = arg0
+	return args, nil
+}
+
 func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
 	var err error
 	args := map[string]interface{}{}
@@ -386,6 +646,250 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg
 
 // region    **************************** field.gotpl *****************************
 
+func (ec *executionContext) _Album_id(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Album",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ID, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalNID2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Album_title(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Album",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Title, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Album_photos(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Album",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Photos, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.([]*models.Photo)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOPhoto2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Album_subAlbums(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Album",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.SubAlbums, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.([]*models.Album)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOAlbum2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Album_parentAlbum(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Album",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ParentAlbum, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.Album)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Album_owner(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Album",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Owner, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(*models.User)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalNUser2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Album_path(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Album",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Path, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _AuthorizeResult_success(ctx context.Context, field graphql.CollectedField, obj *models.AuthorizeResult) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
@@ -582,6 +1086,318 @@ func (ec *executionContext) _Mutation_registerUser(ctx context.Context, field gr
 	return ec.marshalNAuthorizeResult2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAuthorizeResult(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _Photo_id(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Photo",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ID, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalNID2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Photo_title(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Photo",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Title, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Photo_path(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Photo",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Path, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Photo_original(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Photo",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Original, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.PhotoURL)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOPhotoURL2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoURL(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Photo_thumbnail(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Photo",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Thumbnail, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.PhotoURL)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOPhotoURL2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoURL(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Photo_album(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Photo",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Album, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(*models.Album)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalNAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoURL_url(ctx context.Context, field graphql.CollectedField, obj *models.PhotoURL) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoURL",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	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 {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoURL_width(ctx context.Context, field graphql.CollectedField, obj *models.PhotoURL) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoURL",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Width, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*int)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoURL_height(ctx context.Context, field graphql.CollectedField, obj *models.PhotoURL) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoURL",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	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 {
+		return graphql.Null
+	}
+	res := resTmp.(*int)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _Query_users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
@@ -673,6 +1489,156 @@ func (ec *executionContext) _Query_myUser(ctx context.Context, field graphql.Col
 	return ec.marshalOUser2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _Query_myAlbums(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Query",
+		Field:    field,
+		Args:     nil,
+		IsMethod: true,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Query().MyAlbums(rctx)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.([]*models.Album)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOAlbum2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Query_album(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Query",
+		Field:    field,
+		Args:     nil,
+		IsMethod: true,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := ec.field_Query_album_args(ctx, rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	rctx.Args = args
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Query().Album(rctx, args["id"].(*string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.Album)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Query_myPhotos(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Query",
+		Field:    field,
+		Args:     nil,
+		IsMethod: true,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Query().MyPhotos(rctx)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.([]*models.Photo)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOPhoto2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _Query_photo(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "Query",
+		Field:    field,
+		Args:     nil,
+		IsMethod: true,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := ec.field_Query_photo_args(ctx, rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	rctx.Args = args
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Query().Photo(rctx, args["id"].(string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*models.Photo)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
@@ -2075,6 +3041,48 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co
 
 // region    **************************** object.gotpl ****************************
 
+var albumImplementors = []string{"Album"}
+
+func (ec *executionContext) _Album(ctx context.Context, sel ast.SelectionSet, obj *models.Album) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.RequestContext, sel, albumImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	var invalids uint32
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("Album")
+		case "id":
+			out.Values[i] = ec._Album_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
+		case "title":
+			out.Values[i] = ec._Album_title(ctx, field, obj)
+		case "photos":
+			out.Values[i] = ec._Album_photos(ctx, field, obj)
+		case "subAlbums":
+			out.Values[i] = ec._Album_subAlbums(ctx, field, obj)
+		case "parentAlbum":
+			out.Values[i] = ec._Album_parentAlbum(ctx, field, obj)
+		case "owner":
+			out.Values[i] = ec._Album_owner(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
+		case "path":
+			out.Values[i] = ec._Album_path(ctx, field, obj)
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch()
+	if invalids > 0 {
+		return graphql.Null
+	}
+	return out
+}
+
 var authorizeResultImplementors = []string{"AuthorizeResult"}
 
 func (ec *executionContext) _AuthorizeResult(ctx context.Context, sel ast.SelectionSet, obj *models.AuthorizeResult) graphql.Marshaler {
@@ -2145,6 +3153,74 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
 	return out
 }
 
+var photoImplementors = []string{"Photo"}
+
+func (ec *executionContext) _Photo(ctx context.Context, sel ast.SelectionSet, obj *models.Photo) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.RequestContext, sel, photoImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	var invalids uint32
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("Photo")
+		case "id":
+			out.Values[i] = ec._Photo_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
+		case "title":
+			out.Values[i] = ec._Photo_title(ctx, field, obj)
+		case "path":
+			out.Values[i] = ec._Photo_path(ctx, field, obj)
+		case "original":
+			out.Values[i] = ec._Photo_original(ctx, field, obj)
+		case "thumbnail":
+			out.Values[i] = ec._Photo_thumbnail(ctx, field, obj)
+		case "album":
+			out.Values[i] = ec._Photo_album(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch()
+	if invalids > 0 {
+		return graphql.Null
+	}
+	return out
+}
+
+var photoURLImplementors = []string{"PhotoURL"}
+
+func (ec *executionContext) _PhotoURL(ctx context.Context, sel ast.SelectionSet, obj *models.PhotoURL) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.RequestContext, sel, photoURLImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	var invalids uint32
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("PhotoURL")
+		case "url":
+			out.Values[i] = ec._PhotoURL_url(ctx, field, obj)
+		case "width":
+			out.Values[i] = ec._PhotoURL_width(ctx, field, obj)
+		case "height":
+			out.Values[i] = ec._PhotoURL_height(ctx, field, obj)
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch()
+	if invalids > 0 {
+		return graphql.Null
+	}
+	return out
+}
+
 var queryImplementors = []string{"Query"}
 
 func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
@@ -2185,6 +3261,50 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
 				res = ec._Query_myUser(ctx, field)
 				return res
 			})
+		case "myAlbums":
+			field := field
+			out.Concurrently(i, func() (res graphql.Marshaler) {
+				defer func() {
+					if r := recover(); r != nil {
+						ec.Error(ctx, ec.Recover(ctx, r))
+					}
+				}()
+				res = ec._Query_myAlbums(ctx, field)
+				return res
+			})
+		case "album":
+			field := field
+			out.Concurrently(i, func() (res graphql.Marshaler) {
+				defer func() {
+					if r := recover(); r != nil {
+						ec.Error(ctx, ec.Recover(ctx, r))
+					}
+				}()
+				res = ec._Query_album(ctx, field)
+				return res
+			})
+		case "myPhotos":
+			field := field
+			out.Concurrently(i, func() (res graphql.Marshaler) {
+				defer func() {
+					if r := recover(); r != nil {
+						ec.Error(ctx, ec.Recover(ctx, r))
+					}
+				}()
+				res = ec._Query_myPhotos(ctx, field)
+				return res
+			})
+		case "photo":
+			field := field
+			out.Concurrently(i, func() (res graphql.Marshaler) {
+				defer func() {
+					if r := recover(); r != nil {
+						ec.Error(ctx, ec.Recover(ctx, r))
+					}
+				}()
+				res = ec._Query_photo(ctx, field)
+				return res
+			})
 		case "__type":
 			out.Values[i] = ec._Query___type(ctx, field)
 		case "__schema":
@@ -2487,6 +3607,20 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o
 
 // region    ***************************** type.gotpl *****************************
 
+func (ec *executionContext) marshalNAlbum2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx context.Context, sel ast.SelectionSet, v models.Album) graphql.Marshaler {
+	return ec._Album(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx context.Context, sel ast.SelectionSet, v *models.Album) graphql.Marshaler {
+	if v == nil {
+		if !ec.HasError(graphql.GetResolverContext(ctx)) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	return ec._Album(ctx, sel, v)
+}
+
 func (ec *executionContext) marshalNAuthorizeResult2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAuthorizeResult(ctx context.Context, sel ast.SelectionSet, v models.AuthorizeResult) graphql.Marshaler {
 	return ec._AuthorizeResult(ctx, sel, &v)
 }
@@ -2820,6 +3954,57 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a
 	return res
 }
 
+func (ec *executionContext) marshalOAlbum2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx context.Context, sel ast.SelectionSet, v models.Album) graphql.Marshaler {
+	return ec._Album(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalOAlbum2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx context.Context, sel ast.SelectionSet, v []*models.Album) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		rctx := &graphql.ResolverContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithResolverContext(ctx, rctx)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalOAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+	return ret
+}
+
+func (ec *executionContext) marshalOAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx context.Context, sel ast.SelectionSet, v *models.Album) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec._Album(ctx, sel, v)
+}
+
 func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) {
 	return graphql.UnmarshalBoolean(v)
 }
@@ -2843,6 +4028,114 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast
 	return ec.marshalOBoolean2bool(ctx, sel, *v)
 }
 
+func (ec *executionContext) unmarshalOID2string(ctx context.Context, v interface{}) (string, error) {
+	return graphql.UnmarshalID(v)
+}
+
+func (ec *executionContext) marshalOID2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler {
+	return graphql.MarshalID(v)
+}
+
+func (ec *executionContext) unmarshalOID2ᚖstring(ctx context.Context, v interface{}) (*string, error) {
+	if v == nil {
+		return nil, nil
+	}
+	res, err := ec.unmarshalOID2string(ctx, v)
+	return &res, err
+}
+
+func (ec *executionContext) marshalOID2ᚖstring(ctx context.Context, sel ast.SelectionSet, v *string) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec.marshalOID2string(ctx, sel, *v)
+}
+
+func (ec *executionContext) unmarshalOInt2int(ctx context.Context, v interface{}) (int, error) {
+	return graphql.UnmarshalInt(v)
+}
+
+func (ec *executionContext) marshalOInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler {
+	return graphql.MarshalInt(v)
+}
+
+func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v interface{}) (*int, error) {
+	if v == nil {
+		return nil, nil
+	}
+	res, err := ec.unmarshalOInt2int(ctx, v)
+	return &res, err
+}
+
+func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.SelectionSet, v *int) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec.marshalOInt2int(ctx, sel, *v)
+}
+
+func (ec *executionContext) marshalOPhoto2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx context.Context, sel ast.SelectionSet, v models.Photo) graphql.Marshaler {
+	return ec._Photo(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalOPhoto2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx context.Context, sel ast.SelectionSet, v []*models.Photo) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		rctx := &graphql.ResolverContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithResolverContext(ctx, rctx)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+	return ret
+}
+
+func (ec *executionContext) marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx context.Context, sel ast.SelectionSet, v *models.Photo) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec._Photo(ctx, sel, v)
+}
+
+func (ec *executionContext) marshalOPhotoURL2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoURL(ctx context.Context, sel ast.SelectionSet, v models.PhotoURL) graphql.Marshaler {
+	return ec._PhotoURL(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalOPhotoURL2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoURL(ctx context.Context, sel ast.SelectionSet, v *models.PhotoURL) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec._PhotoURL(ctx, sel, v)
+}
+
 func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) {
 	return graphql.UnmarshalString(v)
 }
diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go
index f98f56e..d87425d 100644
--- a/api/graphql/models/generated.go
+++ b/api/graphql/models/generated.go
@@ -2,8 +2,40 @@
 
 package models
 
+type Album struct {
+	ID          string   `json:"id"`
+	Title       *string  `json:"title"`
+	Photos      []*Photo `json:"photos"`
+	SubAlbums   []*Album `json:"subAlbums"`
+	ParentAlbum *Album   `json:"parentAlbum"`
+	Owner       *User    `json:"owner"`
+	Path        *string  `json:"path"`
+}
+
 type AuthorizeResult struct {
 	Success bool    `json:"success"`
 	Status  string  `json:"status"`
 	Token   *string `json:"token"`
 }
+
+type Photo struct {
+	ID    string  `json:"id"`
+	Title *string `json:"title"`
+	// Local filepath for the photo
+	Path *string `json:"path"`
+	// URL to display the photo in full resolution
+	Original *PhotoURL `json:"original"`
+	// URL to display the photo in a smaller resolution
+	Thumbnail *PhotoURL `json:"thumbnail"`
+	// The album that holds the photo
+	Album *Album `json:"album"`
+}
+
+type PhotoURL struct {
+	// URL for previewing the image
+	URL *string `json:"url"`
+	// Width of the image in pixels
+	Width *int `json:"width"`
+	// Height of the image in pixels
+	Height *int `json:"height"`
+}
diff --git a/api/graphql/models/user.go b/api/graphql/models/user.go
index c96453d..9f1114d 100644
--- a/api/graphql/models/user.go
+++ b/api/graphql/models/user.go
@@ -60,7 +60,7 @@ func NewUsersFromRows(rows *sql.Rows) ([]*User, error) {
 }
 
 func AuthorizeUser(database *sql.DB, username string, password string) (*User, error) {
-	row := database.QueryRow("SELECT * FROM users WHERE username = ?", username)
+	row := database.QueryRow("SELECT * FROM user WHERE username = ?", username)
 
 	user, err := NewUserFromRow(row)
 	if err != nil {
@@ -85,11 +85,11 @@ func RegisterUser(database *sql.DB, username string, password string, rootPath s
 	}
 	hashedPass := string(hashedPassBytes)
 
-	if _, err := database.Exec("INSERT INTO users (username, password, root_path) VALUES (?, ?, ?)", username, hashedPass, rootPath); err != nil {
+	if _, err := database.Exec("INSERT INTO user (username, password, root_path) VALUES (?, ?, ?)", username, hashedPass, rootPath); err != nil {
 		return nil, err
 	}
 
-	row := database.QueryRow("SELECT * FROM users WHERE username = ?", username)
+	row := database.QueryRow("SELECT * FROM user WHERE username = ?", username)
 	if row == nil {
 		return nil, ErrorInvalidUserCredentials
 	}
@@ -116,7 +116,7 @@ func (user *User) GenerateAccessToken(database *sql.DB) (*AccessToken, error) {
 	expire := time.Now().Add(14 * 24 * time.Hour)
 	expireString := expire.UTC().Format("2006-01-02 15:04:05")
 
-	if _, err := database.Exec("INSERT INTO access_tokens (value, expire, user_id) VALUES (?, ?, ?)", token_value, expireString, user.UserID); err != nil {
+	if _, err := database.Exec("INSERT INTO access_token (value, expire, user_id) VALUES (?, ?, ?)", token_value, expireString, user.UserID); err != nil {
 		return nil, err
 	}
 
@@ -132,7 +132,7 @@ func VerifyTokenAndGetUser(database *sql.DB, token string) (*User, error) {
 
 	now := time.Now().UTC().Format("2006-01-02 15:04:05")
 
-	row := database.QueryRow("SELECT (user_id) FROM access_tokens WHERE expire > ? AND value = ?", now, token)
+	row := database.QueryRow("SELECT (user_id) FROM access_token WHERE expire > ? AND value = ?", now, token)
 
 	var userId string
 
@@ -141,7 +141,7 @@ func VerifyTokenAndGetUser(database *sql.DB, token string) (*User, error) {
 		return nil, err
 	}
 
-	row = database.QueryRow("SELECT * FROM users WHERE user_id = ?", userId)
+	row = database.QueryRow("SELECT * FROM user WHERE user_id = ?", userId)
 	user, err := NewUserFromRow(row)
 	if err != nil {
 		return nil, err
diff --git a/api/graphql/resolver.go b/api/graphql/resolver.go
index 9dee5ba..8dd4995 100644
--- a/api/graphql/resolver.go
+++ b/api/graphql/resolver.go
@@ -40,3 +40,16 @@ func (r *queryResolver) Users(ctx context.Context) ([]*models.User, error) {
 
 	return users, nil
 }
+
+func (r *queryResolver) MyAlbums(ctx context.Context) ([]*models.Album, error) {
+	panic("Not implemented")
+}
+func (r *queryResolver) Album(ctx context.Context, id *string) (*models.Album, error) {
+	panic("Not implemented")
+}
+func (r *queryResolver) MyPhotos(ctx context.Context) ([]*models.Photo, error) {
+	panic("Not implemented")
+}
+func (r *queryResolver) Photo(ctx context.Context, id string) (*models.Photo, error) {
+	panic("Not implemented")
+}
diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql
index c04e1d9..a2afa67 100644
--- a/api/graphql/schema.graphql
+++ b/api/graphql/schema.graphql
@@ -3,9 +3,20 @@ directive @isAdmin on FIELD_DEFINITION
 scalar Time
 
 type Query {
+  "List of registered users, must be admin to call"
   users: [User!]! @isAdmin
-
+  "Information about the currently logged in user"
   myUser: User
+
+  "List of albums owned by the logged in user"
+  myAlbums: [Album]
+  "Get album by id, user must own the album or be admin"
+  album(id: ID): Album
+
+  "List of photos owned by the logged in user"
+  myPhotos: [Photo]
+  "Get photo by id, user must own the photo or be admin"
+  photo(id: ID!): Photo
 }
 
 type Mutation {
@@ -28,8 +39,46 @@ type User {
   id: ID!
   username: String!
   #albums: [Album]
-  # Local filepath for the user's photos
+  "Local filepath for the user's photos"
   rootPath: String! @isAdmin
   admin: Boolean!
   #shareTokens: [ShareToken]
 }
+
+type Album {
+  id: ID!
+  title: String
+  photos: [Photo]
+  subAlbums: [Album]
+  parentAlbum: Album
+  owner: User!
+  path: String
+
+  # shares: [ShareToken]
+}
+
+type PhotoURL {
+  "URL for previewing the image"
+  url: String
+  "Width of the image in pixels"
+  width: Int
+  "Height of the image in pixels"
+  height: Int
+}
+
+type Photo {
+  id: ID!
+  title: String
+  "Local filepath for the photo"
+  path: String
+  "URL to display the photo in full resolution"
+  original: PhotoURL
+  "URL to display the photo in a smaller resolution"
+  thumbnail: PhotoURL
+  "The album that holds the photo"
+  album: Album!
+  # exif: PhotoEXIF
+
+  # shares: [ShareToken]
+  # downloads: [PhotoDownload]
+}
