mirror of
https://github.com/Itsig0/pocketmovie.git
synced 2026-10-23 08:42:54 +00:00
+ baseline to the git
This commit is contained in:
commit
b3193a10f9
29 files changed
+2079
No files matched your search
@@ -0,0 +1,51 @@
|
|||||||
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
args_bin = []
|
||||||
|
bin = "./tmp/main"
|
||||||
|
cmd = "make build"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||||
|
exclude_file = ["internal/database/db.go", "internal/database/models.go"]
|
||||||
|
exclude_regex = ["_test.go", ".*_templ.go", ".sql.go"]
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
full_bin = ""
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html", "templ", "css", "js", "sql"]
|
||||||
|
include_file = []
|
||||||
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
|
poll = false
|
||||||
|
poll_interval = 0
|
||||||
|
post_cmd = []
|
||||||
|
pre_cmd = []
|
||||||
|
rerun = false
|
||||||
|
rerun_delay = 500
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = false
|
||||||
|
|
||||||
|
[color]
|
||||||
|
app = ""
|
||||||
|
build = "yellow"
|
||||||
|
main = "magenta"
|
||||||
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
main_only = false
|
||||||
|
time = false
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = true
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
|
keep_scroll = true
|
||||||
|
|
||||||
|
[proxy]
|
||||||
|
enabled = true
|
||||||
|
proxy_port = 8383
|
||||||
|
app_port = 3000
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
PORT=3000
|
||||||
|
TMDB_API_KEY=YOUR_KEY
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
.env
|
||||||
|
data/
|
||||||
|
tmp/
|
||||||
|
main
|
||||||
|
pico.min.css
|
||||||
|
datastar.js
|
||||||
|
*_templ.go
|
||||||
|
db.go
|
||||||
|
models.go
|
||||||
|
*.sql.go
|
||||||
|
jersey15*
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
run:
|
||||||
|
@templ generate
|
||||||
|
@go run cmd/api/main.go
|
||||||
|
|
||||||
|
build:
|
||||||
|
@templ generate
|
||||||
|
@sqlc generate
|
||||||
|
@go build -o tmp/main cmd/api/main.go
|
||||||
|
|
||||||
|
setup:
|
||||||
|
@curl -o cmd/web/assets/js/datastar.js https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js
|
||||||
|
@curl -o cmd/web/assets/css/fonts/jersey15.woff2 https://fonts.gstatic.com/s/jersey15/v3/_6_9EDzuROGsUuk2TWjiZYAg.woff2
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# PocketMovie
|
||||||
|
|
||||||
|
A small database to keep track of the movies you have seen or want to see.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/internal/server"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initial() {
|
||||||
|
if _, err := os.Stat("./data"); os.IsNotExist(err) {
|
||||||
|
os.Mkdir("./data/", 0755)
|
||||||
|
fmt.Println("Data created")
|
||||||
|
os.Mkdir("./data/img/", 0755)
|
||||||
|
fmt.Println("Img created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
initial()
|
||||||
|
|
||||||
|
app := server.New()
|
||||||
|
|
||||||
|
app.RegisterFiberRoutes()
|
||||||
|
|
||||||
|
port, _ := strconv.Atoi(os.Getenv("PORT"))
|
||||||
|
err := app.Listen(fmt.Sprintf(":%d", port), fiber.ListenConfig{
|
||||||
|
DisableStartupMessage: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("http server error: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
templ Apikeyinput(apierror string) {
|
||||||
|
@Html("Please input your TMDB Api key - PocketMovie") {
|
||||||
|
<div class="flex-center" style="height:100vh">
|
||||||
|
<div style="width: 1000px">
|
||||||
|
<h1>API Read Access Token needed</h1>
|
||||||
|
<p>To use this application you need to enter your TMDB API Read Access Token. If you do not have a Token you can get one by creating a TMDB account.</p>
|
||||||
|
<p>It is needed to search movies, pull metadata and stream providers.</p>
|
||||||
|
<form method="POST">
|
||||||
|
<fieldset
|
||||||
|
if apierror != "" {
|
||||||
|
aria-invalid="true"
|
||||||
|
aria-describedby="invalid-helper"
|
||||||
|
}
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="apikey"
|
||||||
|
placeholder="API Read Access Token"
|
||||||
|
required
|
||||||
|
if apierror != "" {
|
||||||
|
aria-invalid="true"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Submit"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
if apierror != "" {
|
||||||
|
<small id="invalid-helper">{ apierror }</small>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
:root {
|
||||||
|
--ctp-macchiato-rosewater: #f4dbd6;
|
||||||
|
--ctp-macchiato-rosewater-rgb: 244 219 214;
|
||||||
|
--ctp-macchiato-rosewater-hsl: 10.000 57.692% 89.804%;
|
||||||
|
--ctp-macchiato-flamingo: #f0c6c6;
|
||||||
|
--ctp-macchiato-flamingo-rgb: 240 198 198;
|
||||||
|
--ctp-macchiato-flamingo-hsl: 0.000 58.333% 85.882%;
|
||||||
|
--ctp-macchiato-pink: #f5bde6;
|
||||||
|
--ctp-macchiato-pink-rgb: 245 189 230;
|
||||||
|
--ctp-macchiato-pink-hsl: 316.071 73.684% 85.098%;
|
||||||
|
--ctp-macchiato-mauve: #c6a0f6;
|
||||||
|
--ctp-macchiato-mauve-rgb: 198 160 246;
|
||||||
|
--ctp-macchiato-mauve-hsl: 266.512 82.692% 79.608%;
|
||||||
|
--ctp-macchiato-red: #ed8796;
|
||||||
|
--ctp-macchiato-red-rgb: 237 135 150;
|
||||||
|
--ctp-macchiato-red-hsl: 351.176 73.913% 72.941%;
|
||||||
|
--ctp-macchiato-maroon: #ee99a0;
|
||||||
|
--ctp-macchiato-maroon-rgb: 238 153 160;
|
||||||
|
--ctp-macchiato-maroon-hsl: 355.059 71.429% 76.667%;
|
||||||
|
--ctp-macchiato-peach: #f5a97f;
|
||||||
|
--ctp-macchiato-peach-rgb: 245 169 127;
|
||||||
|
--ctp-macchiato-peach-hsl: 21.356 85.507% 72.941%;
|
||||||
|
--ctp-macchiato-yellow: #eed49f;
|
||||||
|
--ctp-macchiato-yellow-rgb: 238 212 159;
|
||||||
|
--ctp-macchiato-yellow-hsl: 40.253 69.912% 77.843%;
|
||||||
|
--ctp-macchiato-green: #a6da95;
|
||||||
|
--ctp-macchiato-green-rgb: 166 218 149;
|
||||||
|
--ctp-macchiato-green-hsl: 105.217 48.252% 71.961%;
|
||||||
|
--ctp-macchiato-teal: #8bd5ca;
|
||||||
|
--ctp-macchiato-teal-rgb: 139 213 202;
|
||||||
|
--ctp-macchiato-teal-hsl: 171.081 46.835% 69.020%;
|
||||||
|
--ctp-macchiato-sky: #91d7e3;
|
||||||
|
--ctp-macchiato-sky-rgb: 145 215 227;
|
||||||
|
--ctp-macchiato-sky-hsl: 188.780 59.420% 72.941%;
|
||||||
|
--ctp-macchiato-sapphire: #7dc4e4;
|
||||||
|
--ctp-macchiato-sapphire-rgb: 125 196 228;
|
||||||
|
--ctp-macchiato-sapphire-hsl: 198.641 65.605% 69.216%;
|
||||||
|
--ctp-macchiato-blue: #8aadf4;
|
||||||
|
--ctp-macchiato-blue-rgb: 138 173 244;
|
||||||
|
--ctp-macchiato-blue-hsl: 220.189 82.813% 74.902%;
|
||||||
|
--ctp-macchiato-lavender: #b7bdf8;
|
||||||
|
--ctp-macchiato-lavender-rgb: 183 189 248;
|
||||||
|
--ctp-macchiato-lavender-hsl: 234.462 82.278% 84.510%;
|
||||||
|
--ctp-macchiato-text: #cad3f5;
|
||||||
|
--ctp-macchiato-text-rgb: 202 211 245;
|
||||||
|
--ctp-macchiato-text-hsl: 227.442 68.254% 87.647%;
|
||||||
|
--ctp-macchiato-subtext1: #b8c0e0;
|
||||||
|
--ctp-macchiato-subtext1-rgb: 184 192 224;
|
||||||
|
--ctp-macchiato-subtext1-hsl: 228.000 39.216% 80.000%;
|
||||||
|
--ctp-macchiato-subtext0: #a5adcb;
|
||||||
|
--ctp-macchiato-subtext0-rgb: 165 173 203;
|
||||||
|
--ctp-macchiato-subtext0-hsl: 227.368 26.761% 72.157%;
|
||||||
|
--ctp-macchiato-overlay2: #939ab7;
|
||||||
|
--ctp-macchiato-overlay2-rgb: 147 154 183;
|
||||||
|
--ctp-macchiato-overlay2-hsl: 228.333 20.000% 64.706%;
|
||||||
|
--ctp-macchiato-overlay1: #8087a2;
|
||||||
|
--ctp-macchiato-overlay1-rgb: 128 135 162;
|
||||||
|
--ctp-macchiato-overlay1-hsl: 227.647 15.455% 56.863%;
|
||||||
|
--ctp-macchiato-overlay0: #6e738d;
|
||||||
|
--ctp-macchiato-overlay0-rgb: 110 115 141;
|
||||||
|
--ctp-macchiato-overlay0-hsl: 230.323 12.351% 49.216%;
|
||||||
|
--ctp-macchiato-surface2: #5b6078;
|
||||||
|
--ctp-macchiato-surface2-rgb: 91 96 120;
|
||||||
|
--ctp-macchiato-surface2-hsl: 229.655 13.744% 41.373%;
|
||||||
|
--ctp-macchiato-surface1: #494d64;
|
||||||
|
--ctp-macchiato-surface1-rgb: 73 77 100;
|
||||||
|
--ctp-macchiato-surface1-hsl: 231.111 15.607% 33.922%;
|
||||||
|
--ctp-macchiato-surface0: #363a4f;
|
||||||
|
--ctp-macchiato-surface0-rgb: 54 58 79;
|
||||||
|
--ctp-macchiato-surface0-hsl: 230.400 18.797% 26.078%;
|
||||||
|
--ctp-macchiato-base: #24273a;
|
||||||
|
--ctp-macchiato-base-rgb: 36 39 58;
|
||||||
|
--ctp-macchiato-base-hsl: 231.818 23.404% 18.431%;
|
||||||
|
--ctp-macchiato-mantle: #1e2030;
|
||||||
|
--ctp-macchiato-mantle-rgb: 30 32 48;
|
||||||
|
--ctp-macchiato-mantle-hsl: 233.333 23.077% 15.294%;
|
||||||
|
--ctp-macchiato-crust: #181926;
|
||||||
|
--ctp-macchiato-crust-rgb: 24 25 38;
|
||||||
|
--ctp-macchiato-crust-hsl: 235.714 22.581% 12.157%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* jersey-15-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Jersey 15';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('./fonts/jersey15.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
@import "fonts.css";
|
||||||
|
@import "catppuccin.css";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--pico-form-element-spacing-vertical: 0.5rem;
|
||||||
|
--pico-form-element-spacing-horizontal: 0.75rem;
|
||||||
|
|
||||||
|
--pico-color: var(--ctp-macchiato-text);
|
||||||
|
--pico-h1-color: var(--ctp-macchiato-text);
|
||||||
|
--pico-primary-inverse: var(--ctp-macchiato-base);
|
||||||
|
|
||||||
|
--pico-primary: var(--ctp-macchiato-blue);
|
||||||
|
--pico-primary-underline: var(--ctp-macchiato-blue);
|
||||||
|
--pico-primary-background: var(--ctp-macchiato-blue);
|
||||||
|
|
||||||
|
--pico-primary-hover: var(--ctp-macchiato-mauve);
|
||||||
|
--pico-primary-hover-background: var(--ctp-macchiato-mauve);
|
||||||
|
|
||||||
|
--pico-background-color: var(--ctp-macchiato-crust);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-red{
|
||||||
|
color: hsl(351deg, 74%, 40%);
|
||||||
|
background-color: var(--ctp-macchiato-red) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.bg-green{
|
||||||
|
color: hsl(105deg, 48%, 40%);
|
||||||
|
background-color: var(--ctp-macchiato-green) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-link{
|
||||||
|
color: var(--ctp-macchiato-red);
|
||||||
|
text-decoration-color: var(--ctp-macchiato-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: "Jersey 15";
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-dialog {
|
||||||
|
& article {
|
||||||
|
max-height: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& tr {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
& :hover {
|
||||||
|
background-color: var(--pico-hover-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-0 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-4 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
grid-column-gap: 1rem;
|
||||||
|
grid-row-gap: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-3 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
grid-column-gap: 1rem;
|
||||||
|
grid-row-gap: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.movie-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
grid-column-gap: 1.5rem;
|
||||||
|
grid-row-gap: 1.5rem;
|
||||||
|
|
||||||
|
& .movie-tile {
|
||||||
|
|
||||||
|
& div {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
padding: 0.25rem;
|
||||||
|
border-bottom-right-radius: var(--pico-border-radius);
|
||||||
|
border-top-left-radius: var(--pico-border-radius);
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& img {
|
||||||
|
border-radius: var(--pico-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.watched {
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
& svg {
|
||||||
|
width: 3rem;
|
||||||
|
color: var(--pico-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ratingBox {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
i {
|
||||||
|
background-color: var(--pico-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rated {
|
||||||
|
background-color: var(--pico-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
>i {
|
||||||
|
|
||||||
|
&:hover .icon,
|
||||||
|
&:hover~.icon {
|
||||||
|
background-color: var(--pico-secondary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover>.icon {
|
||||||
|
background-color: var(--pico-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.listRating {
|
||||||
|
i {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
background-color: var(--pico-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rated {
|
||||||
|
background-color: var(--pico-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: var(--pico-font-size);
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
|
||||||
|
&.star-full {
|
||||||
|
mask: url('data:image/svg+xml,<svg viewBox="0 0 24 24" fill="%231C274C" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M9.15316 5.40838C10.4198 3.13613 11.0531 2 12 2C12.9469 2 13.5802 3.13612 14.8468 5.40837L15.1745 5.99623C15.5345 6.64193 15.7144 6.96479 15.9951 7.17781C16.2757 7.39083 16.6251 7.4699 17.3241 7.62805L17.9605 7.77203C20.4201 8.32856 21.65 8.60682 21.9426 9.54773C22.2352 10.4886 21.3968 11.4691 19.7199 13.4299L19.2861 13.9372C18.8096 14.4944 18.5713 14.773 18.4641 15.1177C18.357 15.4624 18.393 15.8341 18.465 16.5776L18.5306 17.2544C18.7841 19.8706 18.9109 21.1787 18.1449 21.7602C17.3788 22.3417 16.2273 21.8115 13.9243 20.7512L13.3285 20.4768C12.6741 20.1755 12.3469 20.0248 12 20.0248C11.6531 20.0248 11.3259 20.1755 10.6715 20.4768L10.0757 20.7512C7.77268 21.8115 6.62118 22.3417 5.85515 21.7602C5.08912 21.1787 5.21588 19.8706 5.4694 17.2544L5.53498 16.5776C5.60703 15.8341 5.64305 15.4624 5.53586 15.1177C5.42868 14.773 5.19043 14.4944 4.71392 13.9372L4.2801 13.4299C2.60325 11.4691 1.76482 10.4886 2.05742 9.54773C2.35002 8.60682 3.57986 8.32856 6.03954 7.77203L6.67589 7.62805C7.37485 7.4699 7.72433 7.39083 8.00494 7.17781C8.28555 6.96479 8.46553 6.64194 8.82547 5.99623L9.15316 5.40838Z" ></path></g></svg>');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Modal
|
||||||
|
*
|
||||||
|
* Pico.css - https://picocss.com
|
||||||
|
* Copyright 2019-2024 - Licensed under MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Config
|
||||||
|
const isOpenClass = "modal-is-open";
|
||||||
|
const openingClass = "modal-is-opening";
|
||||||
|
const closingClass = "modal-is-closing";
|
||||||
|
const scrollbarWidthCssVar = "--pico-scrollbar-width";
|
||||||
|
const animationDuration = 400; // ms
|
||||||
|
let visibleModal = null;
|
||||||
|
|
||||||
|
// Toggle modal
|
||||||
|
const toggleModal = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const modal = document.getElementById(event.currentTarget.dataset.target);
|
||||||
|
if (!modal) return;
|
||||||
|
modal && (modal.open ? closeModal(modal) : openModal(modal));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open modal
|
||||||
|
const openModal = (modal) => {
|
||||||
|
const { documentElement: html } = document;
|
||||||
|
const scrollbarWidth = getScrollbarWidth();
|
||||||
|
if (scrollbarWidth) {
|
||||||
|
html.style.setProperty(scrollbarWidthCssVar, `${scrollbarWidth}px`);
|
||||||
|
}
|
||||||
|
html.classList.add(isOpenClass, openingClass);
|
||||||
|
setTimeout(() => {
|
||||||
|
visibleModal = modal;
|
||||||
|
html.classList.remove(openingClass);
|
||||||
|
}, animationDuration);
|
||||||
|
modal.showModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close modal
|
||||||
|
const closeModal = (modal) => {
|
||||||
|
visibleModal = null;
|
||||||
|
const { documentElement: html } = document;
|
||||||
|
html.classList.add(closingClass);
|
||||||
|
setTimeout(() => {
|
||||||
|
html.classList.remove(closingClass, isOpenClass);
|
||||||
|
html.style.removeProperty(scrollbarWidthCssVar);
|
||||||
|
modal.close();
|
||||||
|
}, animationDuration);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get scrollbar width
|
||||||
|
const getScrollbarWidth = () => {
|
||||||
|
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||||
|
return scrollbarWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Is scrollbar visible
|
||||||
|
const isScrollbarVisible = () => {
|
||||||
|
return document.body.scrollHeight > screen.height;
|
||||||
|
};
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
import "git.itsigo.dev/istigo/pocketmovie/internal/apis"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
templ Html(title string) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎬</text></svg>"/>
|
||||||
|
<link rel="stylesheet" href="/assets/css/pico.min.css"/>
|
||||||
|
<link rel="stylesheet" href="/assets/css/main.css"/>
|
||||||
|
<script type="module" src="/assets/js/datastar.js"></script>
|
||||||
|
<script src="/assets/js/modal.js"></script>
|
||||||
|
<title>{ title }</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{ children... }
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Base(title string) {
|
||||||
|
@Html(title) {
|
||||||
|
<header>
|
||||||
|
@header()
|
||||||
|
</header>
|
||||||
|
<main class="container">
|
||||||
|
{ children... }
|
||||||
|
</main>
|
||||||
|
// <footer>
|
||||||
|
// @footer()
|
||||||
|
// </footer>
|
||||||
|
<dialog
|
||||||
|
id="search-dialog"
|
||||||
|
data-signals="{id: ''}"
|
||||||
|
data-on-keydown__window="
|
||||||
|
if(evt.key !== 'Escape' || visibleModal === null) return;
|
||||||
|
closeModal(visibleModal);
|
||||||
|
$id = '';
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<article
|
||||||
|
data-on-click__outside="
|
||||||
|
if (visibleModal === null) return;
|
||||||
|
closeModal(visibleModal);
|
||||||
|
$id = '';
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<header>
|
||||||
|
<button
|
||||||
|
aria-label="Close"
|
||||||
|
rel="prev"
|
||||||
|
data-target="search-dialog"
|
||||||
|
data-on-click="$id = ''; toggleModal(event); "
|
||||||
|
></button>
|
||||||
|
<p>
|
||||||
|
<strong>🎬 Add Movie</strong>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div data-show="$id == ''" id="search-container">
|
||||||
|
<form data-on-submit="@post('/apis/tmdb/searchMovie', {contentType: 'form'})" role="search">
|
||||||
|
<input type="search" id="search" name="search" placeholder="Search"/>
|
||||||
|
<input type="submit" value="Search"/>
|
||||||
|
</form>
|
||||||
|
<table class="striped">
|
||||||
|
<tbody id="search-results"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div data-show="$id != ''" id="add-options">
|
||||||
|
@AddMovieConfig()
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</dialog>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ header() {
|
||||||
|
<nav class="container">
|
||||||
|
<ul>
|
||||||
|
<li><strong style="font-family: 'Jersey 15'; font-size: 2rem">Pocket Movie</strong></li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Movies</a></li>
|
||||||
|
<li><a href="/watchlist">Watchlist</a></li>
|
||||||
|
<li><a href="/settings">Settings</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ SearchMovie(movies []apis.Movie) {
|
||||||
|
<tbody id="search-results">
|
||||||
|
for _, v := range movies {
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
class="add-movie"
|
||||||
|
data-on-click={ fmt.Sprintf("$id = '%d'", v.Id) }
|
||||||
|
>
|
||||||
|
{ v.Title }
|
||||||
|
if v.ReleaseDate != "" {
|
||||||
|
({ strings.Split(v.ReleaseDate, "-")[0] })
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ AddMovieConfig() {
|
||||||
|
<form
|
||||||
|
id="addMovieForm"
|
||||||
|
class="add-movie"
|
||||||
|
data-on-submit="@post('/db/addMovie/' + $id, {contentType: 'form'}); closeModal(visibleModal)"
|
||||||
|
>
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
General
|
||||||
|
</legend>
|
||||||
|
<input type="checkbox" name="watched" id="watched"/>
|
||||||
|
<label style="padding-bottom: 0.5rem" htmlFor="watched">Watched</label>
|
||||||
|
<div class="grid">
|
||||||
|
<input type="number" name="watchcount" placeholder="Watchcount" aria-label="watchcount"/>
|
||||||
|
<input type="number" name="rating" placeholder="Rating" aria-label="rating"/>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
Physical data
|
||||||
|
</legend>
|
||||||
|
<input type="checkbox" name="owned" id="owned"/>
|
||||||
|
<label>Owned</label>
|
||||||
|
<input type="checkbox" name="ripped" id="ripped"/>
|
||||||
|
<label>Ripped</label>
|
||||||
|
</fieldset>
|
||||||
|
<select name="version" id="version" aria-label="Select physical version..">
|
||||||
|
@MediaSelectOptions()
|
||||||
|
</select>
|
||||||
|
<input id="submitMovieButton" type="submit" value="Add Movie"/>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ addMovieButton() {
|
||||||
|
<button id="show-modal-dialog" data-target="search-dialog" onCLick="toggleModal(event);" type="button">Add Movie</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MediaSelectOptions() {
|
||||||
|
<option selected disabled>Select..</option>
|
||||||
|
<option value="">None</option>
|
||||||
|
<option value="DVD">DVD</option>
|
||||||
|
<option value="BD">BD</option>
|
||||||
|
<option value="4KBD">4KBD</option>
|
||||||
|
<option value="DL">DL</option>
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed assets/*
|
||||||
|
var Files embed.FS
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "git.itsigo.dev/istigo/pocketmovie/internal/database"
|
||||||
|
import "strings"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func getConfVal(settings []database.Setting, name string) string {
|
||||||
|
for _, s := range settings {
|
||||||
|
if s.Name == name {
|
||||||
|
return s.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Show(movies []database.Movie, settings []database.Setting) {
|
||||||
|
@Base("Movies - PocketMovie") {
|
||||||
|
//<pre id="data"></pre>
|
||||||
|
//<script>
|
||||||
|
// var data = JSON.parse({{ data }});
|
||||||
|
// var formated_data = JSON.stringify(data, null, 2);
|
||||||
|
// document.getElementById("data").textContent = formated_data;
|
||||||
|
//</script>
|
||||||
|
@addMovieButton()
|
||||||
|
{{ list, grid := "1", "" }}
|
||||||
|
if getConfVal(settings, "HOME_GRID_VIEW" ) == "true" {
|
||||||
|
{{ list, grid = "", "1" }}
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
data-signals={ fmt.Sprintf("{list : '%s', grid : '%s'}", list, grid) }
|
||||||
|
style="float:right"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-show="$list != ''"
|
||||||
|
data-on-click="
|
||||||
|
@post('/db/updateTableView')
|
||||||
|
$list = '';
|
||||||
|
$grid = '1'
|
||||||
|
"
|
||||||
|
>List ☰</button>
|
||||||
|
<button
|
||||||
|
data-show="$grid != ''"
|
||||||
|
data-on-click="
|
||||||
|
@post('/db/updateTableView')
|
||||||
|
$list = '1';
|
||||||
|
$grid = ''
|
||||||
|
"
|
||||||
|
>Grid 田</button>
|
||||||
|
</div>
|
||||||
|
if grid != "" {
|
||||||
|
@MovieTiles(movies)
|
||||||
|
} else {
|
||||||
|
@MovieList(movies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MovieList(movies []database.Movie) {
|
||||||
|
<table id="movie-list" class="striped">
|
||||||
|
<tr>
|
||||||
|
<th>Titel</th>
|
||||||
|
<th>Year</th>
|
||||||
|
<th>Rating</th>
|
||||||
|
<th>Watched</th>
|
||||||
|
<th>Owned</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
for _, item := range movies {
|
||||||
|
<tr>
|
||||||
|
<td><a href={ fmt.Sprintf("/movie/%d", item.ID) }>{ item.Title }</a></td>
|
||||||
|
<td>{ strings.Split(item.Year, "-")[0] }</td>
|
||||||
|
<td>
|
||||||
|
<div class="listRating">
|
||||||
|
{{ class := "icon star-full" }}
|
||||||
|
for i := range(5) {
|
||||||
|
<i
|
||||||
|
if int64(i) < item.Rating {
|
||||||
|
class={ class + " rated" }
|
||||||
|
} else {
|
||||||
|
class={ class }
|
||||||
|
}
|
||||||
|
></i>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{{ statusClasses := "bg-red" }}
|
||||||
|
if item.Status == 1 {
|
||||||
|
{{ statusClasses = "bg-green" }}
|
||||||
|
}
|
||||||
|
<td
|
||||||
|
class={ statusClasses }
|
||||||
|
>
|
||||||
|
{ item.Status != 0 }
|
||||||
|
</td>
|
||||||
|
{{ ownedClasses := "bg-red" }}
|
||||||
|
if item.Owned == 1 {
|
||||||
|
{{ ownedClasses = "bg-green" }}
|
||||||
|
}
|
||||||
|
<td
|
||||||
|
class={ ownedClasses }
|
||||||
|
>
|
||||||
|
{ item.Owned != 0 }
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
class="red-link"
|
||||||
|
href="#"
|
||||||
|
data-on-click={ fmt.Sprintf("confirm('Are you sure?') && @delete('/db/deleteMovie/%d')", item.ID) }
|
||||||
|
>Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MovieTiles(movies []database.Movie) {
|
||||||
|
<div id="movie-list" class="movie-grid">
|
||||||
|
for _, item := range movies {
|
||||||
|
<a
|
||||||
|
class="movie-tile"
|
||||||
|
data-tooltip={ item.Title }
|
||||||
|
data-placement="bottom"
|
||||||
|
href={ fmt.Sprintf("/movie/%d", item.ID) }
|
||||||
|
title={ item.Title }
|
||||||
|
style="position:relative"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
if item.Status == 1 {
|
||||||
|
@fullEye()
|
||||||
|
} else {
|
||||||
|
@closedEye()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<img alt={ item.Title } src={ fmt.Sprintf("/movie/posters/%d.jpg", item.ID) }/>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "git.itsigo.dev/istigo/pocketmovie/internal/database"
|
||||||
|
import "fmt"
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
templ MovieDetails(movie database.Movie) {
|
||||||
|
@Base(movie.Title + " - PocketMovie") {
|
||||||
|
<div class="grid-4">
|
||||||
|
<div style="aspect-ratio: 2 / 3">
|
||||||
|
<img style="border-radius: var(--pico-border-radius)" alt={ movie.Title } src={ fmt.Sprintf("/movie/posters/%d.jpg", movie.ID) }/>
|
||||||
|
</div>
|
||||||
|
<div style="grid-area: 1 / 2 / 2 / 5;">
|
||||||
|
<div>
|
||||||
|
<h1 style="display:inline-block; padding-right:0.5rem">{ movie.Title }</h1>
|
||||||
|
<div style="display:inline-block"><i>{ strings.Split(movie.Year, "-")[0] }</i> Directed by <i>{ movie.Director }</i></div>
|
||||||
|
</div>
|
||||||
|
<div class="grid-3">
|
||||||
|
<div style="grid-area: 1 / 1 / 2 / 3;">
|
||||||
|
<p>{ movie.Overview } </p>
|
||||||
|
<p>Genres: { movie.Genre }</p>
|
||||||
|
<p>Runtime: { movie.Length } Min</p>
|
||||||
|
if movie.StreamingServices != "" {
|
||||||
|
<p>Stream provider: { movie.StreamingServices }</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="grid-area: 1 / 4 / 2 / 5;">
|
||||||
|
<article>
|
||||||
|
@MovieDetailsWatched(movie.ID, movie.Status)
|
||||||
|
<hr/>
|
||||||
|
@MovieDetailsRating(movie.ID, movie.Rating)
|
||||||
|
<hr/>
|
||||||
|
<div class="grid">
|
||||||
|
@MovieDetailsOwned(movie.ID, movie.OwnedType)
|
||||||
|
@MovieDetailsRipped(movie.ID, movie.Ripped)
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gives you the opposite of 1 or 0
|
||||||
|
func revIntBool(status int64) int64 {
|
||||||
|
if status == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MovieDetailsWatched(id int64, status int64) {
|
||||||
|
<div id="movieDetailsWatched" class="flex-center">
|
||||||
|
<span
|
||||||
|
class="watched"
|
||||||
|
data-on-click={ fmt.Sprintf("@post('/db/changeMovieStatus/%d.%d')", id, revIntBool(status)) }
|
||||||
|
>
|
||||||
|
if status == 1 {
|
||||||
|
@fullEye()
|
||||||
|
} else {
|
||||||
|
@closedEye()
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
if status == 1 {
|
||||||
|
<p class="m-0">Watched</p>
|
||||||
|
} else {
|
||||||
|
<p class="m-0">Not watched</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MovieDetailsRating(id int64, rating int64) {
|
||||||
|
<div id="ratings">
|
||||||
|
<p class="center m-0">
|
||||||
|
if rating != 0 {
|
||||||
|
Rated
|
||||||
|
} else {
|
||||||
|
Not rated
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<div class="ratingBox">
|
||||||
|
{{ class := "icon star-full" }}
|
||||||
|
for i := range(5) {
|
||||||
|
<i
|
||||||
|
data-on-click={ fmt.Sprintf("@post('/db/changeMovieRating/%d.%d')", id, i+1) }
|
||||||
|
if int64(i) < rating {
|
||||||
|
class={ class + " rated" }
|
||||||
|
} else {
|
||||||
|
class={ class }
|
||||||
|
}
|
||||||
|
></i>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MovieDetailsOwned(id int64, option string) {
|
||||||
|
<div id="mediatype" class="flex-center">
|
||||||
|
<p>
|
||||||
|
if option != "" {
|
||||||
|
Owned
|
||||||
|
} else {
|
||||||
|
Not owned
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
data-on-click={ fmt.Sprintf("@get('/components/mediaTypeSelect/%d')", id) }
|
||||||
|
class="watched"
|
||||||
|
style="width:fit-content;"
|
||||||
|
>
|
||||||
|
switch option {
|
||||||
|
case "4KBD":
|
||||||
|
@ultraHD()
|
||||||
|
case "BD":
|
||||||
|
@bluray()
|
||||||
|
case "DVD":
|
||||||
|
@dvd()
|
||||||
|
case "DL":
|
||||||
|
@fullDownload()
|
||||||
|
default:
|
||||||
|
@forbiddenLineCircle()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MovieDetailsRipped(id int64, status int64) {
|
||||||
|
<div id="ripped">
|
||||||
|
<div
|
||||||
|
class="watched"
|
||||||
|
data-on-click={ fmt.Sprintf("@post('/db/changeMovieRipped/%d.%d')", id, revIntBool(status)) }
|
||||||
|
>
|
||||||
|
if status != 0 {
|
||||||
|
<p class="center">Ripped</p>
|
||||||
|
@checkedFullCircle()
|
||||||
|
} else {
|
||||||
|
<p class="center">Not ripped</p>
|
||||||
|
@forbiddenLineCircle()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MovieDetailsOwnedSelect(id int8) {
|
||||||
|
<div id="mediatype" class="flex-center">
|
||||||
|
<p class="">Select option</p>
|
||||||
|
<form>
|
||||||
|
<select
|
||||||
|
name="option"
|
||||||
|
data-bind-select
|
||||||
|
data-on-change={ fmt.Sprintf("@post('/db/changeMovieOwned/%d', {contentType: 'form'})", id) }
|
||||||
|
>
|
||||||
|
@MediaSelectOptions()
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ fullStar() {
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M9.15316 5.40838C10.4198 3.13613 11.0531 2 12 2C12.9469 2 13.5802 3.13612 14.8468 5.40837L15.1745 5.99623C15.5345 6.64193 15.7144 6.96479 15.9951 7.17781C16.2757 7.39083 16.6251 7.4699 17.3241 7.62805L17.9605 7.77203C20.4201 8.32856 21.65 8.60682 21.9426 9.54773C22.2352 10.4886 21.3968 11.4691 19.7199 13.4299L19.2861 13.9372C18.8096 14.4944 18.5713 14.773 18.4641 15.1177C18.357 15.4624 18.393 15.8341 18.465 16.5776L18.5306 17.2544C18.7841 19.8706 18.9109 21.1787 18.1449 21.7602C17.3788 22.3417 16.2273 21.8115 13.9243 20.7512L13.3285 20.4768C12.6741 20.1755 12.3469 20.0248 12 20.0248C11.6531 20.0248 11.3259 20.1755 10.6715 20.4768L10.0757 20.7512C7.77268 21.8115 6.62118 22.3417 5.85515 21.7602C5.08912 21.1787 5.21588 19.8706 5.4694 17.2544L5.53498 16.5776C5.60703 15.8341 5.64305 15.4624 5.53586 15.1177C5.42868 14.773 5.19043 14.4944 4.71392 13.9372L4.2801 13.4299C2.60325 11.4691 1.76482 10.4886 2.05742 9.54773C2.35002 8.60682 3.57986 8.32856 6.03954 7.77203L6.67589 7.62805C7.37485 7.4699 7.72433 7.39083 8.00494 7.17781C8.28555 6.96479 8.46553 6.64194 8.82547 5.99623L9.15316 5.40838Z"></path> </g></svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ emptyStar() {
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M9.15316 5.40838C10.4198 3.13613 11.0531 2 12 2C12.9469 2 13.5802 3.13612 14.8468 5.40837L15.1745 5.99623C15.5345 6.64193 15.7144 6.96479 15.9951 7.17781C16.2757 7.39083 16.6251 7.4699 17.3241 7.62805L17.9605 7.77203C20.4201 8.32856 21.65 8.60682 21.9426 9.54773C22.2352 10.4886 21.3968 11.4691 19.7199 13.4299L19.2861 13.9372C18.8096 14.4944 18.5713 14.773 18.4641 15.1177C18.357 15.4624 18.393 15.8341 18.465 16.5776L18.5306 17.2544C18.7841 19.8706 18.9109 21.1787 18.1449 21.7602C17.3788 22.3417 16.2273 21.8115 13.9243 20.7512L13.3285 20.4768C12.6741 20.1755 12.3469 20.0248 12 20.0248C11.6531 20.0248 11.3259 20.1755 10.6715 20.4768L10.0757 20.7512C7.77268 21.8115 6.62118 22.3417 5.85515 21.7602C5.08912 21.1787 5.21588 19.8706 5.4694 17.2544L5.53498 16.5776C5.60703 15.8341 5.64305 15.4624 5.53586 15.1177C5.42868 14.773 5.19043 14.4944 4.71392 13.9372L4.2801 13.4299C2.60325 11.4691 1.76482 10.4886 2.05742 9.54773C2.35002 8.60682 3.57986 8.32856 6.03954 7.77203L6.67589 7.62805C7.37485 7.4699 7.72433 7.39083 8.00494 7.17781C8.28555 6.96479 8.46553 6.64194 8.82547 5.99623L9.15316 5.40838Z" stroke="var(--pico-secondary)" stroke-width="1.5"></path> </g></svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ fullEye() {
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M9.75 12C9.75 10.7574 10.7574 9.75 12 9.75C13.2426 9.75 14.25 10.7574 14.25 12C14.25 13.2426 13.2426 14.25 12 14.25C10.7574 14.25 9.75 13.2426 9.75 12Z"></path> <path fill-rule="evenodd" clip-rule="evenodd" d="M2 12C2 13.6394 2.42496 14.1915 3.27489 15.2957C4.97196 17.5004 7.81811 20 12 20C16.1819 20 19.028 17.5004 20.7251 15.2957C21.575 14.1915 22 13.6394 22 12C22 10.3606 21.575 9.80853 20.7251 8.70433C19.028 6.49956 16.1819 4 12 4C7.81811 4 4.97196 6.49956 3.27489 8.70433C2.42496 9.80853 2 10.3606 2 12ZM12 8.25C9.92893 8.25 8.25 9.92893 8.25 12C8.25 14.0711 9.92893 15.75 12 15.75C14.0711 15.75 15.75 14.0711 15.75 12C15.75 9.92893 14.0711 8.25 12 8.25Z"></path> </g></svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ closedEye() {
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.60603 6.08062C2.11366 5.86307 2.70154 6.09822 2.9191 6.60585L1.99995 6.99977C2.9191 6.60585 2.91924 6.60618 2.9191 6.60585L2.91858 6.60465C2.9183 6.604 2.91851 6.60447 2.91858 6.60465L2.9225 6.61351C2.92651 6.62253 2.93339 6.63785 2.94319 6.65905C2.96278 6.70147 2.99397 6.76735 3.03696 6.85334C3.12302 7.02546 3.25594 7.27722 3.43737 7.58203C3.80137 8.19355 4.35439 9.00801 5.10775 9.81932C5.28532 10.0105 5.47324 10.2009 5.67173 10.3878C5.68003 10.3954 5.68823 10.4031 5.69633 10.4109C7.18102 11.8012 9.25227 12.9998 12 12.9998C13.2089 12.9998 14.2783 12.769 15.2209 12.398C16.4469 11.9154 17.4745 11.1889 18.3156 10.3995C19.2652 9.50815 19.9627 8.54981 20.4232 7.81076C20.6526 7.44268 20.8207 7.13295 20.9299 6.91886C20.9844 6.81192 21.0241 6.72919 21.0491 6.67538C21.0617 6.64848 21.0706 6.62884 21.0758 6.61704L21.0808 6.60585C21.2985 6.0985 21.8864 5.86312 22.3939 6.08062C22.9015 6.29818 23.1367 6.88606 22.9191 7.39369L22 6.99977C22.9191 7.39369 22.9192 7.39346 22.9191 7.39369L22.9169 7.39871L22.9134 7.40693L22.9019 7.43278C22.8924 7.4541 22.879 7.48354 22.8618 7.52048C22.8274 7.59434 22.7774 7.69831 22.7115 7.8275C22.5799 8.08566 22.384 8.44584 22.1206 8.86844C21.718 9.5146 21.152 10.316 20.4096 11.1241L21.2071 11.9215C21.5976 12.312 21.5976 12.9452 21.2071 13.3357C20.8165 13.7262 20.1834 13.7262 19.7928 13.3357L18.9527 12.4955C18.3884 12.9513 17.757 13.3811 17.0558 13.752L17.8381 14.9544C18.1393 15.4173 18.0083 16.0367 17.5453 16.338C17.0824 16.6392 16.463 16.5081 16.1618 16.0452L15.1763 14.5306C14.4973 14.7388 13.772 14.8863 13 14.9554V16.4998C13 17.0521 12.5522 17.4998 12 17.4998C11.4477 17.4998 11 17.0521 11 16.4998V14.9556C10.2253 14.8864 9.50014 14.7386 8.82334 14.531L7.83814 16.0452C7.53693 16.5081 6.91748 16.6392 6.45457 16.338C5.99165 16.0367 5.86056 15.4173 6.16177 14.9544L6.94417 13.7519C6.24405 13.3814 5.61245 12.9515 5.04746 12.4953L4.20706 13.3357C3.81654 13.7262 3.18337 13.7262 2.79285 13.3357C2.40232 12.9452 2.40232 12.312 2.79285 11.9215L3.59029 11.1241C2.74529 10.2043 2.12772 9.292 1.71879 8.605C1.5096 8.25356 1.35345 7.95845 1.2481 7.74776C1.19539 7.64234 1.15529 7.55783 1.12752 7.49771C1.11363 7.46765 1.10282 7.44366 1.09505 7.42618L1.08566 7.4049L1.08267 7.39801L1.0816 7.39553L1.08117 7.39453C1.08098 7.39409 1.08081 7.39369 1.99995 6.99977L1.08117 7.39453C0.863613 6.8869 1.0984 6.29818 1.60603 6.08062Z" fill="var(--pico-secondary)"></path> </g></svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ ultraHD() {
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 48 48">
|
||||||
|
<path d="M 2.2050781 15.027344 C 1.7818486 15.027344 1.3552282 15.109094 0.87695312 15.458984 C 0.39867801 15.808872 0.001953125 16.540578 0.001953125 17.232422 L 0.001953125 25.769531 C 0.001953125 26.1935 0.0842092 26.619918 0.43359375 27.097656 C 0.7829783 27.575395 1.5129957 27.972656 2.2050781 27.972656 L 45.796875 27.972656 C 46.222902 27.972656 46.648027 27.889246 47.125 27.541016 C 47.601973 27.192789 48.001953 26.462999 48.001953 25.769531 L 48.001953 17.232422 C 48.001953 16.807143 47.917093 16.381808 47.568359 15.904297 C 47.219629 15.426786 46.490097 15.027344 45.796875 15.027344 L 2.2050781 15.027344 z M 3.0019531 18.027344 L 45.001953 18.027344 L 45.001953 24.972656 L 3.0019531 24.972656 L 3.0019531 18.027344 z M 4.4277344 18.876953 L 4.4277344 22.251953 C 4.4277344 24.059953 5.4580781 24.117188 6.9550781 24.117188 C 7.3440781 24.117188 7.7399062 24.123047 8.1289062 24.123047 C 9.5179062 24.123047 10.478516 23.913219 10.478516 22.324219 L 10.478516 22.046875 L 10.478516 18.876953 L 9.0390625 18.876953 L 9.0390625 21.857422 L 9.0390625 22.033203 C 9.0180625 22.734203 8.9270938 22.960938 8.1210938 22.960938 L 7.5273438 22.966797 L 6.8867188 22.960938 C 5.9407187 22.937938 5.8883281 22.712078 5.8613281 21.830078 L 5.8613281 18.876953 L 4.4277344 18.876953 z M 30.265625 18.876953 L 30.265625 24.076172 L 31.699219 24.076172 L 31.699219 18.876953 L 30.265625 18.876953 z M 34.947266 18.876953 L 34.947266 20.84375 L 32.523438 20.84375 L 31.798828 22.015625 L 34.947266 22.015625 L 34.947266 24.074219 L 36.388672 24.074219 L 36.388672 18.876953 L 34.947266 18.876953 z M 37.355469 18.876953 L 37.355469 24.074219 L 38.789062 24.074219 L 38.789062 20.035156 L 41.224609 20.035156 C 42.199609 20.035156 42.212891 20.402031 42.212891 21.207031 L 42.212891 21.736328 C 42.212891 22.561328 42.149172 22.923828 41.201172 22.923828 L 39.5625 22.923828 L 38.851562 24.074219 L 42.035156 24.074219 C 42.854156 24.074219 43.460516 23.679375 43.603516 22.859375 C 43.660516 22.498375 43.660156 22.118953 43.660156 21.751953 L 43.662109 21.751953 L 43.662109 20.994141 C 43.662109 19.293141 43.188812 18.876953 41.507812 18.876953 L 37.355469 18.876953 z M 11.455078 19.712891 L 11.455078 23.255859 L 14.980469 23.255859 L 14.980469 22.306641 L 12.589844 22.306641 L 12.589844 19.712891 L 11.455078 19.712891 z M 14.216797 19.712891 L 14.216797 20.660156 L 15.763672 20.660156 L 15.763672 23.255859 L 16.898438 23.255859 L 16.898438 20.660156 L 18.439453 20.660156 L 18.439453 19.712891 L 14.216797 19.712891 z M 18.939453 19.712891 L 18.939453 23.255859 L 20.074219 23.255859 L 20.074219 20.615234 L 21.787109 20.615234 C 21.866109 20.615234 21.982125 20.618531 22.078125 20.644531 C 22.241125 20.692531 22.251859 20.794375 22.255859 20.984375 L 22.255859 21.099609 C 22.255859 21.444609 22.230719 21.527344 21.761719 21.527344 L 20.662109 21.527344 L 20.142578 22.365234 L 21.792969 22.365234 C 22.189969 22.365234 22.210938 22.498812 22.210938 22.882812 L 22.210938 23.255859 L 23.359375 23.255859 L 23.359375 22.548828 C 23.359375 22.108828 23.146109 21.946094 22.787109 21.871094 C 22.964109 21.816094 23.137094 21.749078 23.246094 21.580078 C 23.379094 21.367078 23.398438 21.101656 23.398438 20.847656 C 23.398437 20.020656 23.075172 19.712891 22.201172 19.712891 L 18.939453 19.712891 z M 25.722656 19.712891 L 23.746094 23.255859 L 25.013672 23.255859 L 25.355469 22.625 L 27.542969 22.625 L 27.869141 23.255859 L 29.123047 23.255859 L 27.203125 19.732422 L 27.191406 19.712891 L 25.722656 19.712891 z M 26.4375 20.576172 L 27.154297 21.900391 L 25.720703 21.900391 L 26.4375 20.576172 z M 15.335938 29.105469 C 14.846438 29.106719 14.321141 29.123703 13.869141 29.158203 C 13.670141 29.158203 13.464469 29.409453 13.355469 29.564453 C 13.251469 29.721453 11.869156 31.970547 11.660156 32.310547 C 11.451156 32.642547 11.641859 32.771109 11.880859 32.787109 C 12.507859 32.823109 13.32625 32.826109 14.28125 32.787109 C 15.13025 32.748109 15.717672 32.736328 16.388672 31.486328 C 16.683672 30.932328 15.857422 30.902344 15.857422 30.902344 C 15.944422 30.897344 16.661266 30.831609 17.072266 29.974609 C 17.492266 29.097609 16.734703 29.168344 16.595703 29.152344 C 16.279703 29.119844 15.825437 29.104219 15.335938 29.105469 z M 18.873047 29.216797 C 18.785047 29.216797 18.516813 29.301781 18.257812 29.675781 C 17.995812 30.053781 16.863484 31.931344 16.646484 32.277344 C 16.428484 32.627344 16.503422 32.794922 16.732422 32.794922 L 17.238281 32.794922 C 17.486281 32.794922 17.685922 32.508938 17.794922 32.335938 C 17.901922 32.161938 19.256172 29.983109 19.451172 29.662109 C 19.646172 29.343109 19.521672 29.216797 19.388672 29.216797 L 18.873047 29.216797 z M 14.863281 29.804688 L 15.347656 29.804688 C 15.492656 29.803687 15.831297 29.796109 15.654297 30.162109 C 15.422297 30.647109 14.951844 30.660156 14.714844 30.660156 L 14.333984 30.660156 C 14.245984 30.660156 14.150766 30.661094 14.259766 30.496094 C 14.365766 30.325094 14.517844 30.061266 14.5898Line truncated
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ bluray() {
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 26.001 26.001" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><g><g><path d="M2.737,8.325c-0.017,0-0.031,0.009-0.042,0.022c-1.151,1.621-1.845,2.718-2.565,4.05l-0.072,0.147 l-0.021,0.047c-0.062,0.129-0.046,0.272,0.04,0.408c0.426,0.666,2.813,1.444,8.144,1.444c3.975,0,8.197-0.642,8.197-1.831 c0-1.148-4.17-1.834-8.197-1.834c-1.338,0-2.662,0.188-3.044,0.248c0.364-0.561,1.877-2.598,1.893-2.619 c0.007-0.009,0.012-0.019,0.012-0.03c0-0.008-0.003-0.016-0.006-0.024C7.064,8.337,7.047,8.325,7.029,8.325H2.737 M4.29,12.613 c0-0.248,1.495-0.595,3.931-0.595c2.435,0,3.928,0.347,3.928,0.595s-1.493,0.594-3.928,0.594 C5.785,13.207,4.29,12.861,4.29,12.613"></path> <path d="M5.753,18.595c0,0,19.818,0.806,20.241-6.12c0.342-5.618-13.733-5.084-13.746-5.084 S12.139,7.4,12.139,7.48c0,0.067,0.05,0.09,0.101,0.09c3.903,0,10.254,1.55,10.047,4.909c-0.167,2.736-5.122,5.938-16.527,5.938 c-0.068,0-0.113,0.047-0.113,0.089S5.672,18.587,5.753,18.595"></path> </g> </g> </g></svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ dvd() {
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 15.465 15.465" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><g><g><g><path d="M7.4,8.959c-4.132,0-7.4,0.55-7.4,1.227c0,0.678,3.268,1.227,7.4,1.227s7.543-0.549,7.543-1.227 C14.944,9.508,11.533,8.959,7.4,8.959z M7.263,10.51c-0.957,0-1.733-0.237-1.733-0.53s0.776-0.53,1.733-0.53 s1.732,0.237,1.732,0.53S8.219,10.51,7.263,10.51z M13.319,4.052H9.701L7.769,6.291l-0.92-2.208H1.145L0.933,5.045h2.269 c0,0,1.037-0.136,1.071,0.694c0,1.438-2.376,1.316-2.376,1.316l0.444-1.5H0.869L0.194,7.988h2.668c0,0,2.821-0.25,2.821-2.218 c0,0,0.114-0.574,0.066-0.827L7.124,8.62l3.435-3.565h2.606c0,0,0.798,0.068,0.798,0.685c0,1.438-2.359,1.288-2.359,1.288 l0.365-1.472h-1.287L9.946,7.989h2.453c0,0,3.066-0.19,3.066-2.279C15.465,5.709,15.404,4.052,13.319,4.052z"></path> </g> </g> </g> </g></svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ fullDownload() {
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path fill-rule="evenodd" clip-rule="evenodd" d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12ZM12 6.25C12.4142 6.25 12.75 6.58579 12.75 7V12.1893L14.4697 10.4697C14.7626 10.1768 15.2374 10.1768 15.5303 10.4697C15.8232 10.7626 15.8232 11.2374 15.5303 11.5303L12.5303 14.5303C12.3897 14.671 12.1989 14.75 12 14.75C11.8011 14.75 11.6103 14.671 11.4697 14.5303L8.46967 11.5303C8.17678 11.2374 8.17678 10.7626 8.46967 10.4697C8.76256 10.1768 9.23744 10.1768 9.53033 10.4697L11.25 12.1893V7C11.25 6.58579 11.5858 6.25 12 6.25ZM8 16.25C7.58579 16.25 7.25 16.5858 7.25 17C7.25 17.4142 7.58579 17.75 8 17.75H16C16.4142 17.75 16.75 17.4142 16.75 17C16.75 16.5858 16.4142 16.25 16 16.25H8Z"></path> </g></svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ forbiddenLineCircle() {
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M18.5 5.5L5.50002 18.4998" stroke="var(--pico-secondary)" stroke-width="1.5" stroke-linecap="round"></path> <circle cx="12" cy="12" r="10" stroke="var(--pico-secondary)" stroke-width="1.5"></circle> </g></svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ checkedFullCircle() {
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM16.0303 8.96967C16.3232 9.26256 16.3232 9.73744 16.0303 10.0303L11.0303 15.0303C10.7374 15.3232 10.2626 15.3232 9.96967 15.0303L7.96967 13.0303C7.67678 12.7374 7.67678 12.2626 7.96967 11.9697C8.26256 11.6768 8.73744 11.6768 9.03033 11.9697L10.5 13.4393L12.7348 11.2045L14.9697 8.96967C15.2626 8.67678 15.7374 8.67678 16.0303 8.96967Z"></path> </g></svg>
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "git.itsigo.dev/istigo/pocketmovie/internal/database"
|
||||||
|
import "git.itsigo.dev/istigo/pocketmovie/internal/apis"
|
||||||
|
|
||||||
|
type SettingsConfig struct {
|
||||||
|
Providers []database.StreamingService
|
||||||
|
AvailableProviders []string
|
||||||
|
Regions []apis.Region
|
||||||
|
SelectedRegion string
|
||||||
|
APIKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Settings(settings SettingsConfig) {
|
||||||
|
@Base("Settings - PocketMovie") {
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<div class="grid">
|
||||||
|
<div>
|
||||||
|
<h3>Stream Region</h3>
|
||||||
|
@Region(settings)
|
||||||
|
<h3>Stream Providers</h3>
|
||||||
|
@ProviderTable(settings)
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>API Token</h3>
|
||||||
|
@ApiKey(settings)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ ApiKey(settings SettingsConfig) {
|
||||||
|
<form id="apikeyinput">
|
||||||
|
<fieldset role="group">
|
||||||
|
<input type="text" value={ settings.APIKey } name="apikey" placeholder="TMDB Read Access Token"/>
|
||||||
|
<input type="submit" value="Change" data-on-click="@post('/db/updateApiKey', {contentType: 'form'})"/>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ ProviderTable(settings SettingsConfig) {
|
||||||
|
<div id="providers">
|
||||||
|
<table class="striped">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
for _, p := range(settings.Providers) {
|
||||||
|
<tr>
|
||||||
|
<td>{ p.Title }</td>
|
||||||
|
<td class="center">
|
||||||
|
<a
|
||||||
|
class="red-link"
|
||||||
|
href="#"
|
||||||
|
data-on-click={ fmt.Sprintf("confirm('Are you sure?') && @delete('/db/deleteStreamingService/%d')", p.ID) }
|
||||||
|
>Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
<form>
|
||||||
|
<fieldset role="group">
|
||||||
|
<select name="service" aria-label="Services" required>
|
||||||
|
<option selected disabled value="">Select</option>
|
||||||
|
for _, p := range(settings.AvailableProviders) {
|
||||||
|
<option>{ p }</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Add" data-on-click="@post('/db/addStreamingService', {contentType: 'form'})"/>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Region(settings SettingsConfig) {
|
||||||
|
<form id="regionselector">
|
||||||
|
<fieldset role="group">
|
||||||
|
<select
|
||||||
|
data-on-change={ fmt.Sprintf("@post('/db/updateRegion', {contentType: 'form'})") }
|
||||||
|
name="region"
|
||||||
|
aria-label="Regions"
|
||||||
|
>
|
||||||
|
for _, r := range(settings.Regions) {
|
||||||
|
<option
|
||||||
|
if settings.SelectedRegion == r.Iso {
|
||||||
|
selected
|
||||||
|
}
|
||||||
|
value={ r.Iso }
|
||||||
|
>{ r.EnglishName }</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "git.itsigo.dev/istigo/pocketmovie/internal/database"
|
||||||
|
import "fmt"
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
templ WatchList(movies []database.Movie) {
|
||||||
|
@Base("Watchlist - PocketMovie") {
|
||||||
|
@addMovieButton()
|
||||||
|
<button
|
||||||
|
data-on-click="@post('/db/updateStreamingServices')"
|
||||||
|
data-tooltip="Refresh streaming services"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
@refresh()
|
||||||
|
</button>
|
||||||
|
@List(movies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ List(movies []database.Movie) {
|
||||||
|
<table id="movie-list" class="striped">
|
||||||
|
<tr>
|
||||||
|
<th>Titel</th>
|
||||||
|
<th>Year</th>
|
||||||
|
<th>Genre</th>
|
||||||
|
<th>Owned</th>
|
||||||
|
<th>Streaming service</th>
|
||||||
|
</tr>
|
||||||
|
for _, item := range movies {
|
||||||
|
<tr>
|
||||||
|
<td><a href={ fmt.Sprintf("/movie/%d", item.ID) }>{ item.Title }</a></td>
|
||||||
|
<td>{ strings.Split(item.Year, "-")[0] }</td>
|
||||||
|
<td>{ item.Genre }</td>
|
||||||
|
{{ ownedClasses := "bg-red" }}
|
||||||
|
if item.Owned == 1 {
|
||||||
|
{{ ownedClasses = "bg-green" }}
|
||||||
|
}
|
||||||
|
<td
|
||||||
|
class={ ownedClasses }
|
||||||
|
>
|
||||||
|
{ item.Owned != 0 }
|
||||||
|
</td>
|
||||||
|
<td>{ item.StreamingServices }</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ refresh() {
|
||||||
|
<svg width="1em" height="1em" viewBox="0 0 24 24" stroke="var(--pico-primary-inverse)" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M12 21C16.9706 21 21 16.9706 21 12C21 9.69494 20.1334 7.59227 18.7083 6L16 3M12 3C7.02944 3 3 7.02944 3 12C3 14.3051 3.86656 16.4077 5.29168 18L8 21M21 3H16M16 3V8M3 21H8M8 21V16" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
module git.itsigo.dev/istigo/pocketmovie
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/a-h/templ v0.3.943
|
||||||
|
github.com/gofiber/fiber/v3 v3.0.0-rc.1
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
|
github.com/gofiber/schema v1.6.0 // indirect
|
||||||
|
github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
|
github.com/tinylib/msgp v1.4.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.65.0 // indirect
|
||||||
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
|
golang.org/x/net v0.43.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
github.com/a-h/templ v0.3.943 h1:o+mT/4yqhZ33F3ootBiHwaY4HM5EVaOJfIshvd5UNTY=
|
||||||
|
github.com/a-h/templ v0.3.943/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
|
||||||
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
|
github.com/gofiber/fiber/v3 v3.0.0-rc.1 h1:034MxesK6bqGkidP+QR+Ysc1ukOacBWOHCarCKC1xfg=
|
||||||
|
github.com/gofiber/fiber/v3 v3.0.0-rc.1/go.mod h1:hFdT00oT0XVuQH1/z2i5n1pl/msExHDUie1SsLOkCuM=
|
||||||
|
github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY=
|
||||||
|
github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s=
|
||||||
|
github.com/gofiber/utils/v2 v2.0.0-rc.1 h1:b77K5Rk9+Pjdxz4HlwEBnS7u5nikhx7armQB8xPds4s=
|
||||||
|
github.com/gofiber/utils/v2 v2.0.0-rc.1/go.mod h1:Y1g08g7gvST49bbjHJ1AVqcsmg93912R/tbKWhn6V3E=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||||
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/shamaton/msgpack/v2 v2.3.0 h1:eawIa7lQmwRv0V6rdmL/5Ev9KdJHk07eQH3ceJi3BUw=
|
||||||
|
github.com/shamaton/msgpack/v2 v2.3.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
|
||||||
|
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||||
|
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8=
|
||||||
|
github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
|
||||||
|
github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package apis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
token = os.Getenv("TMDB_API_KEY")
|
||||||
|
apiurl = "https://api.themoviedb.org/3/"
|
||||||
|
)
|
||||||
|
|
||||||
|
func request(requrl string) (*http.Response, []byte) {
|
||||||
|
req, _ := http.NewRequest("GET", apiurl+requrl, nil)
|
||||||
|
|
||||||
|
req.Header.Add("authorization", "Bearer "+token)
|
||||||
|
req.Header.Add("accept", "application/json")
|
||||||
|
|
||||||
|
res, _ := http.DefaultClient.Do(req)
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(res.Body)
|
||||||
|
|
||||||
|
res.Body.Close()
|
||||||
|
return res, body
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildQuery(opts map[string]string) string {
|
||||||
|
query := url.Values{}
|
||||||
|
for k, v := range opts {
|
||||||
|
query.Add(k, v)
|
||||||
|
}
|
||||||
|
return query.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Results []Movie `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Movie struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
OriginalTitle string `json:"original_title"`
|
||||||
|
PosterPath string `json:"poster_path"`
|
||||||
|
ReleaseDate string `json:"release_date"`
|
||||||
|
ImdbID string `json:"imdb_id"`
|
||||||
|
Lenght int `json:"runtime"`
|
||||||
|
Genres []Genre `json:"genres"`
|
||||||
|
Overview string `json:"overview"`
|
||||||
|
Director string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Genre struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Crew struct {
|
||||||
|
Person []Person `json:"crew"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Department string `json:"job"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WatchProviders struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Results map[string]Country `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Country struct {
|
||||||
|
Link string `json:"link"`
|
||||||
|
Flatrate []Provider `json:"flatrate,omitempty"`
|
||||||
|
Free []Provider `json:"free,omitempty"`
|
||||||
|
Rent []Provider `json:"rent,omitempty"`
|
||||||
|
Buy []Provider `json:"buy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Provider struct {
|
||||||
|
LogoPath string `json:"logo_path"`
|
||||||
|
ProviderID int `json:"provider_id"`
|
||||||
|
ProviderName string `json:"provider_name"`
|
||||||
|
DisplayPriority int `json:"display_priority"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchTmdbMovie(search string) []Movie {
|
||||||
|
opts := map[string]string{"query": search, "page": "1"}
|
||||||
|
url := fmt.Sprintf("search/movie?%s", buildQuery(opts))
|
||||||
|
|
||||||
|
_, body := request(url)
|
||||||
|
|
||||||
|
var responseObjekt Response
|
||||||
|
json.Unmarshal(body, &responseObjekt)
|
||||||
|
|
||||||
|
return responseObjekt.Results
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMovieDirector(id string) string {
|
||||||
|
url := fmt.Sprintf("movie/%s/credits", id)
|
||||||
|
_, body := request(url)
|
||||||
|
|
||||||
|
var crewObjekt Crew
|
||||||
|
json.Unmarshal(body, &crewObjekt)
|
||||||
|
|
||||||
|
for _, v := range crewObjekt.Person {
|
||||||
|
if v.Department == "Director" {
|
||||||
|
return v.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMovieStreamingServices(id int, lang string) Country {
|
||||||
|
url := fmt.Sprintf("movie/%d/watch/providers", id)
|
||||||
|
_, body := request(url)
|
||||||
|
|
||||||
|
var responseObjekt WatchProviders
|
||||||
|
json.Unmarshal(body, &responseObjekt)
|
||||||
|
|
||||||
|
// Returning the available streaming services
|
||||||
|
return responseObjekt.Results[lang]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTmdbMovie(id string) Movie {
|
||||||
|
url := fmt.Sprintf("movie/%s", id)
|
||||||
|
_, body := request(url)
|
||||||
|
|
||||||
|
getMovieDirector(id)
|
||||||
|
|
||||||
|
var responseObjekt Movie
|
||||||
|
json.Unmarshal(body, &responseObjekt)
|
||||||
|
|
||||||
|
responseObjekt.Director = getMovieDirector(id)
|
||||||
|
|
||||||
|
return responseObjekt
|
||||||
|
}
|
||||||
|
|
||||||
|
type WatchResults struct {
|
||||||
|
Results []Provider `json:"results,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMovieProviders(region string) []Provider {
|
||||||
|
opts := map[string]string{"language": "en_US", "watch_region": region}
|
||||||
|
url := fmt.Sprintf("watch/providers/movie?%s", buildQuery(opts))
|
||||||
|
|
||||||
|
_, body := request(url)
|
||||||
|
|
||||||
|
var responseObjekt WatchResults
|
||||||
|
json.Unmarshal(body, &responseObjekt)
|
||||||
|
|
||||||
|
return responseObjekt.Results
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegionResults struct {
|
||||||
|
Results []Region `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Region struct {
|
||||||
|
Iso string `json:"iso_3166_1"`
|
||||||
|
EnglishName string `json:"english_name"`
|
||||||
|
NativeName string `json:"native_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAvailableRegions() []Region {
|
||||||
|
url := "watch/providers/regions"
|
||||||
|
|
||||||
|
_, body := request(url)
|
||||||
|
|
||||||
|
var responseObjekt RegionResults
|
||||||
|
json.Unmarshal(body, &responseObjekt)
|
||||||
|
|
||||||
|
return responseObjekt.Results
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
_ "embed"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dburl = "./data/data.db"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed schema.sql
|
||||||
|
var ddl string
|
||||||
|
|
||||||
|
func Init() *Queries {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite3", dburl)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.ExecContext(ctx, ddl); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return New(db)
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
-- name: ListMovies :many
|
||||||
|
SELECT * FROM movie
|
||||||
|
ORDER BY title ASC;
|
||||||
|
|
||||||
|
-- name: ListMovie :one
|
||||||
|
SELECT * FROM movie
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: ListWatchlist :many
|
||||||
|
SELECT * FROM movie
|
||||||
|
WHERE status = 0
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN streaming_services = '' OR streaming_services IS NULL THEN 1 ELSE 0 END,
|
||||||
|
title;
|
||||||
|
|
||||||
|
-- name: ListSettings :many
|
||||||
|
SELECT * FROM settings;
|
||||||
|
|
||||||
|
-- name: ListSetting :one
|
||||||
|
SELECT * FROM settings
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: ListSreamingServices :many
|
||||||
|
SELECT * FROM streaming_services;
|
||||||
|
|
||||||
|
-- name: CreateMovie :one
|
||||||
|
INSERT INTO movie (
|
||||||
|
title, original_title, imdbid, tmdbid, length, genre, streaming_services, director, year, watchcount, rating, status, owned, owned_type, ripped, review, overview
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: AddSreamingService :exec
|
||||||
|
INSERT INTO streaming_services (title) VALUES ( ? );
|
||||||
|
|
||||||
|
-- name: DeleteSreamingService :exec
|
||||||
|
DELETE FROM streaming_services
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: DeleteMovie :exec
|
||||||
|
DELETE FROM movie
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: ChangeMovieStatus :exec
|
||||||
|
UPDATE movie
|
||||||
|
SET status = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: ChangeMovieRating :exec
|
||||||
|
UPDATE movie
|
||||||
|
SET rating = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: ChangeMovieOwned :exec
|
||||||
|
UPDATE movie
|
||||||
|
SET owned = ?, owned_type = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: ChangeMovieStreamingServices :exec
|
||||||
|
UPDATE movie
|
||||||
|
SET streaming_services = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: ChangeMovieRipped :exec
|
||||||
|
UPDATE movie
|
||||||
|
SET ripped = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
|
-- name: ChangeSettingValue :exec
|
||||||
|
UPDATE settings
|
||||||
|
SET value = ?
|
||||||
|
WHERE id = ?;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS movie (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
original_title VARCHAR(255) NOT NULL,
|
||||||
|
imdbid VARCHAR(25) NOT NULL,
|
||||||
|
tmdbid INTEGER NOT NULL,
|
||||||
|
length INTEGER NOT NULL,
|
||||||
|
genre VARCHAR(255) NOT NULL,
|
||||||
|
streaming_services VARCHAR(255) NOT NULL,
|
||||||
|
director VARCHAR(255) NOT NULL,
|
||||||
|
year VARCHAR(10) NOT NULL,
|
||||||
|
watchcount INTEGER NOT NULL,
|
||||||
|
rating INTEGER NOT NULL,
|
||||||
|
status INTEGER NOT NULL,
|
||||||
|
owned INTEGER NOT NULL,
|
||||||
|
owned_type VARCHAR(255) NOT NULL,
|
||||||
|
ripped INTEGER NOT NULL,
|
||||||
|
review TEXT NOT NULL,
|
||||||
|
overview TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS genres (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title VARCHAR(30) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS streaming_services (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title VARCHAR(30) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
value VARCHAR(100) NOT NULL,
|
||||||
|
description VARCHAR(255) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO streaming_services
|
||||||
|
VALUES
|
||||||
|
("1", "Netflix"),
|
||||||
|
("2", "Disney Plus"),
|
||||||
|
("3", "Amazon Prime Video"),
|
||||||
|
("4", "Apple TV+");
|
||||||
|
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO settings
|
||||||
|
VALUES
|
||||||
|
("1", "HOME_GRID_VIEW", "false", "Grid or no grid on the Homepage"),
|
||||||
|
("2", "TMDB_API_KEY", "", "Your TMDB api key"),
|
||||||
|
("3", "REGION", "DE", "Your Region");
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package apikeychecker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/internal/database"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
DB database.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func configDefault(config ...Config) Config {
|
||||||
|
cfg := config[0]
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config Config) fiber.Handler {
|
||||||
|
return func(c fiber.Ctx) error {
|
||||||
|
setting, _ := config.DB.ListSetting(c, 2)
|
||||||
|
referer := string(c.Request().Header.Referer())
|
||||||
|
if setting.Value == "" && !strings.Contains(referer, "apikey") {
|
||||||
|
return c.Redirect().Status(fiber.StatusMovedPermanently).To("/apikey")
|
||||||
|
}
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/cmd/web"
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/internal/apis"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *FiberServer) Index(c fiber.Ctx) error {
|
||||||
|
movies, _ := s.db.ListMovies(c)
|
||||||
|
settings, _ := s.db.ListSettings(c)
|
||||||
|
return render(c, web.Show(movies, settings))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) Watchlist(c fiber.Ctx) error {
|
||||||
|
movies, _ := s.db.ListWatchlist(c)
|
||||||
|
return render(c, web.WatchList(movies))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) Settings(c fiber.Ctx) error {
|
||||||
|
ls, _ := s.db.ListSreamingServices(c)
|
||||||
|
_, ts := s.getAllStreamingServices(c)
|
||||||
|
slices.Sort(ts)
|
||||||
|
|
||||||
|
reg := apis.GetAvailableRegions()
|
||||||
|
selectedreg, _ := s.db.ListSetting(c, 3)
|
||||||
|
key, _ := s.db.ListSetting(c, 2)
|
||||||
|
|
||||||
|
config := web.SettingsConfig{
|
||||||
|
Providers: ls,
|
||||||
|
AvailableProviders: ts,
|
||||||
|
Regions: reg,
|
||||||
|
SelectedRegion: selectedreg.Value,
|
||||||
|
APIKey: key.Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(c, web.Settings(config))
|
||||||
|
}
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/jpeg"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/cmd/web"
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/internal/apis"
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/internal/database"
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/internal/middleware/apikeychecker"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
"github.com/gofiber/fiber/v3/log"
|
||||||
|
"github.com/gofiber/fiber/v3/middleware/static"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *FiberServer) RegisterFiberRoutes() {
|
||||||
|
s.App.Use("/apikey", s.apikeyinput)
|
||||||
|
|
||||||
|
s.App.Use("/assets", static.New("./assets", static.Config{
|
||||||
|
FS: web.Files,
|
||||||
|
Browse: false,
|
||||||
|
}))
|
||||||
|
|
||||||
|
s.App.Use("/movie/posters", static.New("./data/img"))
|
||||||
|
|
||||||
|
s.App.Use(
|
||||||
|
//basicauth.New(basicauth.Config{
|
||||||
|
// Users: map[string]string{
|
||||||
|
// // "doe" hashed using SHA-256
|
||||||
|
// "john": "{SHA256}eZ75KhGvkY4/t0HfQpNPO1aO0tk6wd908bjUGieTKm8=",
|
||||||
|
// },
|
||||||
|
//}),
|
||||||
|
//compress.New(compress.Config{
|
||||||
|
// Level: compress.LevelBestSpeed, // 1
|
||||||
|
//}),
|
||||||
|
apikeychecker.New(apikeychecker.Config{DB: *s.db}),
|
||||||
|
)
|
||||||
|
|
||||||
|
s.App.Get("/", s.Index)
|
||||||
|
s.App.Get("/watchlist", s.Watchlist)
|
||||||
|
s.App.Get("/settings", s.Settings)
|
||||||
|
s.App.Get("/movie/:id", s.movieDetails)
|
||||||
|
|
||||||
|
s.App.Get("/components/mediaTypeSelect/:id", s.mediaTypeSelect)
|
||||||
|
|
||||||
|
s.App.Post("/apis/tmdb/searchMovie", s.searchMovie)
|
||||||
|
s.App.Post("/db/addMovie/:id", s.addMovieToDb)
|
||||||
|
s.App.Post("/db/changeMovieStatus/:id.:status", s.changeMovieStatus)
|
||||||
|
s.App.Post("/db/changeMovieRating/:id.:rating", s.changeMovieRating)
|
||||||
|
s.App.Post("/db/changeMovieOwned/:id", s.changeMovieOwned)
|
||||||
|
s.App.Post("/db/changeMovieRipped/:id.:ripped", s.changeMovieRipped)
|
||||||
|
s.App.Post("/db/updateStreamingServices", s.updateMovieStreamingServices)
|
||||||
|
s.App.Post("/db/updateTableView", s.updateTableView)
|
||||||
|
s.App.Post("/db/updateRegion", s.updateRegion)
|
||||||
|
s.App.Post("/db/updateApiKey", s.updateApiKey)
|
||||||
|
s.App.Delete("/db/deleteMovie/:id", s.deleteMovie)
|
||||||
|
|
||||||
|
s.App.Post("/db/addStreamingService", s.addStreamingService)
|
||||||
|
s.App.Delete("/db/deleteStreamingService/:id", s.deleteStreamingService)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) apikeyinput(c fiber.Ctx) error {
|
||||||
|
key := c.FormValue("apikey")
|
||||||
|
apierror := ""
|
||||||
|
if key != "" {
|
||||||
|
url := "https://api.themoviedb.org/3/authentication"
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
req.Header.Add("accept", "application/json")
|
||||||
|
req.Header.Add("Authorization", "Bearer "+key)
|
||||||
|
res, _ := http.DefaultClient.Do(req)
|
||||||
|
|
||||||
|
if res.StatusCode == 200 {
|
||||||
|
s.db.ChangeSettingValue(c, database.ChangeSettingValueParams{
|
||||||
|
ID: 2,
|
||||||
|
Value: key,
|
||||||
|
})
|
||||||
|
c.Redirect().Status(fiber.StatusMovedPermanently).To("/")
|
||||||
|
}
|
||||||
|
apierror = "Could not certify API Read Access Token"
|
||||||
|
}
|
||||||
|
return render(c, web.Apikeyinput(apierror))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) updateApiKey(c fiber.Ctx) error {
|
||||||
|
key := c.FormValue("apikey")
|
||||||
|
s.db.ChangeSettingValue(c, database.ChangeSettingValueParams{
|
||||||
|
ID: 2,
|
||||||
|
Value: key,
|
||||||
|
})
|
||||||
|
|
||||||
|
config := web.SettingsConfig{
|
||||||
|
APIKey: key,
|
||||||
|
}
|
||||||
|
return render(c, web.ApiKey(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) movieDetails(c fiber.Ctx) error {
|
||||||
|
id, _ := strconv.ParseInt(c.Params("id"), 10, 64)
|
||||||
|
movie, _ := s.db.ListMovie(c, id)
|
||||||
|
return render(c, web.MovieDetails(movie))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) mediaTypeSelect(c fiber.Ctx) error {
|
||||||
|
id, _ := strconv.Atoi(c.Params("id"))
|
||||||
|
return render(c, web.MovieDetailsOwnedSelect(int8(id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) searchMovie(c fiber.Ctx) error {
|
||||||
|
res := apis.SearchTmdbMovie(c.FormValue("search"))
|
||||||
|
return render(c, web.SearchMovie(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) changeMovieStatus(c fiber.Ctx) error {
|
||||||
|
id, _ := strconv.ParseInt(c.Params("id"), 10, 64)
|
||||||
|
status, _ := strconv.ParseInt(c.Params("status"), 10, 64)
|
||||||
|
|
||||||
|
s.db.ChangeMovieStatus(c, database.ChangeMovieStatusParams{
|
||||||
|
ID: id,
|
||||||
|
Status: status,
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(c, web.MovieDetailsWatched(id, status))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) changeMovieRating(c fiber.Ctx) error {
|
||||||
|
id, _ := strconv.ParseInt(c.Params("id"), 10, 64)
|
||||||
|
rating, _ := strconv.ParseInt(c.Params("rating"), 10, 64)
|
||||||
|
|
||||||
|
s.db.ChangeMovieRating(c, database.ChangeMovieRatingParams{
|
||||||
|
ID: id,
|
||||||
|
Rating: rating,
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(c, web.MovieDetailsRating(id, rating))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) changeMovieOwned(c fiber.Ctx) error {
|
||||||
|
id, _ := strconv.ParseInt(c.Params("id"), 10, 64)
|
||||||
|
option := c.FormValue("option")
|
||||||
|
var owned int64 = 0
|
||||||
|
if option != "" {
|
||||||
|
owned = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
s.db.ChangeMovieOwned(c, database.ChangeMovieOwnedParams{
|
||||||
|
ID: id,
|
||||||
|
Owned: owned,
|
||||||
|
OwnedType: option,
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(c, web.MovieDetailsOwned(id, option))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) changeMovieRipped(c fiber.Ctx) error {
|
||||||
|
id, _ := strconv.ParseInt(c.Params("id"), 10, 64)
|
||||||
|
ripped, _ := strconv.ParseInt(c.Params("ripped"), 10, 64)
|
||||||
|
|
||||||
|
s.db.ChangeMovieRipped(c, database.ChangeMovieRippedParams{
|
||||||
|
ID: id,
|
||||||
|
Ripped: ripped,
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(c, web.MovieDetailsRipped(id, ripped))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) updateMovieStreamingServices(c fiber.Ctx) error {
|
||||||
|
movies, _ := s.db.ListMovies(c)
|
||||||
|
for _, v := range movies {
|
||||||
|
s.db.ChangeMovieStreamingServices(c, database.ChangeMovieStreamingServicesParams{
|
||||||
|
ID: v.ID,
|
||||||
|
StreamingServices: s.getStreamingServicesForMovie(c, v.Tmdbid),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
movies, _ = s.db.ListWatchlist(c)
|
||||||
|
return render(c, web.List(movies))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) updateTableView(c fiber.Ctx) error {
|
||||||
|
setting, _ := s.db.ListSetting(c, 1)
|
||||||
|
v := "false"
|
||||||
|
if setting.Value == "false" {
|
||||||
|
v = "true"
|
||||||
|
}
|
||||||
|
s.db.ChangeSettingValue(c, database.ChangeSettingValueParams{
|
||||||
|
Value: v,
|
||||||
|
ID: 1,
|
||||||
|
})
|
||||||
|
movies, _ := s.db.ListMovies(c)
|
||||||
|
if v == "true" {
|
||||||
|
return render(c, web.MovieTiles(movies))
|
||||||
|
}
|
||||||
|
return render(c, web.MovieList(movies))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) updateRegion(c fiber.Ctx) error {
|
||||||
|
region := c.FormValue("region")
|
||||||
|
s.db.ChangeSettingValue(c, database.ChangeSettingValueParams{
|
||||||
|
Value: region,
|
||||||
|
ID: 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
regions := apis.GetAvailableRegions()
|
||||||
|
|
||||||
|
config := web.SettingsConfig{
|
||||||
|
Regions: regions,
|
||||||
|
SelectedRegion: region,
|
||||||
|
}
|
||||||
|
return render(c, web.Region(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) addStreamingService(c fiber.Ctx) error {
|
||||||
|
title := c.FormValue("service")
|
||||||
|
|
||||||
|
s.db.AddSreamingService(c, title)
|
||||||
|
|
||||||
|
ls, _ := s.db.ListSreamingServices(c)
|
||||||
|
_, ts := s.getAllStreamingServices(c)
|
||||||
|
slices.Sort(ts)
|
||||||
|
|
||||||
|
config := web.SettingsConfig{
|
||||||
|
Providers: ls,
|
||||||
|
AvailableProviders: ts,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(c, web.ProviderTable(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) deleteStreamingService(c fiber.Ctx) error {
|
||||||
|
id := fiber.Params[int64](c, "id")
|
||||||
|
|
||||||
|
s.db.DeleteSreamingService(c, id)
|
||||||
|
|
||||||
|
ls, _ := s.db.ListSreamingServices(c)
|
||||||
|
_, ts := s.getAllStreamingServices(c)
|
||||||
|
slices.Sort(ts)
|
||||||
|
|
||||||
|
config := web.SettingsConfig{
|
||||||
|
Providers: ls,
|
||||||
|
AvailableProviders: ts,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(c, web.ProviderTable(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) deleteMovie(c fiber.Ctx) error {
|
||||||
|
id := fiber.Params[int64](c, "id")
|
||||||
|
|
||||||
|
s.db.DeleteMovie(c, id)
|
||||||
|
|
||||||
|
movies, _ := s.db.ListMovies(c)
|
||||||
|
return render(c, web.MovieList(movies))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) addMovieToDb(c fiber.Ctx) error {
|
||||||
|
|
||||||
|
res := apis.GetTmdbMovie(c.Params("id"))
|
||||||
|
referer := string(c.Request().Header.Referer())
|
||||||
|
|
||||||
|
// Combine Genres
|
||||||
|
genres := []string{}
|
||||||
|
for _, v := range res.Genres {
|
||||||
|
genres = append(genres, v.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the streaming providers
|
||||||
|
streamingservices := s.getStreamingServicesForMovie(c, int64(res.Id))
|
||||||
|
|
||||||
|
rating := 0
|
||||||
|
if c.FormValue("rating") != "" {
|
||||||
|
rating, _ = strconv.Atoi(c.FormValue("rating"))
|
||||||
|
}
|
||||||
|
watched := 0
|
||||||
|
if c.FormValue("watched") == "on" {
|
||||||
|
watched = 1
|
||||||
|
}
|
||||||
|
watchcount := 0
|
||||||
|
if c.FormValue("watchcount") != "" {
|
||||||
|
watchcount, _ = strconv.Atoi(c.FormValue("watchcount"))
|
||||||
|
}
|
||||||
|
owned := 0
|
||||||
|
if c.FormValue("owned") == "on" {
|
||||||
|
owned = 1
|
||||||
|
}
|
||||||
|
ownedType := ""
|
||||||
|
if c.FormValue("version") != "" {
|
||||||
|
ownedType = c.FormValue("version")
|
||||||
|
}
|
||||||
|
ripped := 0
|
||||||
|
if c.FormValue("ripped") == "on" {
|
||||||
|
ripped = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
movie, err := s.db.CreateMovie(c, database.CreateMovieParams{
|
||||||
|
Title: res.Title,
|
||||||
|
OriginalTitle: res.OriginalTitle,
|
||||||
|
Imdbid: res.ImdbID,
|
||||||
|
Tmdbid: int64(res.Id),
|
||||||
|
Length: int64(res.Lenght),
|
||||||
|
Genre: strings.Join(genres, ", "),
|
||||||
|
StreamingServices: streamingservices,
|
||||||
|
Director: res.Director,
|
||||||
|
Year: res.ReleaseDate,
|
||||||
|
Watchcount: int64(watchcount),
|
||||||
|
Rating: int64(rating),
|
||||||
|
Status: int64(watched),
|
||||||
|
Owned: int64(owned),
|
||||||
|
OwnedType: ownedType,
|
||||||
|
Ripped: int64(ripped),
|
||||||
|
Review: "",
|
||||||
|
Overview: res.Overview,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the movie poster
|
||||||
|
resp, err := http.Get("https://image.tmdb.org/t/p/original" + res.PosterPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
tmp, err := jpeg.Decode(resp.Body)
|
||||||
|
if err == nil {
|
||||||
|
path := fmt.Sprintf("./data/img/%d.jpg", movie.ID)
|
||||||
|
log.Info(path)
|
||||||
|
outputFile, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jpeg.Encode(outputFile, tmp, nil)
|
||||||
|
|
||||||
|
outputFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(referer, "watchlist") {
|
||||||
|
movies, _ := s.db.ListWatchlist(c)
|
||||||
|
return render(c, web.List(movies))
|
||||||
|
}
|
||||||
|
|
||||||
|
gridSetting, _ := s.db.ListSetting(c, 1)
|
||||||
|
movies, _ := s.db.ListMovies(c)
|
||||||
|
componet := web.MovieList(movies)
|
||||||
|
|
||||||
|
if gridSetting.Value == "true" {
|
||||||
|
componet = web.MovieTiles(movies)
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(c, componet)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/internal/database"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FiberServer struct {
|
||||||
|
*fiber.App
|
||||||
|
|
||||||
|
db *database.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *FiberServer {
|
||||||
|
server := &FiberServer{
|
||||||
|
App: fiber.New(fiber.Config{
|
||||||
|
ServerHeader: "PocketMovie",
|
||||||
|
AppName: "PocketMovie",
|
||||||
|
}),
|
||||||
|
db: database.Init(),
|
||||||
|
}
|
||||||
|
return server
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.itsigo.dev/istigo/pocketmovie/internal/apis"
|
||||||
|
"github.com/a-h/templ"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
"github.com/gofiber/fiber/v3/middleware/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
var store = session.New()
|
||||||
|
|
||||||
|
func render(c fiber.Ctx, component templ.Component) error {
|
||||||
|
// or templ wil bork...
|
||||||
|
c.Set("Content-type", "text/html")
|
||||||
|
return component.Render(c, c.Response().BodyWriter())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the Streaming services for a movie as a string (service 1, service 2, ...)
|
||||||
|
func (s *FiberServer) getStreamingServicesForMovie(c fiber.Ctx, id int64) string {
|
||||||
|
// Get the streaming providers
|
||||||
|
dbServicesSlice := s.getLocalStreamingServices(c)
|
||||||
|
|
||||||
|
region, _ := s.db.ListSetting(c, 3)
|
||||||
|
|
||||||
|
flatrate := apis.GetMovieStreamingServices(int(id), region.Value).Flatrate
|
||||||
|
free := apis.GetMovieStreamingServices(int(id), region.Value).Free
|
||||||
|
flatrate = append(flatrate, free...)
|
||||||
|
streaningservices := []string{}
|
||||||
|
for _, v := range flatrate {
|
||||||
|
if slices.Contains(dbServicesSlice, v.ProviderName) {
|
||||||
|
streaningservices = append(streaningservices, v.ProviderName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(streaningservices, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) getAllStreamingServices(c fiber.Ctx) ([]string, []string) {
|
||||||
|
ls := s.getLocalStreamingServices(c)
|
||||||
|
ts := s.getTmdbStreamingServices(c, ls)
|
||||||
|
return ls, ts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) getLocalStreamingServices(c fiber.Ctx) []string {
|
||||||
|
services, _ := s.db.ListSreamingServices(c)
|
||||||
|
|
||||||
|
sl := []string{}
|
||||||
|
for _, p := range services {
|
||||||
|
sl = append(sl, p.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FiberServer) getTmdbStreamingServices(c fiber.Ctx, localservices []string) []string {
|
||||||
|
region, _ := s.db.ListSetting(c, 3)
|
||||||
|
services := apis.GetMovieProviders(region.Value)
|
||||||
|
|
||||||
|
sl := []string{}
|
||||||
|
|
||||||
|
for _, p := range services {
|
||||||
|
if !slices.Contains(localservices, p.ProviderName) {
|
||||||
|
sl = append(sl, p.ProviderName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sl
|
||||||
|
}
|
||||||
Reference in new issue
Block a user