diff --git a/api/gqlgen.yml b/api/gqlgen.yml
index e12dcc0..8b535f3 100644
--- a/api/gqlgen.yml
+++ b/api/gqlgen.yml
@@ -36,3 +36,5 @@ models:
     model: github.com/viktorstrate/photoview/api/graphql/models.Album
   ShareToken:
     model: github.com/viktorstrate/photoview/api/graphql/models.ShareToken
+  SiteInfo:
+    model: github.com/viktorstrate/photoview/api/graphql/models.SiteInfo
diff --git a/api/graphql/generated.go b/api/graphql/generated.go
index a3be4d1..2c138c4 100644
--- a/api/graphql/generated.go
+++ b/api/graphql/generated.go
@@ -40,13 +40,10 @@ type Config struct {
 type ResolverRoot interface {
 	Album() AlbumResolver
 	Media() MediaResolver
-	MediaEXIF() MediaEXIFResolver
 	Mutation() MutationResolver
 	Query() QueryResolver
 	ShareToken() ShareTokenResolver
 	Subscription() SubscriptionResolver
-	User() UserResolver
-	VideoMetadata() VideoMetadataResolver
 }
 
 type DirectiveRoot struct {
@@ -216,8 +213,6 @@ type ComplexityRoot struct {
 }
 
 type AlbumResolver interface {
-	ID(ctx context.Context, obj *models.Album) (int, error)
-
 	Media(ctx context.Context, obj *models.Album, filter *models.Filter, onlyFavorites *bool) ([]*models.Media, error)
 	SubAlbums(ctx context.Context, obj *models.Album, filter *models.Filter) ([]*models.Album, error)
 
@@ -226,8 +221,6 @@ type AlbumResolver interface {
 	Shares(ctx context.Context, obj *models.Album) ([]*models.ShareToken, error)
 }
 type MediaResolver interface {
-	ID(ctx context.Context, obj *models.Media) (int, error)
-
 	Thumbnail(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
 	HighRes(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
 	VideoWeb(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
@@ -235,10 +228,6 @@ type MediaResolver interface {
 	Shares(ctx context.Context, obj *models.Media) ([]*models.ShareToken, error)
 	Downloads(ctx context.Context, obj *models.Media) ([]*models.MediaDownload, error)
 }
-type MediaEXIFResolver interface {
-	ID(ctx context.Context, obj *models.MediaEXIF) (int, error)
-	Media(ctx context.Context, obj *models.MediaEXIF) (*models.Media, error)
-}
 type MutationResolver interface {
 	AuthorizeUser(ctx context.Context, username string, password string) (*models.AuthorizeResult, error)
 	RegisterUser(ctx context.Context, username string, password string, rootPath string) (*models.AuthorizeResult, error)
@@ -272,22 +261,11 @@ type QueryResolver interface {
 	Search(ctx context.Context, query string, limitMedia *int, limitAlbums *int) (*models.SearchResult, error)
 }
 type ShareTokenResolver interface {
-	ID(ctx context.Context, obj *models.ShareToken) (int, error)
-
 	HasPassword(ctx context.Context, obj *models.ShareToken) (bool, error)
 }
 type SubscriptionResolver interface {
 	Notification(ctx context.Context) (<-chan *models.Notification, error)
 }
-type UserResolver interface {
-	ID(ctx context.Context, obj *models.User) (int, error)
-}
-type VideoMetadataResolver interface {
-	ID(ctx context.Context, obj *models.VideoMetadata) (int, error)
-	Media(ctx context.Context, obj *models.VideoMetadata) (*models.Media, error)
-
-	Bitrate(ctx context.Context, obj *models.VideoMetadata) (*int, error)
-}
 
 type executableSchema struct {
 	resolvers  ResolverRoot
@@ -1330,15 +1308,15 @@ type Query {
     onlyWithFavorites: Boolean
   ): [Album!]!
   "Get album by id, user must own the album or be admin"
-  album(id: Int!): Album!
+  album(id: ID!): Album!
 
   "List of media owned by the logged in user"
   myMedia(filter: Filter): [Media!]!
   "Get media by id, user must own the media or be admin"
-  media(id: Int!): Media!
+  media(id: ID!): Media!
 
   "Get a list of media by their ids, user must own the media or be admin"
-  mediaList(ids: [Int!]!): [Media!]!
+  mediaList(ids: [ID!]!): [Media!]!
 
   "Get media owned by the logged in user, returned in GeoJson format"
   myMediaGeoJson: Any!
@@ -1371,22 +1349,22 @@ type Mutation {
   "Scan all users for new media"
   scanAll: ScannerResult! @isAdmin
   "Scan a single user for new media"
-  scanUser(userId: Int!): ScannerResult!
+  scanUser(userId: ID!): ScannerResult!
 
   "Generate share token for album"
-  shareAlbum(albumId: Int!, expire: Time, password: String): ShareToken
+  shareAlbum(albumId: ID!, expire: Time, password: String): ShareToken
   "Generate share token for media"
-  shareMedia(mediaId: Int!, expire: Time, password: String): ShareToken
+  shareMedia(mediaId: ID!, expire: Time, password: String): ShareToken
   "Delete a share token by it's token value"
   deleteShareToken(token: String!): ShareToken
   "Set a password for a token, if null is passed for the password argument, the password will be cleared"
   protectShareToken(token: String!, password: String): ShareToken
 
   "Mark or unmark a media as being a favorite"
-  favoriteMedia(mediaId: Int!, favorite: Boolean!): Media
+  favoriteMedia(mediaId: ID!, favorite: Boolean!): Media
 
   updateUser(
-    id: Int!
+    id: ID!
     username: String
     rootPath: String
     password: String
@@ -1398,7 +1376,7 @@ type Mutation {
     password: String
     admin: Boolean!
   ): User @isAdmin
-  deleteUser(id: Int!): User @isAdmin
+  deleteUser(id: ID!): User @isAdmin
 
   """
   Set how often, in seconds, the server should automatically scan for new media,
@@ -1448,7 +1426,7 @@ type ScannerResult {
 
 "A token used to publicly access an album or media"
 type ShareToken {
-  id: Int!
+  id: ID!
   token: String!
   "The user who created the token"
   owner: User!
@@ -1473,7 +1451,7 @@ type SiteInfo {
 }
 
 type User {
-  id: Int!
+  id: ID!
   username: String!
   #albums: [Album]
   "Local filepath for the user's photos"
@@ -1483,7 +1461,7 @@ type User {
 }
 
 type Album {
-  id: Int!
+  id: ID!
   title: String!
   "The media inside this album"
   media(
@@ -1528,7 +1506,7 @@ enum MediaType {
 }
 
 type Media {
-  id: Int!
+  id: ID!
   title: String!
   "Local filepath for the media"
   path: String!
@@ -1551,7 +1529,7 @@ type Media {
 
 "EXIF metadata from the camera"
 type MediaEXIF {
-  id: Int!
+  id: ID!
   media: Media!
   "The model name of the camera"
   camera: String
@@ -1575,14 +1553,14 @@ type MediaEXIF {
 }
 
 type VideoMetadata {
-  id: Int!
+  id: ID!
   media: Media!
   width: Int!
   height: Int!
   duration: Float!
   codec: String
   framerate: Float
-  bitrate: Int
+  bitrate: String
   colorProfile: String
   audio: String
 }
@@ -1726,7 +1704,7 @@ func (ec *executionContext) field_Mutation_deleteUser_args(ctx context.Context,
 	var arg0 int
 	if tmp, ok := rawArgs["id"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
-		arg0, err = ec.unmarshalNInt2int(ctx, tmp)
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -1741,7 +1719,7 @@ func (ec *executionContext) field_Mutation_favoriteMedia_args(ctx context.Contex
 	var arg0 int
 	if tmp, ok := rawArgs["mediaId"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("mediaId"))
-		arg0, err = ec.unmarshalNInt2int(ctx, tmp)
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -1855,7 +1833,7 @@ func (ec *executionContext) field_Mutation_scanUser_args(ctx context.Context, ra
 	var arg0 int
 	if tmp, ok := rawArgs["userId"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId"))
-		arg0, err = ec.unmarshalNInt2int(ctx, tmp)
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -1900,7 +1878,7 @@ func (ec *executionContext) field_Mutation_shareAlbum_args(ctx context.Context,
 	var arg0 int
 	if tmp, ok := rawArgs["albumId"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("albumId"))
-		arg0, err = ec.unmarshalNInt2int(ctx, tmp)
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -1933,7 +1911,7 @@ func (ec *executionContext) field_Mutation_shareMedia_args(ctx context.Context,
 	var arg0 int
 	if tmp, ok := rawArgs["mediaId"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("mediaId"))
-		arg0, err = ec.unmarshalNInt2int(ctx, tmp)
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -1966,7 +1944,7 @@ func (ec *executionContext) field_Mutation_updateUser_args(ctx context.Context,
 	var arg0 int
 	if tmp, ok := rawArgs["id"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
-		arg0, err = ec.unmarshalNInt2int(ctx, tmp)
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -2032,7 +2010,7 @@ func (ec *executionContext) field_Query_album_args(ctx context.Context, rawArgs
 	var arg0 int
 	if tmp, ok := rawArgs["id"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
-		arg0, err = ec.unmarshalNInt2int(ctx, tmp)
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -2047,7 +2025,7 @@ func (ec *executionContext) field_Query_mediaList_args(ctx context.Context, rawA
 	var arg0 []int
 	if tmp, ok := rawArgs["ids"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ids"))
-		arg0, err = ec.unmarshalNInt2ᚕintᚄ(ctx, tmp)
+		arg0, err = ec.unmarshalNID2ᚕintᚄ(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -2062,7 +2040,7 @@ func (ec *executionContext) field_Query_media_args(ctx context.Context, rawArgs
 	var arg0 int
 	if tmp, ok := rawArgs["id"]; ok {
 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
-		arg0, err = ec.unmarshalNInt2int(ctx, tmp)
+		arg0, err = ec.unmarshalNID2int(ctx, tmp)
 		if err != nil {
 			return nil, err
 		}
@@ -2273,14 +2251,14 @@ func (ec *executionContext) _Album_id(ctx context.Context, field graphql.Collect
 		Object:     "Album",
 		Field:      field,
 		Args:       nil,
-		IsMethod:   true,
-		IsResolver: true,
+		IsMethod:   false,
+		IsResolver: 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 ec.resolvers.Album().ID(rctx, obj)
+		return obj.ID, nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -2294,7 +2272,7 @@ func (ec *executionContext) _Album_id(ctx context.Context, field graphql.Collect
 	}
 	res := resTmp.(int)
 	fc.Result = res
-	return ec.marshalNInt2int(ctx, field.Selections, res)
+	return ec.marshalNID2int(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) _Album_title(ctx context.Context, field graphql.CollectedField, obj *models.Album) (ret graphql.Marshaler) {
@@ -2730,14 +2708,14 @@ func (ec *executionContext) _Media_id(ctx context.Context, field graphql.Collect
 		Object:     "Media",
 		Field:      field,
 		Args:       nil,
-		IsMethod:   true,
-		IsResolver: true,
+		IsMethod:   false,
+		IsResolver: 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 ec.resolvers.Media().ID(rctx, obj)
+		return obj.ID, nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -2751,7 +2729,7 @@ func (ec *executionContext) _Media_id(ctx context.Context, field graphql.Collect
 	}
 	res := resTmp.(int)
 	fc.Result = res
-	return ec.marshalNInt2int(ctx, field.Selections, res)
+	return ec.marshalNID2int(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) _Media_title(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
@@ -3243,14 +3221,14 @@ func (ec *executionContext) _MediaEXIF_id(ctx context.Context, field graphql.Col
 		Object:     "MediaEXIF",
 		Field:      field,
 		Args:       nil,
-		IsMethod:   true,
-		IsResolver: true,
+		IsMethod:   false,
+		IsResolver: 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 ec.resolvers.MediaEXIF().ID(rctx, obj)
+		return obj.ID, nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -3264,7 +3242,7 @@ func (ec *executionContext) _MediaEXIF_id(ctx context.Context, field graphql.Col
 	}
 	res := resTmp.(int)
 	fc.Result = res
-	return ec.marshalNInt2int(ctx, field.Selections, res)
+	return ec.marshalNID2int(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) _MediaEXIF_media(ctx context.Context, field graphql.CollectedField, obj *models.MediaEXIF) (ret graphql.Marshaler) {
@@ -3279,13 +3257,13 @@ func (ec *executionContext) _MediaEXIF_media(ctx context.Context, field graphql.
 		Field:      field,
 		Args:       nil,
 		IsMethod:   true,
-		IsResolver: true,
+		IsResolver: 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 ec.resolvers.MediaEXIF().Media(rctx, obj)
+		return obj.Media(), nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -5568,14 +5546,14 @@ func (ec *executionContext) _ShareToken_id(ctx context.Context, field graphql.Co
 		Object:     "ShareToken",
 		Field:      field,
 		Args:       nil,
-		IsMethod:   true,
-		IsResolver: true,
+		IsMethod:   false,
+		IsResolver: 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 ec.resolvers.ShareToken().ID(rctx, obj)
+		return obj.ID, nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -5589,7 +5567,7 @@ func (ec *executionContext) _ShareToken_id(ctx context.Context, field graphql.Co
 	}
 	res := resTmp.(int)
 	fc.Result = res
-	return ec.marshalNInt2int(ctx, field.Selections, res)
+	return ec.marshalNID2int(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) _ShareToken_token(ctx context.Context, field graphql.CollectedField, obj *models.ShareToken) (ret graphql.Marshaler) {
@@ -5994,14 +5972,14 @@ func (ec *executionContext) _User_id(ctx context.Context, field graphql.Collecte
 		Object:     "User",
 		Field:      field,
 		Args:       nil,
-		IsMethod:   true,
-		IsResolver: true,
+		IsMethod:   false,
+		IsResolver: 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 ec.resolvers.User().ID(rctx, obj)
+		return obj.ID, nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6015,7 +5993,7 @@ func (ec *executionContext) _User_id(ctx context.Context, field graphql.Collecte
 	}
 	res := resTmp.(int)
 	fc.Result = res
-	return ec.marshalNInt2int(ctx, field.Selections, res)
+	return ec.marshalNID2int(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) _User_username(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) {
@@ -6154,14 +6132,14 @@ func (ec *executionContext) _VideoMetadata_id(ctx context.Context, field graphql
 		Object:     "VideoMetadata",
 		Field:      field,
 		Args:       nil,
-		IsMethod:   true,
-		IsResolver: true,
+		IsMethod:   false,
+		IsResolver: 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 ec.resolvers.VideoMetadata().ID(rctx, obj)
+		return obj.ID, nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6175,7 +6153,7 @@ func (ec *executionContext) _VideoMetadata_id(ctx context.Context, field graphql
 	}
 	res := resTmp.(int)
 	fc.Result = res
-	return ec.marshalNInt2int(ctx, field.Selections, res)
+	return ec.marshalNID2int(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) _VideoMetadata_media(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
@@ -6190,13 +6168,13 @@ func (ec *executionContext) _VideoMetadata_media(ctx context.Context, field grap
 		Field:      field,
 		Args:       nil,
 		IsMethod:   true,
-		IsResolver: true,
+		IsResolver: 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 ec.resolvers.VideoMetadata().Media(rctx, obj)
+		return obj.Media(), nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6393,14 +6371,14 @@ func (ec *executionContext) _VideoMetadata_bitrate(ctx context.Context, field gr
 		Object:     "VideoMetadata",
 		Field:      field,
 		Args:       nil,
-		IsMethod:   true,
-		IsResolver: true,
+		IsMethod:   false,
+		IsResolver: 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 ec.resolvers.VideoMetadata().Bitrate(rctx, obj)
+		return obj.Bitrate, nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -6409,9 +6387,9 @@ func (ec *executionContext) _VideoMetadata_bitrate(ctx context.Context, field gr
 	if resTmp == nil {
 		return graphql.Null
 	}
-	res := resTmp.(*int)
+	res := resTmp.(*string)
 	fc.Result = res
-	return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) _VideoMetadata_colorProfile(ctx context.Context, field graphql.CollectedField, obj *models.VideoMetadata) (ret graphql.Marshaler) {
@@ -7629,19 +7607,10 @@ func (ec *executionContext) _Album(ctx context.Context, sel ast.SelectionSet, ob
 		case "__typename":
 			out.Values[i] = graphql.MarshalString("Album")
 		case "id":
-			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._Album_id(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&invalids, 1)
-				}
-				return res
-			})
+			out.Values[i] = ec._Album_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&invalids, 1)
+			}
 		case "title":
 			out.Values[i] = ec._Album_title(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
@@ -7780,19 +7749,10 @@ func (ec *executionContext) _Media(ctx context.Context, sel ast.SelectionSet, ob
 		case "__typename":
 			out.Values[i] = graphql.MarshalString("Media")
 		case "id":
-			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._Media_id(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&invalids, 1)
-				}
-				return res
-			})
+			out.Values[i] = ec._Media_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&invalids, 1)
+			}
 		case "title":
 			out.Values[i] = ec._Media_title(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
@@ -7941,33 +7901,15 @@ func (ec *executionContext) _MediaEXIF(ctx context.Context, sel ast.SelectionSet
 		case "__typename":
 			out.Values[i] = graphql.MarshalString("MediaEXIF")
 		case "id":
-			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._MediaEXIF_id(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&invalids, 1)
-				}
-				return res
-			})
+			out.Values[i] = ec._MediaEXIF_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
 		case "media":
-			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._MediaEXIF_media(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&invalids, 1)
-				}
-				return res
-			})
+			out.Values[i] = ec._MediaEXIF_media(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
 		case "camera":
 			out.Values[i] = ec._MediaEXIF_camera(ctx, field, obj)
 		case "maker":
@@ -8465,19 +8407,10 @@ func (ec *executionContext) _ShareToken(ctx context.Context, sel ast.SelectionSe
 		case "__typename":
 			out.Values[i] = graphql.MarshalString("ShareToken")
 		case "id":
-			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._ShareToken_id(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&invalids, 1)
-				}
-				return res
-			})
+			out.Values[i] = ec._ShareToken_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&invalids, 1)
+			}
 		case "token":
 			out.Values[i] = ec._ShareToken_token(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
@@ -8588,33 +8521,24 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
 		case "__typename":
 			out.Values[i] = graphql.MarshalString("User")
 		case "id":
-			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._User_id(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&invalids, 1)
-				}
-				return res
-			})
+			out.Values[i] = ec._User_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
 		case "username":
 			out.Values[i] = ec._User_username(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				atomic.AddUint32(&invalids, 1)
+				invalids++
 			}
 		case "rootPath":
 			out.Values[i] = ec._User_rootPath(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				atomic.AddUint32(&invalids, 1)
+				invalids++
 			}
 		case "admin":
 			out.Values[i] = ec._User_admin(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				atomic.AddUint32(&invalids, 1)
+				invalids++
 			}
 		default:
 			panic("unknown field " + strconv.Quote(field.Name))
@@ -8639,63 +8563,36 @@ func (ec *executionContext) _VideoMetadata(ctx context.Context, sel ast.Selectio
 		case "__typename":
 			out.Values[i] = graphql.MarshalString("VideoMetadata")
 		case "id":
-			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._VideoMetadata_id(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&invalids, 1)
-				}
-				return res
-			})
+			out.Values[i] = ec._VideoMetadata_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
 		case "media":
-			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._VideoMetadata_media(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&invalids, 1)
-				}
-				return res
-			})
+			out.Values[i] = ec._VideoMetadata_media(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
 		case "width":
 			out.Values[i] = ec._VideoMetadata_width(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				atomic.AddUint32(&invalids, 1)
+				invalids++
 			}
 		case "height":
 			out.Values[i] = ec._VideoMetadata_height(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				atomic.AddUint32(&invalids, 1)
+				invalids++
 			}
 		case "duration":
 			out.Values[i] = ec._VideoMetadata_duration(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				atomic.AddUint32(&invalids, 1)
+				invalids++
 			}
 		case "codec":
 			out.Values[i] = ec._VideoMetadata_codec(ctx, field, obj)
 		case "framerate":
 			out.Values[i] = ec._VideoMetadata_framerate(ctx, field, obj)
 		case "bitrate":
-			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._VideoMetadata_bitrate(ctx, field, obj)
-				return res
-			})
+			out.Values[i] = ec._VideoMetadata_bitrate(ctx, field, obj)
 		case "colorProfile":
 			out.Values[i] = ec._VideoMetadata_colorProfile(ctx, field, obj)
 		case "audio":
@@ -9072,6 +8969,51 @@ func (ec *executionContext) marshalNFloat2float64(ctx context.Context, sel ast.S
 	return res
 }
 
+func (ec *executionContext) unmarshalNID2int(ctx context.Context, v interface{}) (int, error) {
+	res, err := graphql.UnmarshalIntID(v)
+	return res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalNID2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler {
+	res := graphql.MarshalIntID(v)
+	if res == graphql.Null {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "must not be null")
+		}
+	}
+	return res
+}
+
+func (ec *executionContext) unmarshalNID2ᚕintᚄ(ctx context.Context, v interface{}) ([]int, error) {
+	var vSlice []interface{}
+	if v != nil {
+		if tmp1, ok := v.([]interface{}); ok {
+			vSlice = tmp1
+		} else {
+			vSlice = []interface{}{v}
+		}
+	}
+	var err error
+	res := make([]int, len(vSlice))
+	for i := range vSlice {
+		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
+		res[i], err = ec.unmarshalNID2int(ctx, vSlice[i])
+		if err != nil {
+			return nil, err
+		}
+	}
+	return res, nil
+}
+
+func (ec *executionContext) marshalNID2ᚕintᚄ(ctx context.Context, sel ast.SelectionSet, v []int) graphql.Marshaler {
+	ret := make(graphql.Array, len(v))
+	for i := range v {
+		ret[i] = ec.marshalNID2int(ctx, sel, v[i])
+	}
+
+	return ret
+}
+
 func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) {
 	res, err := graphql.UnmarshalInt(v)
 	return res, graphql.ErrorOnPath(ctx, err)
@@ -9102,36 +9044,6 @@ func (ec *executionContext) marshalNInt2int64(ctx context.Context, sel ast.Selec
 	return res
 }
 
-func (ec *executionContext) unmarshalNInt2ᚕintᚄ(ctx context.Context, v interface{}) ([]int, error) {
-	var vSlice []interface{}
-	if v != nil {
-		if tmp1, ok := v.([]interface{}); ok {
-			vSlice = tmp1
-		} else {
-			vSlice = []interface{}{v}
-		}
-	}
-	var err error
-	res := make([]int, len(vSlice))
-	for i := range vSlice {
-		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
-		res[i], err = ec.unmarshalNInt2int(ctx, vSlice[i])
-		if err != nil {
-			return nil, err
-		}
-	}
-	return res, nil
-}
-
-func (ec *executionContext) marshalNInt2ᚕintᚄ(ctx context.Context, sel ast.SelectionSet, v []int) graphql.Marshaler {
-	ret := make(graphql.Array, len(v))
-	for i := range v {
-		ret[i] = ec.marshalNInt2int(ctx, sel, v[i])
-	}
-
-	return ret
-}
-
 func (ec *executionContext) marshalNMedia2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMedia(ctx context.Context, sel ast.SelectionSet, v models.Media) graphql.Marshaler {
 	return ec._Media(ctx, sel, &v)
 }
diff --git a/api/graphql/models/album.go b/api/graphql/models/album.go
index b4d8676..161e9a3 100644
--- a/api/graphql/models/album.go
+++ b/api/graphql/models/album.go
@@ -8,11 +8,11 @@ import (
 )
 
 type Album struct {
-	gorm.Model
+	Model
 	Title         string
-	ParentAlbumID *uint
+	ParentAlbumID *int
 	ParentAlbum   *Album
-	OwnerID       uint
+	OwnerID       int
 	Owner         User
 	Path          string
 	PathHash      string `gorm:"unique"`
diff --git a/api/graphql/models/base.go b/api/graphql/models/base.go
new file mode 100644
index 0000000..9d4147d
--- /dev/null
+++ b/api/graphql/models/base.go
@@ -0,0 +1,14 @@
+package models
+
+import (
+	"time"
+
+	"gorm.io/gorm"
+)
+
+type Model struct {
+	ID        int `gorm:"primarykey"`
+	CreatedAt time.Time
+	UpdatedAt time.Time
+	DeletedAt gorm.DeletedAt `gorm:"index"`
+}
diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go
index 3413a2e..09b0b8c 100644
--- a/api/graphql/models/generated.go
+++ b/api/graphql/models/generated.go
@@ -51,15 +51,6 @@ type SearchResult struct {
 	Media  []*Media `json:"media"`
 }
 
-// General information about the site
-type SiteInfo struct {
-	InitialSetup bool `json:"initialSetup"`
-	// How often automatic scans should be initiated in seconds
-	PeriodicScanInterval int `json:"periodicScanInterval"`
-	// How many max concurrent scanner jobs that should run at once
-	ConcurrentWorkers int `json:"concurrentWorkers"`
-}
-
 type MediaType string
 
 const (
diff --git a/api/graphql/models/media.go b/api/graphql/models/media.go
index 2651e04..4bc70f6 100644
--- a/api/graphql/models/media.go
+++ b/api/graphql/models/media.go
@@ -6,24 +6,23 @@ import (
 	"time"
 
 	"github.com/viktorstrate/photoview/api/utils"
-	"gorm.io/gorm"
 )
 
 type Media struct {
-	gorm.Model
+	Model
 	Title           string
 	Path            string
 	PathHash        string
-	AlbumID         uint
+	AlbumID         int
 	Album           Album
-	ExifID          *uint
+	ExifID          *int
 	Exif            *MediaEXIF
 	MediaURL        []MediaURL
 	DateShot        time.Time
 	DateImported    time.Time
 	Favorite        bool
 	Type            MediaType
-	VideoMetadataID *uint
+	VideoMetadataID *int
 	VideoMetadata   *VideoMetadata
 	SideCarPath     *string
 	SideCarHash     *string
@@ -44,8 +43,8 @@ const (
 )
 
 type MediaURL struct {
-	gorm.Model
-	MediaID     uint
+	Model
+	MediaID     int
 	Media       Media
 	MediaName   string
 	Width       int
diff --git a/api/graphql/models/media_exif.go b/api/graphql/models/media_exif.go
index 6090cbb..f4a98f8 100644
--- a/api/graphql/models/media_exif.go
+++ b/api/graphql/models/media_exif.go
@@ -2,12 +2,10 @@ package models
 
 import (
 	"time"
-
-	"gorm.io/gorm"
 )
 
 type MediaEXIF struct {
-	gorm.Model
+	Model
 	Camera          *string
 	Maker           *string
 	Lens            *string
@@ -22,3 +20,7 @@ type MediaEXIF struct {
 	GPSLatitude     *float64
 	GPSLonitude     *float64
 }
+
+func (exif *MediaEXIF) Media() *Media {
+	panic("not implemented")
+}
diff --git a/api/graphql/models/share_token.go b/api/graphql/models/share_token.go
index a60a5cd..055eebf 100644
--- a/api/graphql/models/share_token.go
+++ b/api/graphql/models/share_token.go
@@ -2,20 +2,18 @@ package models
 
 import (
 	"time"
-
-	"gorm.io/gorm"
 )
 
 type ShareToken struct {
-	gorm.Model
+	Model
 	Value    string
-	OwnerID  uint
+	OwnerID  int
 	Owner    User
 	Expire   *time.Time
 	Password *string
-	AlbumID  *uint
+	AlbumID  *int
 	Album    Album
-	MediaID  *uint
+	MediaID  *int
 	Media    *Media
 }
 
diff --git a/api/graphql/models/site_info.go b/api/graphql/models/site_info.go
index 0dc6f19..97b9bec 100644
--- a/api/graphql/models/site_info.go
+++ b/api/graphql/models/site_info.go
@@ -6,7 +6,7 @@ import (
 )
 
 type SiteInfo struct {
-	gorm.Model
+	Model
 	InitialSetup         bool
 	PeriodicScanInterval int
 	ConcurrentWorkers    int
diff --git a/api/graphql/models/user.go b/api/graphql/models/user.go
index 5b8d6a8..864c70d 100644
--- a/api/graphql/models/user.go
+++ b/api/graphql/models/user.go
@@ -13,7 +13,7 @@ import (
 )
 
 type User struct {
-	gorm.Model
+	Model
 	Username string  `gorm:"unique,size:128"`
 	Password *string `gorm:"size:256`
 	RootPath string  `gorm:"size:512`
@@ -25,8 +25,8 @@ type User struct {
 // }
 
 type AccessToken struct {
-	gorm.Model
-	UserID uint
+	Model
+	UserID int
 	User   User   `gorm:"constraint:OnDelete:CASCADE;"`
 	Value  string `gorm:"size:24`
 	Expire time.Time
diff --git a/api/graphql/models/utils.go b/api/graphql/models/utils.go
index 5cec5a4..6a446b4 100644
--- a/api/graphql/models/utils.go
+++ b/api/graphql/models/utils.go
@@ -6,6 +6,8 @@ import (
 )
 
 func (filter *Filter) FormatSQL(context string) (string, error) {
+	// TODO: Migrate method to gorm
+
 	if filter == nil {
 		return "", nil
 	}
diff --git a/api/graphql/models/video_metadata.go b/api/graphql/models/video_metadata.go
index 727e496..75f2efa 100644
--- a/api/graphql/models/video_metadata.go
+++ b/api/graphql/models/video_metadata.go
@@ -1,11 +1,7 @@
 package models
 
-import (
-	"gorm.io/gorm"
-)
-
 type VideoMetadata struct {
-	gorm.Model
+	Model
 	Width        int
 	Height       int
 	Duration     float64
@@ -15,3 +11,7 @@ type VideoMetadata struct {
 	ColorProfile *string
 	Audio        *string
 }
+
+func (metadata *VideoMetadata) Media() *Media {
+	panic("not implemented")
+}
diff --git a/api/graphql/resolvers/album.go b/api/graphql/resolvers/album.go
index 5d0353a..f4b98e3 100644
--- a/api/graphql/resolvers/album.go
+++ b/api/graphql/resolvers/album.go
@@ -2,7 +2,8 @@ package resolvers
 
 import (
 	"context"
-	"database/sql"
+	"fmt"
+	"log"
 
 	api "github.com/viktorstrate/photoview/api/graphql"
 	"github.com/viktorstrate/photoview/api/graphql/auth"
@@ -15,41 +16,26 @@ func (r *queryResolver) MyAlbums(ctx context.Context, filter *models.Filter, onl
 		return nil, auth.ErrUnauthorized
 	}
 
-	filterSQL, err := filter.FormatSQL("album")
-	if err != nil {
-		return nil, err
+	query := r.Database.Where("owner_id = ?", user.ID)
+
+	if onlyRoot != nil && *onlyRoot == true {
+		query = query.Where("parent_album = ()", query.Model(&models.Album{})).Select("id").Where("parent_album IS NULL AND owner_id = ?", user.ID)
 	}
 
-	var rows *sql.Rows
+	if showEmpty == nil || *showEmpty == false {
+		subQuery := r.Database.Model(&models.Media{}).Where("album_id = album.album_id")
 
-	filterFavorites := " AND favorite = 1"
-	if onlyWithFavorites == nil || *onlyWithFavorites == false {
-		filterFavorites = ""
-	}
-
-	filterEmpty := " AND EXISTS (SELECT * FROM media WHERE album_id = album.album_id" + filterFavorites + ") "
-	if showEmpty != nil && *showEmpty == true && (onlyWithFavorites == nil || *onlyWithFavorites == false) {
-		filterEmpty = ""
-	}
-
-	if onlyRoot == nil || *onlyRoot == false {
-		rows, err = r.Database.Query("SELECT * FROM album WHERE owner_id = ?"+filterEmpty+filterSQL, user.UserID)
-		if err != nil {
-			return nil, err
-		}
-	} else {
-		rows, err = r.Database.Query(`
-			SELECT * FROM album WHERE owner_id = ? AND parent_album = (
-				SELECT album_id FROM album WHERE parent_album IS NULL AND owner_id = ?
-			)
-		`+filterEmpty+filterSQL, user.UserID, user.UserID)
-		if err != nil {
-			return nil, err
+		if onlyWithFavorites != nil && *onlyWithFavorites == true {
+			subQuery = subQuery.Where("favorite = 1")
 		}
+
+		query = query.Where("EXISTS (?)", subQuery)
 	}
 
-	albums, err := models.NewAlbumsFromRows(rows)
-	if err != nil {
+	// TODO: Incorporate models.FormatSQL
+
+	var albums []*models.Album
+	if err := query.Find(albums).Error; err != nil {
 		return nil, err
 	}
 
@@ -62,13 +48,12 @@ func (r *queryResolver) Album(ctx context.Context, id int) (*models.Album, error
 		return nil, auth.ErrUnauthorized
 	}
 
-	row := r.Database.QueryRow("SELECT * FROM album WHERE album_id = ? AND owner_id = ?", id, user.UserID)
-	album, err := models.NewAlbumFromRow(row)
-	if err != nil {
+	var album models.Album
+	if err := r.Database.Where("owner_id = ?", user.ID).First(&album, id).Error; err != nil {
 		return nil, err
 	}
 
-	return album, nil
+	return &album, nil
 }
 
 func (r *Resolver) Album() api.AlbumResolver {
@@ -77,32 +62,21 @@ func (r *Resolver) Album() api.AlbumResolver {
 
 type albumResolver struct{ *Resolver }
 
-func (r *albumResolver) Media(ctx context.Context, obj *models.Album, filter *models.Filter, onlyFavorites *bool) ([]*models.Media, error) {
+func (r *albumResolver) Media(ctx context.Context, album *models.Album, filter *models.Filter, onlyFavorites *bool) ([]*models.Media, error) {
 
-	filterSQL, err := filter.FormatSQL("media")
-	if err != nil {
-		return nil, err
+	query := r.Database.
+		Joins("Album").
+		Where("Album.id = ?", album.ID).
+		Where("media.id IN (?)", r.Database.Model(&models.MediaURL{})).Select("media_id").Where("media_url.media_id = media.id")
+
+	if onlyFavorites != nil && *onlyFavorites == true {
+		query = query.Where("media.favorite = 1")
 	}
 
-	filterFavorites := " AND media.favorite = 1 "
-	if onlyFavorites == nil || *onlyFavorites == false {
-		filterFavorites = ""
-	}
+	// TODO: Incorporate filter.FormatSQL
 
-	mediaRows, err := r.Database.Query(`
-		SELECT media.* FROM album, media
-		WHERE album.album_id = ? AND media.album_id = album.album_id
-		AND media.media_id IN (
-			SELECT media_id FROM media_url WHERE media_url.media_id = media.media_id
-		)
-	`+filterFavorites+filterSQL, obj.AlbumID)
-	if err != nil {
-		return nil, err
-	}
-	defer mediaRows.Close()
-
-	media, err := models.NewMediaFromRows(mediaRows)
-	if err != nil {
+	var media []*models.Media
+	if err := query.Find(&media).Error; err != nil {
 		return nil, err
 	}
 
@@ -111,47 +85,44 @@ func (r *albumResolver) Media(ctx context.Context, obj *models.Album, filter *mo
 
 func (r *albumResolver) Thumbnail(ctx context.Context, obj *models.Album) (*models.Media, error) {
 
-	row := r.Database.QueryRow(`
-		WITH recursive sub_albums AS (
-			SELECT * FROM album AS root WHERE album_id = ?
-			UNION ALL
-			SELECT child.* FROM album AS child JOIN sub_albums ON child.parent_album = sub_albums.album_id
-		)
+	log.Println("TODO: Album thumbnail migrated yet")
 
-		SELECT * FROM media WHERE media.album_id IN (
-			SELECT album_id FROM sub_albums
-		) AND media.media_id IN (
-			SELECT media_id FROM media_url WHERE media_url.media_id = media.media_id
-		) LIMIT 1
-	`, obj.AlbumID)
+	return nil, nil
 
-	media, err := models.NewMediaFromRow(row)
-	if err != nil {
-		if err == sql.ErrNoRows {
-			return nil, nil
-		} else {
-			return nil, err
-		}
-	}
+	// row := r.Database.QueryRow(`
+	// 	WITH recursive sub_albums AS (
+	// 		SELECT * FROM album AS root WHERE album_id = ?
+	// 		UNION ALL
+	// 		SELECT child.* FROM album AS child JOIN sub_albums ON child.parent_album = sub_albums.album_id
+	// 	)
 
-	return media, nil
+	// 	SELECT * FROM media WHERE media.album_id IN (
+	// 		SELECT album_id FROM sub_albums
+	// 	) AND media.media_id IN (
+	// 		SELECT media_id FROM media_url WHERE media_url.media_id = media.media_id
+	// 	) LIMIT 1
+	// `, obj.AlbumID)
+
+	// media, err := models.NewMediaFromRow(row)
+	// if err != nil {
+	// 	if err == sql.ErrNoRows {
+	// 		return nil, nil
+	// 	} else {
+	// 		return nil, err
+	// 	}
+	// }
+
+	// return media, nil
 }
 
-func (r *albumResolver) SubAlbums(ctx context.Context, obj *models.Album, filter *models.Filter) ([]*models.Album, error) {
-	filterSQL, err := filter.FormatSQL("album")
-	if err != nil {
+func (r *albumResolver) SubAlbums(ctx context.Context, parent *models.Album, filter *models.Filter) ([]*models.Album, error) {
+
+	var albums []*models.Album
+	if err := r.Database.Where("parent_album = ?", parent.ID).Find(albums).Error; err != nil {
 		return nil, err
 	}
 
-	rows, err := r.Database.Query("SELECT * FROM album WHERE parent_album = ?"+filterSQL, obj.AlbumID)
-	if err != nil {
-		return nil, err
-	}
-
-	albums, err := models.NewAlbumsFromRows(rows)
-	if err != nil {
-		return nil, err
-	}
+	// TODO: Incorporate filter.FormatSQL
 
 	return albums, nil
 }
@@ -164,34 +135,38 @@ func (r *albumResolver) Owner(ctx context.Context, obj *models.Album) (*models.U
 	panic("not implemented")
 }
 
-func (r *albumResolver) Shares(ctx context.Context, obj *models.Album) ([]*models.ShareToken, error) {
-	rows, err := r.Database.Query("SELECT * FROM share_token WHERE album_id = ?", obj.ID())
-	if err != nil {
+func (r *albumResolver) Shares(ctx context.Context, album *models.Album) ([]*models.ShareToken, error) {
+
+	var shareTokens []*models.ShareToken
+	if err := r.Database.Where("album_id = ?", album.ID).Find(shareTokens).Error; err != nil {
 		return nil, err
 	}
-	defer rows.Close()
 
-	return models.NewShareTokensFromRows(rows)
+	return shareTokens, nil
 }
 
 func (r *albumResolver) Path(ctx context.Context, obj *models.Album) ([]*models.Album, error) {
-	user := auth.UserFromContext(ctx)
-	if user == nil {
-		empty := make([]*models.Album, 0)
-		return empty, nil
-	}
 
-	rows, err := r.Database.Query(`
-		WITH recursive path_albums AS (
-			SELECT * FROM album anchor WHERE anchor.album_id = ?
-			UNION
-			SELECT parent.* FROM path_albums child JOIN album parent ON parent.album_id = child.parent_album
-		)
-		SELECT * FROM path_albums WHERE album_id != ? AND owner_id = ?
-	`, obj.AlbumID, obj.AlbumID, user.UserID)
-	if err != nil {
-		return nil, err
-	}
+	fmt.Println("TODO: Album path not migrated yet")
 
-	return models.NewAlbumsFromRows(rows)
+	return make([]*models.Album, 0), nil
+	// user := auth.UserFromContext(ctx)
+	// if user == nil {
+	// 	empty := make([]*models.Album, 0)
+	// 	return empty, nil
+	// }
+
+	// rows, err := r.Database.Query(`
+	// 	WITH recursive path_albums AS (
+	// 		SELECT * FROM album anchor WHERE anchor.album_id = ?
+	// 		UNION
+	// 		SELECT parent.* FROM path_albums child JOIN album parent ON parent.album_id = child.parent_album
+	// 	)
+	// 	SELECT * FROM path_albums WHERE album_id != ? AND owner_id = ?
+	// `, obj.AlbumID, obj.AlbumID, user.UserID)
+	// if err != nil {
+	// 	return nil, err
+	// }
+
+	// return models.NewAlbumsFromRows(rows)
 }
diff --git a/api/graphql/resolvers/mediaGeoJson.go b/api/graphql/resolvers/mediaGeoJson.go
index ea4de3e..0f12f1a 100644
--- a/api/graphql/resolvers/mediaGeoJson.go
+++ b/api/graphql/resolvers/mediaGeoJson.go
@@ -2,12 +2,7 @@ package resolvers
 
 import (
 	"context"
-	"errors"
 	"os"
-	"path"
-
-	"github.com/viktorstrate/photoview/api/graphql/auth"
-	"github.com/viktorstrate/photoview/api/utils"
 )
 
 type geoJSONFeatureCollection struct {
@@ -62,69 +57,71 @@ func makeGeoJSONFeatureGeometryPoint(lat float64, long float64) geoJSONFeatureGe
 
 func (r *queryResolver) MyMediaGeoJSON(ctx context.Context) (interface{}, error) {
 
-	user := auth.UserFromContext(ctx)
-	if user == nil {
-		return nil, errors.New("unauthorized")
-	}
+	// user := auth.UserFromContext(ctx)
+	// if user == nil {
+	// 	return nil, errors.New("unauthorized")
+	// }
 
-	rows, err := r.Database.Query(`
-	SELECT media.media_id, media.title,
-	  url.media_name AS thumbnail_name, url.width AS thumbnail_width, url.height AS thumbnail_height,
-	  exif.gps_latitude, exif.gps_longitude FROM media_exif exif
-	INNER JOIN media ON exif.exif_id = media.exif_id
-	INNER JOIN media_url url ON media.media_id = url.media_id
-	INNER JOIN album ON media.album_id = album.album_id
-	WHERE exif.gps_latitude IS NOT NULL
-	  AND exif.gps_longitude IS NOT NULL
-		AND url.purpose = 'thumbnail'
-		AND album.owner_id = ?;
-	`, user.UserID)
-	defer rows.Close()
-	if err != nil {
-		return nil, err
-	}
+	// rows, err := r.Database.Query(`
+	// SELECT media.media_id, media.title,
+	//   url.media_name AS thumbnail_name, url.width AS thumbnail_width, url.height AS thumbnail_height,
+	//   exif.gps_latitude, exif.gps_longitude FROM media_exif exif
+	// INNER JOIN media ON exif.exif_id = media.exif_id
+	// INNER JOIN media_url url ON media.media_id = url.media_id
+	// INNER JOIN album ON media.album_id = album.album_id
+	// WHERE exif.gps_latitude IS NOT NULL
+	//   AND exif.gps_longitude IS NOT NULL
+	// 	AND url.purpose = 'thumbnail'
+	// 	AND album.owner_id = ?;
+	// `, user.UserID)
+	// defer rows.Close()
+	// if err != nil {
+	// 	return nil, err
+	// }
 
-	features := make([]geoJSONFeature, 0)
+	// features := make([]geoJSONFeature, 0)
 
-	for rows.Next() {
+	// for rows.Next() {
 
-		var mediaID int
-		var mediaTitle string
-		var thumbnailName string
-		var thumbnailWidth int
-		var thumbnailHeight int
-		var latitude float64
-		var longitude float64
+	// 	var mediaID int
+	// 	var mediaTitle string
+	// 	var thumbnailName string
+	// 	var thumbnailWidth int
+	// 	var thumbnailHeight int
+	// 	var latitude float64
+	// 	var longitude float64
 
-		if err := rows.Scan(&mediaID, &mediaTitle, &thumbnailName, &thumbnailWidth, &thumbnailHeight, &latitude, &longitude); err != nil {
-			return nil, err
-		}
+	// 	if err := rows.Scan(&mediaID, &mediaTitle, &thumbnailName, &thumbnailWidth, &thumbnailHeight, &latitude, &longitude); err != nil {
+	// 		return nil, err
+	// 	}
 
-		geoPoint := makeGeoJSONFeatureGeometryPoint(latitude, longitude)
+	// 	geoPoint := makeGeoJSONFeatureGeometryPoint(latitude, longitude)
 
-		thumbnailURL := utils.ApiEndpointUrl()
-		thumbnailURL.Path = path.Join(thumbnailURL.Path, "photo", thumbnailName)
+	// 	thumbnailURL := utils.ApiEndpointUrl()
+	// 	thumbnailURL.Path = path.Join(thumbnailURL.Path, "photo", thumbnailName)
 
-		properties := geoJSONMediaProperties{
-			MediaID:    mediaID,
-			MediaTitle: mediaTitle,
-			Thumbnail: struct {
-				URL    string `json:"url"`
-				Width  int    `json:"width"`
-				Height int    `json:"height"`
-			}{
-				URL:    thumbnailURL.String(),
-				Width:  thumbnailWidth,
-				Height: thumbnailHeight,
-			},
-		}
+	// 	properties := geoJSONMediaProperties{
+	// 		MediaID:    mediaID,
+	// 		MediaTitle: mediaTitle,
+	// 		Thumbnail: struct {
+	// 			URL    string `json:"url"`
+	// 			Width  int    `json:"width"`
+	// 			Height int    `json:"height"`
+	// 		}{
+	// 			URL:    thumbnailURL.String(),
+	// 			Width:  thumbnailWidth,
+	// 			Height: thumbnailHeight,
+	// 		},
+	// 	}
 
-		features = append(features, makeGeoJSONFeature(properties, geoPoint))
-	}
+	// 	features = append(features, makeGeoJSONFeature(properties, geoPoint))
+	// }
 
-	featureCollection := makeGeoJSONFeatureCollection(features)
+	// featureCollection := makeGeoJSONFeatureCollection(features)
 
-	return featureCollection, nil
+	// return featureCollection, nil
+
+	panic("to be migrated")
 }
 
 func (r *queryResolver) MapboxToken(ctx context.Context) (*string, error) {
diff --git a/api/graphql/resolvers/search.go b/api/graphql/resolvers/search.go
index 47a7d01..4706495 100644
--- a/api/graphql/resolvers/search.go
+++ b/api/graphql/resolvers/search.go
@@ -3,71 +3,70 @@ package resolvers
 import (
 	"context"
 
-	"github.com/pkg/errors"
-	"github.com/viktorstrate/photoview/api/graphql/auth"
 	"github.com/viktorstrate/photoview/api/graphql/models"
 )
 
 func (r *Resolver) Search(ctx context.Context, query string, _limitMedia *int, _limitAlbums *int) (*models.SearchResult, error) {
-	user := auth.UserFromContext(ctx)
-	if user == nil {
-		return nil, auth.ErrUnauthorized
-	}
+	// user := auth.UserFromContext(ctx)
+	// if user == nil {
+	// 	return nil, auth.ErrUnauthorized
+	// }
 
-	limitMedia := 10
-	limitAlbums := 10
+	// limitMedia := 10
+	// limitAlbums := 10
 
-	if _limitMedia != nil {
-		limitMedia = *_limitMedia
-	}
+	// if _limitMedia != nil {
+	// 	limitMedia = *_limitMedia
+	// }
 
-	if _limitAlbums != nil {
-		limitAlbums = *_limitAlbums
-	}
+	// if _limitAlbums != nil {
+	// 	limitAlbums = *_limitAlbums
+	// }
 
-	wildQuery := "%" + query + "%"
+	// wildQuery := "%" + query + "%"
 
-	photoRows, err := r.Database.Query(`
-		SELECT media.* FROM media JOIN album ON media.album_id = album.album_id
-		WHERE album.owner_id = ? AND ( media.title LIKE ? OR media.path LIKE ? )
-		ORDER BY (
-			case when media.title LIKE ? then 2
-			     when media.path LIKE ? then 1
-			end ) DESC
-		LIMIT ?
-	`, user.UserID, wildQuery, wildQuery, wildQuery, wildQuery, limitMedia)
-	if err != nil {
-		return nil, errors.Wrapf(err, "searching media")
-	}
+	// photoRows, err := r.Database.Query(`
+	// 	SELECT media.* FROM media JOIN album ON media.album_id = album.album_id
+	// 	WHERE album.owner_id = ? AND ( media.title LIKE ? OR media.path LIKE ? )
+	// 	ORDER BY (
+	// 		case when media.title LIKE ? then 2
+	// 		     when media.path LIKE ? then 1
+	// 		end ) DESC
+	// 	LIMIT ?
+	// `, user.UserID, wildQuery, wildQuery, wildQuery, wildQuery, limitMedia)
+	// if err != nil {
+	// 	return nil, errors.Wrapf(err, "searching media")
+	// }
 
-	photos, err := models.NewMediaFromRows(photoRows)
-	if err != nil {
-		return nil, err
-	}
+	// photos, err := models.NewMediaFromRows(photoRows)
+	// if err != nil {
+	// 	return nil, err
+	// }
 
-	albumRows, err := r.Database.Query(`
-		SELECT * FROM album
-		WHERE owner_id = ? AND ( title LIKE ? OR path LIKE ? )
-		ORDER BY (
-			case when title LIKE ? then 2
-			     when path LIKE ? then 1
-			end ) DESC
-		LIMIT ?
-	`, user.UserID, wildQuery, wildQuery, wildQuery, wildQuery, limitAlbums)
-	if err != nil {
-		return nil, errors.Wrapf(err, "searching albums")
-	}
+	// albumRows, err := r.Database.Query(`
+	// 	SELECT * FROM album
+	// 	WHERE owner_id = ? AND ( title LIKE ? OR path LIKE ? )
+	// 	ORDER BY (
+	// 		case when title LIKE ? then 2
+	// 		     when path LIKE ? then 1
+	// 		end ) DESC
+	// 	LIMIT ?
+	// `, user.UserID, wildQuery, wildQuery, wildQuery, wildQuery, limitAlbums)
+	// if err != nil {
+	// 	return nil, errors.Wrapf(err, "searching albums")
+	// }
 
-	albums, err := models.NewAlbumsFromRows(albumRows)
-	if err != nil {
-		return nil, err
-	}
+	// albums, err := models.NewAlbumsFromRows(albumRows)
+	// if err != nil {
+	// 	return nil, err
+	// }
 
-	result := models.SearchResult{
-		Query:  query,
-		Media:  photos,
-		Albums: albums,
-	}
+	// result := models.SearchResult{
+	// 	Query:  query,
+	// 	Media:  photos,
+	// 	Albums: albums,
+	// }
 
-	return &result, nil
+	// return &result, nil
+	panic("to be migrated")
 }
diff --git a/api/graphql/resolvers/share_token.go b/api/graphql/resolvers/share_token.go
index c1d8792..8fcb32c 100644
--- a/api/graphql/resolvers/share_token.go
+++ b/api/graphql/resolvers/share_token.go
@@ -111,7 +111,7 @@ func (r *queryResolver) ShareTokenValidatePassword(ctx context.Context, tokenVal
 	return true, nil
 }
 
-func (r *mutationResolver) ShareAlbum(ctx context.Context, albumID uint, expire *time.Time, password *string) (*models.ShareToken, error) {
+func (r *mutationResolver) ShareAlbum(ctx context.Context, albumID int, expire *time.Time, password *string) (*models.ShareToken, error) {
 	user := auth.UserFromContext(ctx)
 	if user == nil {
 		return nil, auth.ErrUnauthorized
@@ -152,7 +152,7 @@ func (r *mutationResolver) ShareAlbum(ctx context.Context, albumID uint, expire
 	return &shareToken, nil
 }
 
-func (r *mutationResolver) ShareMedia(ctx context.Context, mediaID uint, expire *time.Time, password *string) (*models.ShareToken, error) {
+func (r *mutationResolver) ShareMedia(ctx context.Context, mediaID int, expire *time.Time, password *string) (*models.ShareToken, error) {
 	user := auth.UserFromContext(ctx)
 	if user == nil {
 		return nil, auth.ErrUnauthorized
diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql
index 05544ba..3c8e95c 100644
--- a/api/graphql/schema.graphql
+++ b/api/graphql/schema.graphql
@@ -34,15 +34,15 @@ type Query {
     onlyWithFavorites: Boolean
   ): [Album!]!
   "Get album by id, user must own the album or be admin"
-  album(id: Int!): Album!
+  album(id: ID!): Album!
 
   "List of media owned by the logged in user"
   myMedia(filter: Filter): [Media!]!
   "Get media by id, user must own the media or be admin"
-  media(id: Int!): Media!
+  media(id: ID!): Media!
 
   "Get a list of media by their ids, user must own the media or be admin"
-  mediaList(ids: [Int!]!): [Media!]!
+  mediaList(ids: [ID!]!): [Media!]!
 
   "Get media owned by the logged in user, returned in GeoJson format"
   myMediaGeoJson: Any!
@@ -75,22 +75,22 @@ type Mutation {
   "Scan all users for new media"
   scanAll: ScannerResult! @isAdmin
   "Scan a single user for new media"
-  scanUser(userId: Int!): ScannerResult!
+  scanUser(userId: ID!): ScannerResult!
 
   "Generate share token for album"
-  shareAlbum(albumId: Int!, expire: Time, password: String): ShareToken
+  shareAlbum(albumId: ID!, expire: Time, password: String): ShareToken
   "Generate share token for media"
-  shareMedia(mediaId: Int!, expire: Time, password: String): ShareToken
+  shareMedia(mediaId: ID!, expire: Time, password: String): ShareToken
   "Delete a share token by it's token value"
   deleteShareToken(token: String!): ShareToken
   "Set a password for a token, if null is passed for the password argument, the password will be cleared"
   protectShareToken(token: String!, password: String): ShareToken
 
   "Mark or unmark a media as being a favorite"
-  favoriteMedia(mediaId: Int!, favorite: Boolean!): Media
+  favoriteMedia(mediaId: ID!, favorite: Boolean!): Media
 
   updateUser(
-    id: Int!
+    id: ID!
     username: String
     rootPath: String
     password: String
@@ -102,7 +102,7 @@ type Mutation {
     password: String
     admin: Boolean!
   ): User @isAdmin
-  deleteUser(id: Int!): User @isAdmin
+  deleteUser(id: ID!): User @isAdmin
 
   """
   Set how often, in seconds, the server should automatically scan for new media,
@@ -152,7 +152,7 @@ type ScannerResult {
 
 "A token used to publicly access an album or media"
 type ShareToken {
-  id: Int!
+  id: ID!
   token: String!
   "The user who created the token"
   owner: User!
@@ -177,7 +177,7 @@ type SiteInfo {
 }
 
 type User {
-  id: Int!
+  id: ID!
   username: String!
   #albums: [Album]
   "Local filepath for the user's photos"
@@ -187,7 +187,7 @@ type User {
 }
 
 type Album {
-  id: Int!
+  id: ID!
   title: String!
   "The media inside this album"
   media(
@@ -232,7 +232,7 @@ enum MediaType {
 }
 
 type Media {
-  id: Int!
+  id: ID!
   title: String!
   "Local filepath for the media"
   path: String!
@@ -255,7 +255,7 @@ type Media {
 
 "EXIF metadata from the camera"
 type MediaEXIF {
-  id: Int!
+  id: ID!
   media: Media!
   "The model name of the camera"
   camera: String
@@ -279,14 +279,14 @@ type MediaEXIF {
 }
 
 type VideoMetadata {
-  id: Int!
+  id: ID!
   media: Media!
   width: Int!
   height: Int!
   duration: Float!
   codec: String
   framerate: Float
-  bitrate: Int
+  bitrate: String
   colorProfile: String
   audio: String
 }
diff --git a/api/scanner/cleanup_media.go b/api/scanner/cleanup_media.go
index a3aadd7..8a48ea2 100644
--- a/api/scanner/cleanup_media.go
+++ b/api/scanner/cleanup_media.go
@@ -11,8 +11,8 @@ import (
 	"gorm.io/gorm"
 )
 
-func CleanupMedia(db *gorm.DB, albumId uint, albumMedia []*models.Media) []error {
-	albumMediaIds := make([]uint, len(albumMedia))
+func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error {
+	albumMediaIds := make([]int, len(albumMedia))
 	for i, media := range albumMedia {
 		albumMediaIds[i] = media.ID
 	}
diff --git a/api/scanner/process_photo.go b/api/scanner/process_photo.go
index 842c10c..6e12275 100644
--- a/api/scanner/process_photo.go
+++ b/api/scanner/process_photo.go
@@ -22,7 +22,7 @@ import (
 )
 
 // Higher order function used to check if MediaURL for a given MediaPurpose exists
-func makePhotoURLChecker(tx *gorm.DB, mediaID uint) (func(purpose models.MediaPurpose) (*models.MediaURL, error), error) {
+func makePhotoURLChecker(tx *gorm.DB, mediaID int) (func(purpose models.MediaPurpose) (*models.MediaURL, error), error) {
 	return func(purpose models.MediaPurpose) (*models.MediaURL, error) {
 		var mediaURL models.MediaURL
 
diff --git a/api/scanner/scanner_media.go b/api/scanner/scanner_media.go
index 7845d29..05dde2b 100644
--- a/api/scanner/scanner_media.go
+++ b/api/scanner/scanner_media.go
@@ -47,7 +47,7 @@ func hashSideCarFile(path *string) *string {
 	return &hash
 }
 
-func ScanMedia(tx *gorm.DB, mediaPath string, albumId uint, cache *AlbumScannerCache) (*models.Media, bool, error) {
+func ScanMedia(tx *gorm.DB, mediaPath string, albumId int, cache *AlbumScannerCache) (*models.Media, bool, error) {
 	mediaName := path.Base(mediaPath)
 
 	// Check if media already exists
diff --git a/api/scanner/scanner_user.go b/api/scanner/scanner_user.go
index bd3e491..8be0c24 100644
--- a/api/scanner/scanner_user.go
+++ b/api/scanner/scanner_user.go
@@ -28,7 +28,7 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *AlbumScanner
 
 	type scanInfo struct {
 		path     string
-		parentID *uint
+		parentID *int
 	}
 
 	scanQueue := list.New()
