3 Commits c2931b4cd2 ... 4b01054a11

Autor SHA1 Mensaje Fecha
  Gildas Chabot 4b01054a11 Introduce List page with add, view, update hace 4 años
  Gildas Chabot 497774c0cc Rename list page to home page hace 4 años
  Gildas Chabot 0fdafaab8a Show metadata on the list page hace 4 años
Se han modificado 7 ficheros con 273 adiciones y 52 borrados
  1. 67 4
      collection.go
  2. 19 0
      list.go
  3. 12 0
      movies.go
  4. 71 10
      pages/pages.go
  5. 76 0
      templates/home.html
  6. 7 31
      templates/list.html
  7. 21 7
      templates/style.css

+ 67 - 4
collection.go

@@ -2,16 +2,20 @@ package movies
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"fmt"
5 6
 	"io/ioutil"
6 7
 	"sync"
7 8
 )
8 9
 
9 10
 type Collection struct {
10
-	Movies []*Movie
11
+	Movies   []*Movie
12
+	movieMap map[string]*Movie
13
+
14
+	Lists   []*List
15
+	listMap map[string]*List
11 16
 
12 17
 	mutex      sync.RWMutex
13 18
 	hasChanged bool
14
-	movieMap   map[string]*Movie
15 19
 }
16 20
 
17 21
 func Import(filename string) (*Collection, error) {
@@ -31,6 +35,11 @@ func Import(filename string) (*Collection, error) {
31 35
 		c.movieMap[m.IMDBID] = m
32 36
 	}
33 37
 
38
+	c.listMap = make(map[string]*List)
39
+	for _, l := range c.Lists {
40
+		c.listMap[l.ID] = l
41
+	}
42
+
34 43
 	return &c, nil
35 44
 }
36 45
 
@@ -56,16 +65,18 @@ func (c *Collection) Add(m *Movie) {
56 65
 	c.mutex.Lock()
57 66
 	defer c.mutex.Unlock()
58 67
 
68
+	c.hasChanged = true
69
+
59 70
 	for _, present := range c.Movies {
60 71
 		if m.IMDBID == present.IMDBID {
72
+			present.OMDB = m.OMDB
73
+			present.FillFromOMDB()
61 74
 			return
62 75
 		}
63 76
 	}
64 77
 
65 78
 	c.Movies = append(c.Movies, m)
66 79
 	c.movieMap[m.IMDBID] = m
67
-
68
-	c.hasChanged = true
69 80
 }
70 81
 
