diff --git a/main.go b/main.go
index f3d064922d6c689a92251f6a86f9f6b3e023dd7c..f85b689c393d28747d2355d378edcd936a95aa74 100644
--- a/main.go
+++ b/main.go
@@ -3,6 +3,7 @@ package main
 import (
 	"database/sql"
 	"fmt"
+	"git.kuschku.de/justjanne/imghost-frontend/model"
 	"git.kuschku.de/justjanne/imghost-frontend/repo"
 	"github.com/go-redis/redis"
 	"github.com/gorilla/mux"
@@ -10,6 +11,35 @@ import (
 	"net/http"
 )
 
+type ImageViewModel struct {
+	User  model.User
+	Image model.Image
+}
+
+type AlbumViewModel struct {
+	User   model.User
+	Album  model.Album
+	Images []model.AlbumImage
+}
+
+func MethodOverride(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Method == http.MethodPost {
+			method := r.PostFormValue("_method")
+			if method == "" {
+				method = r.Header.Get("X-HTTP-Method-Override")
+			}
+
+			if method == http.MethodPut ||
+				method == http.MethodPatch ||
+				method == http.MethodDelete {
+				r.Method = method
+			}
+		}
+		next.ServeHTTP(w, r)
+	})
+}
+
 func main() {
 	config := NewConfigFromEnv()
 
@@ -31,6 +61,7 @@ func main() {
 
 	imageRepo := repo.NewImageRepository(pageContext.Database)
 	albumRepo := repo.NewAlbumRepository(pageContext.Database)
+	albumImageRepo := repo.NewAlbumImageRepository(pageContext.Database)
 
 	router := mux.NewRouter().StrictSlash(true)
 	router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
@@ -42,13 +73,17 @@ func main() {
 		user := parseUser(r)
 		imageId := mux.Vars(r)["imageId"]
 		image, err := imageRepo.Get(imageId)
-		if err != nil { panic(err) }
+		if err != nil {
+			panic(err)
+		}
 
-		err = formatTemplate(w, "image_detail.html", ImageDetailData{
+		err = formatTemplate(w, "image_detail.gohtml", ImageViewModel{
 			user,
 			image,
 		})
-		if err != nil { panic(err) }
+		if err != nil {
+			panic(err)
+		}
 	}).Methods("GET")
 	router.HandleFunc("/i/{imageId}", func(w http.ResponseWriter, r *http.Request) {
 		var err error
@@ -75,16 +110,22 @@ func main() {
 		user := parseUser(r)
 		albumId := mux.Vars(r)["albumId"]
 		album, err := albumRepo.Get(albumId)
-		if err != nil { panic(err) }
-		images, err := albumRepo.GetImages(album)
-		if err != nil { panic(err) }
-
-		err = formatTemplate(w, "album_detail.html", AlbumDetailData{
+		if err != nil {
+			panic(err)
+		}
+		images, err := albumImageRepo.List(album)
+		if err != nil {
+			panic(err)
+		}
+
+		err = formatTemplate(w, "album_detail.gohtml", AlbumViewModel{
 			user,
 			album,
 			images,
 		})
-		if err != nil { panic(err) }
+		if err != nil {
+			panic(err)
+		}
 	}).Methods("GET")
 	router.HandleFunc("/a/{albumId}", func(w http.ResponseWriter, r *http.Request) {
 		var err error
@@ -95,15 +136,51 @@ func main() {
 		if err != nil { panic(err) }
 
 		err = album.VerifyOwner(user)
-		if err != nil { panic(err) }
+		if err != nil {
+			panic(err)
+		}
 
 		album.Title = r.FormValue("title")
 		album.Description = r.FormValue("description")
 
 		err = albumRepo.Update(album)
-		if err != nil { panic(err) }
+		if err != nil {
+			panic(err)
+		}
+
+		http.Redirect(w, r, "/a/"+albumId, http.StatusFound)
+	}).Methods("POST")
+	router.HandleFunc("/a/{albumId}/{imageId}", func(w http.ResponseWriter, r *http.Request) {
+		var err error
+
+		user := parseUser(r)
+		vars := mux.Vars(r)
+		albumId := vars["albumId"]
+		imageId := vars["imageId"]
+		album, err := albumRepo.Get(albumId)
+		if err != nil {
+			panic(err)
+		}
+
+		err = album.VerifyOwner(user)
+		if err != nil {
+			panic(err)
+		}
+
+		albumImage, err := albumImageRepo.Get(album, imageId)
+		if err != nil {
+			panic(err)
+		}
 
-		http.Redirect(w, r, "/a/" + albumId, http.StatusFound)
+		albumImage.Title = r.FormValue("title")
+		albumImage.Description = r.FormValue("description")
+
+		err = albumImageRepo.Update(album, albumImage)
+		if err != nil {
+			panic(err)
+		}
+
+		http.Redirect(w, r, "/a/"+albumId, http.StatusFound)
 	}).Methods("POST")
 	router.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
 		_, _ = w.Write([]byte(fmt.Sprintf("postgres: %v\n", pageContext.Database.Ping())))
@@ -112,19 +189,16 @@ func main() {
 	router.HandleFunc("/{imageId}", func(w http.ResponseWriter, r *http.Request) {
 		var err error
 
-		user := parseUser(r)
 		imageId := mux.Vars(r)["imageId"]
 		image, err := imageRepo.Get(imageId)
-		if err != nil { panic(err) }
+		if err != nil {
+			panic(err)
+		}
 
-		err = formatTemplate(w, "image_detail.html", ImageDetailData{
-			user,
-			image,
-		})
-		if err != nil { panic(err) }
+		http.Redirect(w, r, fmt.Sprintf("/i/%s", image.Id), http.StatusFound)
 	})
 
-	err = http.ListenAndServe(":8080", router)
+	err = http.ListenAndServe(":8080", MethodOverride(router))
 	if err != nil {
 		panic(err)
 	}
diff --git a/model/album.go b/model/album.go
index c385d91f89e0fe44bd90c36cebe19c3b9c2300e8..59f060dec70ba7bf12a42c9d16ec690e14f25c05 100644
--- a/model/album.go
+++ b/model/album.go
@@ -11,6 +11,7 @@ type Album struct {
 	Title       string    `json:"title"`
 	Description string    `json:"description"`
 	CreatedAt   time.Time `json:"created_at"`
+	UpdatedAt   time.Time `json:"updated_at"`
 }
 
 func (album Album) VerifyOwner(user User) error {
diff --git a/model/album_image.go b/model/album_image.go
index 3379dfeb1cafd1f0e003be61cf0c3f1baa0043ac..3bb099c54d02400ca53eeb8c41283d91cb478af6 100644
--- a/model/album_image.go
+++ b/model/album_image.go
@@ -4,5 +4,4 @@ type AlbumImage struct {
 	Id          string `json:"id"`
 	Title       string `json:"title"`
 	Description string `json:"description"`
-	Position    int    `json:"position"`
 }
diff --git a/page_image_detail.go b/page_image_detail.go
deleted file mode 100644
index 8759da59b63e049a38c41743961bc339518f7512..0000000000000000000000000000000000000000
--- a/page_image_detail.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"git.kuschku.de/justjanne/imghost-frontend/model"
-	_ "github.com/lib/pq"
-	"net/http"
-	"os"
-	"path"
-)
-
-type ImageDetailData struct {
-	User   model.User
-	Image  model.Image
-}
-
-func pageImageDetail(ctx PageContext) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		user := parseUser(r)
-		_, imageId := path.Split(r.URL.Path)
-
-		result, err := ctx.Database.Query(`
-			SELECT
-				id,
-				owner,
-				coalesce(title,  ''),
-				coalesce(description, ''),
-        		coalesce(created_at, to_timestamp(0)),
-				coalesce(original_name, ''),
-				coalesce(type, '')
-			FROM images
-			WHERE id = $1
-			`, imageId)
-		if err != nil {
-			fmt.Printf("An error occured: %s", err.Error())
-			_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
-			return
-		}
-
-		var info model.Image
-
-		if result.Next() {
-			var owner string
-			err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType)
-			if err != nil {
-				fmt.Printf("An error occured: %s", err.Error())
-				_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
-				return
-			}
-
-			switch r.PostFormValue("action") {
-			case "update":
-				_, err = ctx.Database.Exec(
-					"UPDATE images SET title = $1, description = $2 WHERE id = $3 AND owner = $4",
-					r.PostFormValue("title"),
-					r.PostFormValue("description"),
-					info.Id,
-					user.Id,
-				)
-				if err != nil {
-					fmt.Printf("An error occured: %s", err.Error())
-					_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
-					return
-				}
-				if r.PostFormValue("from_js") == "true" {
-					_ = returnJson(w, true)
-				} else {
-					http.Redirect(w, r, r.URL.Path, http.StatusFound)
-				}
-				return
-			case "delete":
-				_, err = ctx.Database.Exec("DELETE FROM images WHERE id = $1 AND owner = $2", info.Id, user.Id)
-				if err != nil {
-					fmt.Printf("An error occured: %s", err.Error())
-					_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
-					return
-				}
-				for _, definition := range ctx.Config.Sizes {
-					err := os.Remove(path.Join(ctx.Config.TargetFolder, fmt.Sprintf("%s%s", info.Id, definition.Suffix)))
-					if err != nil && !os.IsNotExist(err) {
-						fmt.Printf("An error occured: %s", err.Error())
-						_ = returnError(w, http.StatusInternalServerError, "Internal Server Error")
-						return
-					}
-				}
-				http.Redirect(w, r, "/me/images", http.StatusFound)
-				return
-			}
-
-			if err = formatTemplate(w, "image_detail.html", ImageDetailData{
-				user,
-				info,
-			}); err != nil {
-				panic(err)
-			}
-			return
-		}
-
-		_ = returnError(w, http.StatusNotFound, "Image Not Found")
-	})
-}
diff --git a/repo/album_repository.go b/repo/album_repository.go
index 4ced2fb8941a20f3d650283260800d14780fa5e7..f55f3afde76593a8f5153fc4c10d067ab8feaaff 100644
--- a/repo/album_repository.go
+++ b/repo/album_repository.go
@@ -60,7 +60,8 @@ func (repo AlbumRepository) Get(albumId string) (model.Album, error) {
 				owner,
 				coalesce(title,  ''),
 				coalesce(description, ''),
-        		coalesce(created_at, to_timestamp(0))
+        		coalesce(created_at, to_timestamp(0)),
+        		coalesce(updated_at, to_timestamp(0))
 			FROM albums
 			WHERE id = $1`,
 			albumId)
@@ -71,7 +72,7 @@ func (repo AlbumRepository) Get(albumId string) (model.Album, error) {
 	if result.Next() {
 		if err := result.Scan(
 			&album.Id, &album.Owner, &album.Title, &album.Description,
-			&album.CreatedAt,
+			&album.CreatedAt, &album.UpdatedAt,
 		); err != nil {
 			return album, err
 		}
@@ -121,78 +122,3 @@ func (repo AlbumRepository) Delete(album model.Album) error {
 
 	return nil
 }
-
-func (repo AlbumRepository) AddImage(album model.Album, image model.AlbumImage) error {
-	if _, err := repo.db.Exec(`
-		INSERT INTO album_images (album, image, title, description, position) 
-		VALUES ($1, $2, $3, $4)`,
-		album.Id,
-		image.Id,
-		image.Title,
-		image.Description,
-		image.Position,
-	); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (repo AlbumRepository) GetImages(album model.Album) ([]model.AlbumImage, error) {
-	var albumImages []model.AlbumImage
-
-	result, err := repo.db.Query(`
-			SELECT
-				image,
-				coalesce(title,  ''),
-				coalesce(description, ''),
-			    position
-			FROM album_images`)
-	if err != nil {
-		return albumImages, err
-	}
-
-	for result.Next() {
-		var albumImage model.AlbumImage
-
-		if err := result.Scan(
-			&albumImage.Id, &albumImage.Title, &albumImage.Description,
-			&albumImage.Position,
-		); err != nil {
-			return albumImages, err
-		}
-		albumImages = append(albumImages, albumImage)
-	}
-
-	return albumImages, nil
-}
-
-func (repo AlbumRepository) RemoveImage(album model.Album, imageId string) error {
-	if _, err := repo.db.Exec(
-		"DELETE FROM album_images WHERE album = $1 AND image = $2",
-		album.Id,
-		imageId,
-	); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (repo AlbumRepository) ReorderImages(album model.Album, images []model.AlbumImage) error {
-	if _, err := repo.db.Exec(
-		"DELETE FROM album_images WHERE album = $1",
-		album.Id,
-	); err != nil {
-		return err
-	}
-
-	for _, image := range images {
-		if err := repo.AddImage(album, image); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
diff --git a/repo/albumimage_repository.go b/repo/albumimage_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..60ccfb44e750765e4b8f52970c201f2a7810e3e4
--- /dev/null
+++ b/repo/albumimage_repository.go
@@ -0,0 +1,199 @@
+package repo
+
+import (
+	"database/sql"
+	"git.kuschku.de/justjanne/imghost-frontend/model"
+	"time"
+)
+
+type AlbumImageRepository struct {
+	db *sql.DB
+}
+
+func NewAlbumImageRepository(db *sql.DB) AlbumImageRepository {
+	return AlbumImageRepository{
+		db: db,
+	}
+}
+
+func (repo AlbumImageRepository) List(album model.Album) ([]model.AlbumImage, error) {
+	var albums []model.AlbumImage
+
+	result, err := repo.db.Query(`
+			SELECT
+				image,
+				coalesce(title,  ''),
+				coalesce(description, '')
+			FROM album_images
+			WHERE album = $1
+			ORDER BY position
+			`, album.Id)
+	if err != nil {
+		return albums, err
+	}
+
+	for result.Next() {
+		var album model.AlbumImage
+
+		if err := result.Scan(
+			&album.Id, &album.Title, &album.Description,
+		); err != nil {
+			return albums, err
+		}
+		albums = append(albums, album)
+	}
+
+	return albums, nil
+}
+
+func (repo AlbumImageRepository) Get(album model.Album, imageId string) (model.AlbumImage, error) {
+	var albumImage model.AlbumImage
+
+	result, err := repo.db.Query(`
+			SELECT
+				image,
+				coalesce(title,  ''),
+				coalesce(description, '')
+			FROM album_images
+			WHERE album = $1
+			AND image = $2
+			ORDER BY position`,
+		album.Id,
+		imageId)
+	if err != nil {
+		return albumImage, err
+	}
+
+	if result.Next() {
+		if err := result.Scan(
+			&albumImage.Id, &albumImage.Title, &albumImage.Description,
+		); err != nil {
+			return albumImage, err
+		}
+	}
+
+	return albumImage, nil
+}
+
+func (repo AlbumImageRepository) Append(album model.Album, image model.AlbumImage) error {
+	if _, err := repo.db.Exec(`
+		INSERT INTO album_images (album, image, title, description, position) 
+		VALUES ($1, $2, $3, $4, (
+		    SELECT COUNT(image)
+		    FROM album_images
+		    WHERE album = $1
+		))`,
+		album.Id,
+		image.Id,
+		image.Title,
+		image.Description,
+	); err != nil {
+		return err
+	}
+	if _, err := repo.db.Exec(`
+		UPDATE albums SET updated_at = $2 WHERE id = $1`,
+		album.Id,
+		time.Now().UTC(),
+	); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (repo AlbumImageRepository) Update(album model.Album, image model.AlbumImage) error {
+	if _, err := repo.db.Exec(`
+		UPDATE album_images 
+		SET title = $3, description = $4 
+		WHERE album = $1 
+	    AND image = $2`,
+		album.Id,
+		image.Id,
+		image.Title,
+		image.Description,
+	); err != nil {
+		return err
+	}
+	if _, err := repo.db.Exec(`
+		UPDATE albums SET updated_at = $2 WHERE id = $1`,
+		album.Id,
+		time.Now().UTC(),
+	); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (repo AlbumImageRepository) DeleteAll(album model.Album) error {
+	if _, err := repo.db.Exec(
+		"DELETE FROM album_images WHERE album = $1",
+		album.Id,
+	); err != nil {
+		return err
+	}
+	if _, err := repo.db.Exec(`
+		UPDATE albums SET updated_at = $2 WHERE id = $1`,
+		album.Id,
+		time.Now().UTC(),
+	); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (repo AlbumImageRepository) Delete(album model.Album, image model.AlbumImage) error {
+	if _, err := repo.db.Exec(
+		"DELETE FROM album_images WHERE album = $1 AND image = $2",
+		album.Id,
+		image.Id,
+	); err != nil {
+		return err
+	}
+	if _, err := repo.db.Exec(`
+		UPDATE albums SET updated_at = $2 WHERE id = $1`,
+		album.Id,
+		time.Now().UTC(),
+	); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (repo AlbumImageRepository) Reorder(album model.Album, images []model.AlbumImage) error {
+	tx, err := repo.db.Begin()
+	if err != nil {
+		return err
+	}
+
+	for index, image := range images {
+		if _, err := tx.Exec(`
+		UPDATE album_images 
+		SET position = $3 
+		WHERE album = $1 
+	    AND image = $2`,
+			album.Id,
+			image.Id,
+			index,
+		); err != nil {
+			return err
+		}
+	}
+
+	if _, err := tx.Exec(`
+		UPDATE albums SET updated_at = $2 WHERE id = $1`,
+		album.Id,
+		time.Now().UTC(),
+	); err != nil {
+		return err
+	}
+
+	err = tx.Commit()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/templates/album_detail.gohtml b/templates/album_detail.gohtml
new file mode 100644
index 0000000000000000000000000000000000000000..6cce2d636ac68563fa361503c0da86d0aae5650e
--- /dev/null
+++ b/templates/album_detail.gohtml
@@ -0,0 +1,101 @@
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.AlbumViewModel*/ -}}
+<form>
+  <h1>User</h1>
+  <p>
+    <label>
+      Id:<br>
+      <input type="text" disabled value="{{.User.Id}}">
+    </label>
+  </p>
+  <p>
+    <label>
+      Name:<br>
+      <input type="text" disabled value="{{.User.Name}}">
+    </label>
+  </p>
+  <p>
+    <label>
+      Email:<br>
+      <input type="text" disabled value="{{.User.Email}}">
+    </label>
+  </p>
+  <p><b>Roles: </b></p>
+  <ul>
+      {{range .User.Roles}}
+        <li>{{.}}</li>
+      {{end}}
+  </ul>
+</form>
+<form method="post">
+  <h1>Album</h1>
+  <p>
+    <label>
+      Id:<br>
+      <input type="text" disabled value="{{.Album.Id}}">
+    </label>
+  </p>
+  <p>
+    <label>
+      Owner:<br>
+      <input type="text" disabled value="{{.Album.Owner}}">
+    </label>
+  </p>
+  <p>
+    <label>
+      Title:<br>
+      <input type="text" name="title" value="{{.Album.Title}}">
+    </label>
+  </p>
+  <p>
+    <label>
+      Description:<br>
+      <input type="text" name="description" value="{{.Album.Description}}">
+    </label>
+  </p>
+  <p>
+    <label>
+      CreatedAt:<br>
+      <input type="text" disabled value="{{.Album.CreatedAt}}">
+    </label>
+  </p>
+  <p>
+    <label>
+      UpdatedAt:<br>
+      <input type="text" disabled value="{{.Album.UpdatedAt}}">
+    </label>
+  </p>
+  <p>
+    <input type="submit">
+  </p>
+</form>
+<ul>
+    {{range .Images}}
+      <li>
+        <form method="post" action="/a/{{$.Album.Id}}/{{.Id}}">
+          <h1>Image</h1>
+          <p>
+            <label>
+              Id:<br>
+              <input type="text" disabled value="{{.Id}}">
+            </label>
+          </p>
+          <p>
+            <label>
+              Title:<br>
+              <input type="text" name="title" value="{{.Title}}">
+            </label>
+          </p>
+          <p>
+            <label>
+              Description:<br>
+              <input type="text" name="description" value="{{.Description}}">
+            </label>
+          </p>
+          <p>
+            <input type="submit">
+          </p>
+        </form>
+        <img src="https://i.k8r.eu/{{.Image.Id}}l"/>
+      </li>
+    {{end}}
+</ul>
diff --git a/templates/image_detail.html b/templates/image_detail.gohtml
similarity index 89%
rename from templates/image_detail.html
rename to templates/image_detail.gohtml
index c8e455a82a353b6694e3f42d4b8a78514bbe9a55..1da70f2db51769782a6a037ee2f63efba04c42d1 100644
--- a/templates/image_detail.html
+++ b/templates/image_detail.gohtml
@@ -1,4 +1,4 @@
-{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageDetailData*/ -}}
+{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageViewModel*/ -}}
 <form>
   <h1>User</h1>
   <p>
@@ -27,6 +27,7 @@
   </ul>
 </form>
 <form method="post">
+  <input type="hidden" name="_method" value="post">
   <h1>Image</h1>
   <p>
     <label>
@@ -80,3 +81,4 @@
     <input type="submit">
   </p>
 </form>
+<img src="https://i.k8r.eu/{{.Image.Id}}l"/>