diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7911ceb..ac9b535 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,10 +3,13 @@
All notable change notes in this project will be documented in this file.
## [0.1.0-2] - 2023-04-09
+
### Changed
+
- `docker-compose.yml`: Changed the volume path for the `init.sql` file:
- From: `./schema.sql:/docker-entrypoint-initdb.d/schema.sql`
- To: `./init.sql:/docker-entrypoint-initdb.d/init.sql`
### Removed
+
- `docker/schema.sql`: The file has been removed. The schema configuration has been moved to the `init.sql` file.
diff --git a/Makefile b/Makefile
index 5ca4b65..6928e17 100644
--- a/Makefile
+++ b/Makefile
@@ -81,8 +81,6 @@ pg-docs:
# Generate or recreate SQLC queries.
sqlc:
cd $(CORE_DIR) && sqlc generate
- make gomock
-
.PHONY: test
# Test all files and generate coverage file.
@@ -120,9 +118,10 @@ tidy:
.PHONY: build-linux
# Build and generate linux executable.
build-linux:
- cd $(CORE_DIR) && make tidy
- cd $(CORE_DIR) && make remove-debug
- cd $(CORE_DIR) && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./tmp/arena ./cmd/.
+ cd $(CORE_DIR) && go mod tidy
+ cd $(CORE_DIR) && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./tmp/$(MOD_NAME) ./cmd/.
+
+
.PHONY: pack-docker
# Run docker build for pack binary and assets to Docker container.
diff --git a/README.md b/README.md
index a313147..45de3ee 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,20 @@
# Rating Orama
-Rating Orama is a web application for displaying TV show ratings and statistics.
+Rating Orama is a web application for displaying TV show ratings and statistics.
It is composed of 2 main parts:
-1. **Core**: Written in Go and Fiber, responsible for orchestrating everything
-and displaying the data using a template engine.
-3. **Database**: PostgreSQL for storing data.
+1. **Core**: Written in Go and Fiber, responsible for orchestrating everything
+ and displaying the data using a template engine.
+2. **Database**: PostgreSQL for storing data.
## Running the project
-There are two ways to run the project: launching each part individually or
-building the Dockerfile and running it using Docker Compose. Here's an example
+There are two ways to run the project: launching each part individually or
+building the Dockerfile and running it using Docker Compose. Here's an example
of the `docker-compose.yml` file for the latter option:
```yaml
-version: '3'
+version: "3"
services:
core:
@@ -49,5 +49,5 @@ volumes:
## Contributions
-If you have ideas for improvements or bug fixes, feel free to contribute! To do
+If you have ideas for improvements or bug fixes, feel free to contribute! To do
so, simply clone the repository, create a new branch, and submit a pull request.
diff --git a/core/Dockerfile b/core/Dockerfile
index aef3dbe..a2a5639 100644
--- a/core/Dockerfile
+++ b/core/Dockerfile
@@ -2,7 +2,6 @@ FROM alpine:3.20
WORKDIR /app
-COPY ./database ./database
-COPY ./tmp/rating .
+COPY ../tmp/rating .
CMD ["/app/rating"]
\ No newline at end of file
diff --git a/core/cmd/main.go b/core/cmd/main.go
index 3d50673..49f1a72 100644
--- a/core/cmd/main.go
+++ b/core/cmd/main.go
@@ -13,7 +13,7 @@ import (
"github.com/zepyrshut/rating-orama/internal/repository"
)
-const version = "0.2.0-beta.20241116-4"
+const version = "0.2.0-beta.20250128-1"
const appName = "rating-orama"
func init() {
@@ -25,6 +25,7 @@ var database embed.FS
func main() {
engine := html.New("./views", ".html")
+ engine.Reload(true)
app := app.NewExtendedApp(appName, version, ".env")
app.Migrate(database)
diff --git a/core/cmd/routes.go b/core/cmd/routes.go
index 6ab3f7b..7da6d0d 100644
--- a/core/cmd/routes.go
+++ b/core/cmd/routes.go
@@ -7,6 +7,7 @@ import (
func router(h *handlers.Handlers, r *fiber.App) {
+ r.Get("/", h.GetIndex)
r.Get("/tvshow", h.GetTVShow)
}
diff --git a/core/internal/handlers/tvshow.go b/core/internal/handlers/tvshow.go
index 22f74f0..1efd13d 100644
--- a/core/internal/handlers/tvshow.go
+++ b/core/internal/handlers/tvshow.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "encoding/json"
"log/slog"
"net/http"
@@ -10,6 +11,12 @@ import (
"github.com/zepyrshut/rating-orama/internal/sqlc"
)
+func (hq *Handlers) GetIndex(c *fiber.Ctx) error {
+ return c.Render("index", fiber.Map{
+ "Title": "Rating Orama",
+ }, "layouts/main")
+}
+
func (hq *Handlers) GetTVShow(c *fiber.Ctx) error {
ttShowID := c.Query("ttid")
@@ -20,8 +27,9 @@ func (hq *Handlers) GetTVShow(c *fiber.Ctx) error {
var title string
var scraperEpisodes []scraper.Episode
var sqlcEpisodes []sqlc.Episode
+ var totalVoteCount int32
- tvShow, err := hq.queries.CheckTVShowExists(c.Context(), ttShowID)
+ sqlcTvShow, err := hq.queries.CheckTVShowExists(c.Context(), ttShowID)
if err != nil {
title, scraperEpisodes = scraper.ScrapeEpisodes(ttShowID)
//TODO: make transactional
@@ -42,26 +50,61 @@ func (hq *Handlers) GetTVShow(c *fiber.Ctx) error {
slog.Error("failed to create episodes", "ttid", ttShowID, "error", err)
return c.SendStatus(http.StatusInternalServerError)
}
-
sqlcEpisodes = append(sqlcEpisodes, sqlcEpisode)
}
slog.Info("scraped seasons", "ttid", ttShowID, "title", title)
} else {
- title = tvShow.Name
- sqlcEpisodes, err = hq.queries.GetEpisodes(c.Context(), tvShow.ID)
+ title = sqlcTvShow.Name
+ sqlcEpisodes, err = hq.queries.GetEpisodes(c.Context(), sqlcTvShow.ID)
if err != nil {
slog.Error("failed to get episodes", "ttid", ttShowID, "error", err)
return c.SendStatus(http.StatusInternalServerError)
}
+ for _, episode := range sqlcEpisodes {
+ totalVoteCount += episode.VoteCount
+ }
+
hq.queries.IncreasePopularity(c.Context(), ttShowID)
- slog.Info("tv show exists", "ttid", ttShowID, "title", tvShow.Name)
+ slog.Info("tv show exists", "ttid", ttShowID, "title", sqlcTvShow.Name)
}
- return c.JSON(fiber.Map{
- "popularity": tvShow.Popularity,
- "title": title,
- "seasons": sqlcEpisodes,
+ episodesJSON, err := json.Marshal(sqlcEpisodes)
+ if err != nil {
+ slog.Error("failed to marshal episodes", "ttid", ttShowID, "error", err)
+ return c.SendStatus(http.StatusInternalServerError)
+ }
+
+ // calculate avg rating for the show
+ avgRatingShow, err := hq.queries.TvShowAverageRating(c.Context(), sqlcTvShow.ID)
+ if err != nil {
+ slog.Error("failed to calculate avg rating for the show", "ttid", ttShowID, "error", err)
+ return c.SendStatus(http.StatusInternalServerError)
+ }
+
+ medianRatingShow, err := hq.queries.TvShowMedianRating(c.Context(), sqlcTvShow.ID)
+ if err != nil {
+ slog.Error("failed to calculate median rating for the show", "ttid", ttShowID, "error", err)
+ return c.SendStatus(http.StatusInternalServerError)
+ }
+
+ seasongAvgRatings, err := hq.queries.SeasonAverageRating(c.Context(), sqlc.SeasonAverageRatingParams{
+ TvShowID: sqlcTvShow.ID,
})
+
+ seasonMedianRatings, err := hq.queries.SeasonMedianRating(c.Context(), sqlc.SeasonMedianRatingParams{
+ TvShowID: sqlcTvShow.ID,
+ })
+
+ return c.Render("tvshow", fiber.Map{
+ "Title": sqlcTvShow.Name,
+ "tvshow": sqlcTvShow,
+ "episodes": string(episodesJSON),
+ "avg_rating_show": avgRatingShow,
+ "median_rating_show": medianRatingShow,
+ "season_avg_ratings": seasongAvgRatings,
+ "season_median_ratings": seasonMedianRatings,
+ "total_vote_count": totalVoteCount,
+ }, "layouts/main")
}
diff --git a/core/sqlc.yaml b/core/sqlc.yaml
index 4843973..5ca3d05 100644
--- a/core/sqlc.yaml
+++ b/core/sqlc.yaml
@@ -9,8 +9,7 @@ sql:
out: "./internal/sqlc"
sql_package: "pgx/v5"
emit_interface: true
- emit_empty_slices: true
- emit_json_tags: true
+ emit_empty_slices: true
+ emit_json_tags: true
rename:
uuid: "UUID"
-
\ No newline at end of file
diff --git a/core/tmp/rating-orama b/core/tmp/rating-orama
new file mode 100644
index 0000000..4347be6
Binary files /dev/null and b/core/tmp/rating-orama differ
diff --git a/core/views/charts.html b/core/views/charts.html
deleted file mode 100644
index 55f9c13..0000000
--- a/core/views/charts.html
+++ /dev/null
@@ -1,101 +0,0 @@
-{{template "partials/header" .}}
-
-
-
{{ .TvShow.Title }}
-
-
-
- IMDb ID:
- {{ .TvShow.ShowID }}
-
-
-
-
- -
- Title:
- {{ .TvShow.Title }}
-
- -
- Runtime:
- {{ .TvShow.Runtime }} min
-
- -
- Seasons:
- {{len .TvShow.Seasons }}
-
-
-
-
- -
- Total votes:
- {{ .TvShow.Votes }}
-
- -
- Average rating:
- {{printf "%.2f" .TvShow.AvgRating }}
-
- -
- Median rating:
- {{printf "%.2f" .TvShow.MedianRating }}
-
-
-
-
-
-
-
Seasons overall
-
-
-
-
-
-
- Season
-
-
-
- {{ range .TvShow.Seasons }}
-
- {{ end }}
-
-
-
-
-
-{{template "partials/footer" .}}
-
-
diff --git a/core/views/css/index.css b/core/views/css/index.css
deleted file mode 100644
index ab625f4..0000000
--- a/core/views/css/index.css
+++ /dev/null
@@ -1 +0,0 @@
-*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mt-12{margin-top:3rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.flex{display:flex}.grid{display:grid}.h-6{height:1.5rem}.min-h-screen{min-height:100vh}.w-11\/12{width:91.666667%}.w-2\/12{width:16.666667%}.w-2\/3{width:66.666667%}.w-6{width:1.5rem}.w-full{width:100%}.max-w-md{max-width:28rem}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-3{gap:.75rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border-2{border-width:2px}.border-black{--tw-border-opacity: 1;border-color:rgb(0 0 0 / var(--tw-border-opacity))}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity))}.fill-black{fill:#000}.p-12{padding:3rem}.p-6{padding:1.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hover\:bg-black:hover{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.hover\:text-gray-700\/75:hover{color:#374151bf}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}
diff --git a/core/views/index.html b/core/views/index.html
index a27ea2d..c196419 100644
--- a/core/views/index.html
+++ b/core/views/index.html
@@ -1,55 +1,22 @@
-{{template "partials/header" .}}
-