71 82
 func (c *Collection) HasChanged() bool {
@@ -100,3 +111,55 @@ func (c *Collection) Update(m *Movie) {
100 111
 
101 112
 	c.hasChanged = true
102 113
 }
114
+
115
+func (c *Collection) GetList(listID string) (*List, bool) {
116
+	c.mutex.RLock()
117
+	defer c.mutex.RUnlock()
118
+
119
+	l, ok := c.listMap[listID]
120
+	return l, ok
121
+}
122
+
123
+func (c *Collection) AddList(id, title string) error {
124
+	c.mutex.Lock()
125
+	defer c.mutex.Unlock()
126
+
127
+	c.hasChanged = true
128
+
129
+	for _, present := range c.Lists {
130
+		if id == present.ID {
131
+			return fmt.Errorf("there is already a list with id %q", id)
132
+		}
133
+	}
134
+
135
+	l := &List{
136
+		ID:    id,
137
+		Title: title,
138
+	}
139
+
140
+	c.Lists = append(c.Lists, l)
141
+	c.listMap[l.ID] = l
142
+
143
+	return nil
144
+}
145
+
146
+func (c *Collection) UpdateList(l *List) {
147
+	c.mutex.Lock()
148
+	defer c.mutex.Unlock()
149
+
150
+	c.listMap[l.ID] = l
151
+
152
+	found := false
153
+	for i := range c.Lists {
154
+		if c.Lists[i].ID == l.ID {
155
+			c.Lists[i] = l
156
+			found = true
157
+			break
158
+		}
159
+	}
160
+	if !found {
161
+		c.Lists = append(c.Lists, l)
162
+	}
163
+
164
+	c.hasChanged = true
165
+}

+ 19 - 0
list.go

@@ -0,0 +1,19 @@
1
+package movies
2
+
3
+import "encoding/json"
4
+
5
+type List struct {
6
+	ID          string
7
+	Title       string
8
+	Description string
9
+	Movies      []string // IMDBIDs
10
+}
11
+
12
+func UnmarshalList(b []byte) (*List, error) {
13
+	var l List
14
+	if err := json.Unmarshal(b, &l); err != nil {
15
+		return nil, err
16
+	}
17
+
18
+	return &l, nil
19
+}

+ 12 - 0
movies.go

@@ -12,6 +12,9 @@ type Movie struct {
12 12
 	Poster   string
13 13
 	Rating   *Rating
14 14
 	IMDBID   string
15
+	Country  string
16
+	Year     string
17
+	Runtime  string
15 18
 
16 19
 	OMDB OMDBMovie
17 20
 }
@@ -83,6 +86,15 @@ func (m *Movie) FillFromOMDB() error {
83 86
 	if m.IMDBID == "" {
84 87
 		m.IMDBID = m.OMDB.IMDBID
85 88
 	}
89
+	if m.Country == "" {
90
+		m.Country = m.OMDB.Country
91
+	}
92
+	if m.Year == "" {
93
+		m.Year = m.OMDB.Year
94
+	}
95
+	if m.Runtime == "" {
96
+		m.Runtime = m.OMDB.Runtime
97
+	}
86 98
 
87 99
 	if m.Rating == nil {
88 100
 		m.Rating = &Rating{}

+ 71 - 10
pages/pages.go

@@ -12,18 +12,22 @@ import (
12 12
 )
13 13
 
14 14
 func Router(c *movies.Collection) http.HandlerFunc {
15
-	listHandler := List(c)
15
+	homeHandler := Home(c)
16 16
 	movieHandler := Movie(c)
17
+	listHandler := List(c)
17 18
 
18 19
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
19 20
 		path := r.URL.Path
20 21
 		switch {
21 22
 		case path == "/":
22
-			listHandler(w, r)
23
+			homeHandler(w, r)
23 24
 			return
24 25
 		case path == "/style.css":
25 26
 			http.ServeFile(w, r, "templates/style.css")
26 27
 			return
28
+		case strings.HasPrefix(path, "/l/"):
29
+			listHandler(w, r)
30
+			return
27 31
 		case strings.HasPrefix(path, "/tt"):
28 32
 			movieHandler(w, r)
29 33
 			return
@@ -31,9 +35,9 @@ func Router(c *movies.Collection) http.HandlerFunc {
31 35
 	})
32 36
 }
33 37
 
34
-func List(c *movies.Collection) http.HandlerFunc {
38
+func Home(c *movies.Collection) http.HandlerFunc {
35 39
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36
-		t, err := template.ParseFiles("templates/list.html")
40
+		t, err := template.ParseFiles("templates/home.html")
37 41
 		if err != nil {
38 42
 			fmt.Println(err)
39 43
 			return
@@ -42,12 +46,20 @@ func List(c *movies.Collection) http.HandlerFunc {
42 46
 		var errs []error
43 47
 
44 48
 		if r.Method == "POST" {
45
-			m, err := movies.Unmarshal([]byte(r.FormValue("omdb_json")))
46
-			if err != nil {
47
-				w.WriteHeader(http.StatusBadRequest)
48
-				errs = append(errs, err)
49
-			} else {
50
-				c.Add(m)
49
+			if r.FormValue("omdb_json") != "" {
50
+				m, err := movies.Unmarshal([]byte(r.FormValue("omdb_json")))
51
+				if err != nil {
52
+					w.WriteHeader(http.StatusBadRequest)
53
+					errs = append(errs, err)
54
+				} else {
55
+					c.Add(m)
56
+				}
57
+			}
58
+
59
+			if r.FormValue("list-id") != "" {
60
+				if err := c.AddList(r.FormValue("list-id"), r.FormValue("list-title")); err != nil {
61
+					errs = append(errs, err)
62
+				}
51 63
 			}
52 64
 		}
53 65
 
@@ -118,3 +130,52 @@ func Movie(c *movies.Collection) http.HandlerFunc {
118 130
 		})
119 131
 	})
120 132
 }
133
+
134
+func List(c *movies.Collection) http.HandlerFunc {
135
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
136
+		t, err := template.ParseFiles("templates/list.html")
137
+		if err != nil {
138
+			fmt.Println(err)
139
+			return
140
+		}
141
+
142
+		var errs []error
143
+
144
+		splitted := strings.Split(r.URL.Path, "/")
145
+		if len(splitted) < 3 {
146
+			w.WriteHeader(http.StatusNotFound)
147
+			return
148
+		}
149
+
150
+		listID := splitted[2]
151
+
152
+		l, ok := c.GetList(listID)
153
+		if !ok {
154
+			w.WriteHeader(http.StatusNotFound)
155
+			return
156
+		}
157
+
158
+		if r.Method == "POST" {
159
+			updated, err := movies.UnmarshalList([]byte(r.FormValue("list_json")))
160
+			if err != nil {
161
+				errs = append(errs, err)
162
+			} else if listID != updated.ID {
163
+				errs = append(errs, fmt.Errorf("you cannot change the imdb id."))
164
+			} else {
165
+				l = updated
166
+				c.UpdateList(updated)
167
+			}
168
+		}
169
+
170
+		b, err := json.MarshalIndent(l, "", "  ")
171
+		if err != nil {
172
+			errs = append(errs, err)
173
+		}
174
+
175
+		t.Execute(w, map[string]interface{}{
176
+			"List":     l,
177
+			"ListJSON": string(b),
178
+			"Errors":   errs,
179
+		})
180
+	})
181
+}

+ 76 - 0
templates/home.html

@@ -0,0 +1,76 @@
1
+<html>
2
+    <head>
3
+        <title>gildas.ch</title>
4
+
5
+        <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
6
+        <link rel="stylesheet" href="/style.css">
7
+    </head>
8
+    <body>
9
+        {{ range $e := .Errors }}
10
+        <div class="error">{{ $e }}</div>
11
+        {{ end }}
12
+
13
+        <h2>Movies</h2>
14
+
15
+        <ul class="movie-list">
16
+            {{ range $m := .Collection.Movies }}
17
+            <a href="/{{ $m.IMDBID }}">
18
+                <li class="movie">
19
+                    <div class="poster">
20
+                        <img src="{{ $m.Poster }}" title="{{ $m.Title }}" alt="{{ $m.Title }}" />
21
+                    </div>
22
+                    <div class="title">
23
+                        <h3>{{ $m.Title }}</h3>
24
+                        <div>{{ $m.Director }}</div>
25
+                    </div>
26
+                    <ul class="metadata">
27
+                        <li>{{ $m.Year }}</li>
28
+                        <li style="flex-grow: 2;">{{ $m.Country }}</li>
29
+                        <li>{{ $m.Runtime }}</li>
30
+                    </ul>
31
+                </li>
32
+            </a>
33
+            {{ end }}
34
+        </ul>
35
+
36
+        <h2>Lists</h2>
37
+
38
+        <ul class="list-list">
39
+            {{ range $l := .Collection.Lists }}
40
+            <a href="/l/{{ $l.ID }}">
41
+                <li class="list">
42
+                    <h3>{{ $l.Title }} ({{ $l.ID }})</h3>
43
+                    <p>{{ $l.Description }}</p>
44
+                </li>
45
+            </a>
46
+            {{ end }}
47
+        </ul>
48
+
49
+        <h2>Add a movie</h2>
50
+        <div>
51
+            <form method="get">
52
+                <p>
53
+                    Search by IMDB id: <input type="text" name="imdb_id" value="{{ .IMDBID }}" />
54
+                    <input type="submit" />
55
+                </p>
56
+            </form>
57
+            <form method="post" action="/">
58
+                <p>Add OMBD JSON movie:<br />
59
+                    <textarea name="omdb_json" style="width:500px;height:140px;">{{ .OMDBString }}</textarea>
60
+                    <input type="submit" />
61
+                </p>
62
+            </form>
63
+        </div>
64
+
65
+        <h2>Add a list</h2>
66
+        <div>
67
+            <form method="post" action="/">
68
+                <div>
69
+                    ID: <input type="text" name="list-id" />
70
+                    Title: <input type="text" name="list-title" />
71
+                    <input type="submit" />
72
+                </div>
73
+            </form>
74
+        </div>
75
+    </body>
76
+</html>

+ 7 - 31
templates/list.html

@@ -10,38 +10,14 @@
10 10
         <div class="error">{{ $e }}</div>
11 11
         {{ end }}
12 12
 
13
-        <h2>Movies</h2>
13
+        <h1>{{ .List.Title }}</h1>
14 14
 
15
-        <ul class="movie-list">
16
-            {{ range $m := .Collection.Movies }}
17
-            <a href="/{{ $m.IMDBID }}">
18
-                <li>
19
-                    <div class="poster">
20
-                        <img src="{{ $m.Poster }}" title="{{ $m.Title }}" alt="{{ $m.Title }}" />
21
-                    </div>
22
-                    <div class="metadata">
23
-                        <h3>{{ $m.Title }}</h3>
24
-                        <div>{{ $m.Director }}</div>
25
-                    </div>
26
-                </li>
27
-            </a>
28
-            {{ end }}
29
-        </ul>
15
+        <p>{{ .List.Description }}</p>
30 16
 
31
-        <h2>Add a movie</h2>
32
-        <div>
33
-            <form method="get">
34
-                <p>
35
-                    Search by IMDB id: <input type="text" name="imdb_id" value="{{ .IMDBID }}" />
36
-                    <input type="submit" />
37
-                </p>
38
-            </form>
39
-            <form method="post" action="/">
40
-                <p>Add OMBD JSON movie:<br />
41
-                    <textarea name="omdb_json" style="width:500px;height:140px;">{{ .OMDBString }}</textarea>
42
-                    <input type="submit" />
43
-                </p>
44
-            </form>
45
-        </div>
17
+        <form method="post">
18
+            <p>Update list: <input type="submit" /><br />
19
+                <textarea name="list_json" style="width:90%;height:500px;">{{ .ListJSON }}</textarea>
20
+            </p>
21
+        </form>
46 22
     </body>
47 23
 </html>

+ 21 - 7
templates/style.css

@@ -16,26 +16,40 @@ ul.movie-list {
16 16
     flex-wrap: wrap;
17 17
     padding: 0;
18 18
 }
19
-ul.movie-list li {
19
+ul.movie-list li.movie {
20 20
     width: 240px;
21
-    height: 400px;
21
+    height: 440px;
22 22
     display: grid;
23
-    grid-template-rows: 320px 80px;
23
+    grid-template-rows: 320px 80px 40px;
24 24
     box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
25 25
     margin: 10px;
26 26
 }
27
-ul.movie-list li .poster {
27
+ul.movie-list li.movie .poster {
28 28
     justify-self: center;
29 29
     align-self: center;
30 30
 }
31
-ul.movie-list li .metadata {
31
+ul.movie-list li.movie .title {
32 32
     padding: 10px;
33 33
 }
34
-ul.movie-list li img {
34
+ul.movie-list li.movie img {
35 35
     max-width: 240px;
36 36
     max-height: 320px;
37 37
 }
38
-ul.movie-list li a {
38
+ul.movie-list li.movie a {
39 39
     color: black;
40 40
     text-decoration: none;
41 41
 }
42
+ul.movie-list li.movie ul.metadata {
43
+    list-style-type: none;
44
+    padding: 0;
45
+    display: flex;
46
+    flex-wrap: nowrap;
47
+    justify-content: space-between;
48
+    padding-top: 10px;
49
+    padding-bottom: 10px;
50
+}
51
+ul.movie-list li.movie ul.metadata li {
52
+    overflow: hidden;
53
+    padding-left: 10px;
54
+    padding-right: 10px;
55
+}