CHANGE: Switched The Interface From HTML to JSON API
Build, Push and Deploy CMS / build-push-deploy (push) Successful in 1m53s
Build, Push and Deploy CMS / build-push-deploy (push) Successful in 1m53s
This commit is contained in:
@@ -14,7 +14,6 @@ RUN apk add --no-cache ca-certificates
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /out/cms ./cms
|
||||
COPY static ./static
|
||||
|
||||
EXPOSE 8081
|
||||
ENTRYPOINT ["./cms"]
|
||||
|
||||
+1
-2
@@ -3,13 +3,11 @@ module thamanyah/cms/v2
|
||||
go 1.25.12
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.3.1020
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.36
|
||||
github.com/aws/aws-sdk-go-v2/service/mediaconvert v1.97.2
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.107.1
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lib/pq v1.12.3
|
||||
)
|
||||
|
||||
@@ -29,4 +27,5 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.45.5 // indirect
|
||||
github.com/aws/smithy-go v1.27.7 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
)
|
||||
|
||||
@@ -2,8 +2,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
|
||||
github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.5 h1:yKT5GYnFWhuDo+DqKvE5ZPwVn3RjC4MAeBtZGlh6AVM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.5/go.mod h1:wZjAJppCntyOGgVSmgVTfDyRJK5PHOasO6Wsy8U7Axk=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.17 h1:mn+Vxb9zgz/FE/yDTcFim3DZ1qpcrxR+qBQkBrl6bzA=
|
||||
@@ -68,10 +66,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||
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/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"thamanyah/cms/v2/internal/views"
|
||||
)
|
||||
|
||||
func Home(w http.ResponseWriter, r *http.Request) {
|
||||
views.Home().Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func Hello(w http.ResponseWriter, r *http.Request) {
|
||||
views.Greeting("Fahd Shalhoub").Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func Health(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Write([]byte("ok"))
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, body any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
if err := json.NewEncoder(w).Encode(body); err != nil {
|
||||
log.Printf("Something Went Wrong Writing The Response: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeJSONError(w http.ResponseWriter, status int, message string) {
|
||||
writeJSON(w, status, map[string]string{"error": message})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"thamanyah/cms/v2/internal/services"
|
||||
"thamanyah/cms/v2/internal/views"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -20,15 +19,22 @@ const (
|
||||
maxJSONBodySize = 1 << 20 // 1 MiB
|
||||
)
|
||||
|
||||
func NewVideo(w http.ResponseWriter, r *http.Request) {
|
||||
type categoriesResponse struct {
|
||||
Categories []string `json:"categories"`
|
||||
}
|
||||
|
||||
func ListCategories(w http.ResponseWriter, r *http.Request) {
|
||||
categories, err := services.DB.ListCategories(r.Context())
|
||||
if err != nil {
|
||||
log.Printf("Something Went Wrong Loading Categories: %s", err)
|
||||
http.Error(w, "Something Went Wrong Loading Categories", http.StatusInternalServerError)
|
||||
writeJSONError(w, http.StatusInternalServerError, "Something Went Wrong Loading Categories")
|
||||
return
|
||||
}
|
||||
|
||||
views.VideoUpload(categories).Render(r.Context(), w)
|
||||
if categories == nil {
|
||||
categories = []string{}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, categoriesResponse{Categories: categories})
|
||||
}
|
||||
|
||||
type presignRequest struct {
|
||||
@@ -68,8 +74,7 @@ func PresignVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(presignResponse{UploadURL: presignedURL, Key: key})
|
||||
writeJSON(w, http.StatusOK, presignResponse{UploadURL: presignedURL, Key: key})
|
||||
}
|
||||
|
||||
type completeRequest struct {
|
||||
@@ -81,24 +86,39 @@ type completeRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type videoResponse struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Categories []string `json:"categories"`
|
||||
Tags string `json:"tags"`
|
||||
FileName string `json:"fileName"`
|
||||
StorageKey string `json:"storageKey"`
|
||||
MediaConvertJobID string `json:"mediaConvertJobId"`
|
||||
Status string `json:"status"`
|
||||
SizeBytes int64 `json:"sizeBytes"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxJSONBodySize)
|
||||
|
||||
var req completeRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
renderUploadError(w, r, "Invalid request.")
|
||||
writeJSONError(w, http.StatusBadRequest, "Invalid request.")
|
||||
return
|
||||
}
|
||||
|
||||
title := strings.TrimSpace(req.Title)
|
||||
if title == "" {
|
||||
renderUploadError(w, r, "Title is required.")
|
||||
writeJSONError(w, http.StatusUnprocessableEntity, "Title is required.")
|
||||
return
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(req.Key)
|
||||
if key == "" || !strings.HasPrefix(key, videoUploadPrefix+"/") {
|
||||
renderUploadError(w, r, "A video file is required.")
|
||||
writeJSONError(w, http.StatusUnprocessableEntity, "A video file is required.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -112,11 +132,11 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
jobID, err := services.MediaConvertClient.QueueEncodingJob(r.Context(), key)
|
||||
if err != nil {
|
||||
log.Printf("Something Went Wrong On Creation Of Transcoding Job: %s", err)
|
||||
renderUploadError(w, r, "Something Went Wrong On Creation Of Transcoding Job")
|
||||
writeJSONError(w, http.StatusInternalServerError, "Something Went Wrong On Creation Of Transcoding Job")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = services.DB.CreateVideo(r.Context(), services.Video{
|
||||
video, err := services.DB.CreateVideo(r.Context(), services.Video{
|
||||
Title: title,
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
Categories: categories,
|
||||
@@ -129,32 +149,24 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Something Went Wrong Saving The Video Record: %s", err)
|
||||
renderUploadError(w, r, "Something Went Wrong Saving The Video Record")
|
||||
writeJSONError(w, http.StatusInternalServerError, "Something Went Wrong Saving The Video Record")
|
||||
return
|
||||
}
|
||||
|
||||
views.VideoUploadSuccess(views.VideoMetadata{
|
||||
Title: title,
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
Categories: categories,
|
||||
Tags: strings.TrimSpace(req.Tags),
|
||||
FileName: strings.TrimSpace(req.FileName),
|
||||
JobID: jobID,
|
||||
StoredAs: key,
|
||||
SizeBytes: 60,
|
||||
}).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func writeJSONError(w http.ResponseWriter, status int, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": message})
|
||||
}
|
||||
|
||||
func renderUploadError(w http.ResponseWriter, r *http.Request, message string) {
|
||||
fmt.Println("Error Ocurred: " + message)
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
views.VideoUploadError(message).Render(r.Context(), w)
|
||||
writeJSON(w, http.StatusCreated, videoResponse{
|
||||
ID: video.ID,
|
||||
Title: video.Title,
|
||||
Description: video.Description,
|
||||
Categories: video.Categories,
|
||||
Tags: video.Tags,
|
||||
FileName: video.FileName,
|
||||
StorageKey: video.StorageKey,
|
||||
MediaConvertJobID: video.MediaConvertJobID,
|
||||
Status: video.Status,
|
||||
SizeBytes: video.SizeBytes,
|
||||
CreatedAt: video.CreatedAt,
|
||||
UpdatedAt: video.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
func randomFilename(original string) (string, error) {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package views
|
||||
|
||||
import "thamanyah/cms/v2/internal/views/layouts"
|
||||
|
||||
templ Home() {
|
||||
@layouts.Layout("Thamanyah CMS") {
|
||||
<h1>Thamanyah CMS</h1>
|
||||
<p>htmx is wired up and ready to go.</p>
|
||||
<button hx-get="/api/hello" hx-target="#greeting" hx-swap="innerHTML">
|
||||
Say hello
|
||||
</button>
|
||||
<div id="greeting"></div>
|
||||
<p><a href="/videos/new">Upload a video</a></p>
|
||||
}
|
||||
}
|
||||
|
||||
templ Greeting(name string) {
|
||||
<p>Hello, { name }! This fragment was swapped in by htmx.</p>
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.1020
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "thamanyah/cms/v2/internal/views/layouts"
|
||||
|
||||
func Home() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1>Thamanyah CMS</h1><p>htmx is wired up and ready to go.</p><button hx-get=\"/api/hello\" hx-target=\"#greeting\" hx-swap=\"innerHTML\">Say hello</button><div id=\"greeting\"></div><p><a href=\"/videos/new\">Upload a video</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = layouts.Layout("Thamanyah CMS").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Greeting(name string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p>Hello, ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/home.templ`, Line: 18, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "! This fragment was swapped in by htmx.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
@@ -1,26 +0,0 @@
|
||||
package layouts
|
||||
|
||||
templ Layout(title string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>{ title }</title>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<link rel="stylesheet" href="/static/css/app.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
{ children... }
|
||||
</main>
|
||||
<script>
|
||||
document.body.addEventListener('htmx:beforeSwap', function (evt) {
|
||||
if (evt.detail.xhr.status === 422) {
|
||||
evt.detail.shouldSwap = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.1020
|
||||
package layouts
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
func Layout(title string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/layouts/layout.templ`, Line: 9, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><script src=\"https://unpkg.com/htmx.org@2.0.4\"></script><link rel=\"stylesheet\" href=\"/static/css/app.css\"></head><body><main class=\"container\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</main><script>\n\t\t\t\tdocument.body.addEventListener('htmx:beforeSwap', function (evt) {\n\t\t\t\t\tif (evt.detail.xhr.status === 422) {\n\t\t\t\t\t\tevt.detail.shouldSwap = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t</script></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
@@ -1,34 +0,0 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VideoMetadata struct {
|
||||
Title string
|
||||
Description string
|
||||
Categories []string
|
||||
Tags string
|
||||
FileName string
|
||||
StoredAs string
|
||||
JobID string
|
||||
SizeBytes int64
|
||||
}
|
||||
|
||||
func joinCategories(categories []string) string {
|
||||
return strings.Join(categories, ", ")
|
||||
}
|
||||
|
||||
func formatSize(n int64) string {
|
||||
const unit = 1024
|
||||
if n < unit {
|
||||
return fmt.Sprintf("%d B", n)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for m := n / unit; m >= unit; m /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %ciB", float64(n)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package views
|
||||
|
||||
import "thamanyah/cms/v2/internal/views/layouts"
|
||||
|
||||
templ VideoUpload(categories []string) {
|
||||
@layouts.Layout("Upload Video · Thamanyah CMS") {
|
||||
<h1>Upload a video</h1>
|
||||
<p>Provide the video file along with its metadata.</p>
|
||||
<form id="upload-form">
|
||||
<div class="field">
|
||||
<label for="title">Title</label>
|
||||
<input type="text" id="title" name="title" required/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="description">Description</label>
|
||||
<textarea id="description" name="description" rows="4"></textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="category">Categories</label>
|
||||
<select id="category" name="category" multiple>
|
||||
for _, category := range categories {
|
||||
<option value={ category }>{ category }</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="tags">Tags</label>
|
||||
<input type="text" id="tags" name="tags" placeholder="comma, separated, tags"/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="video">Video file</label>
|
||||
<input type="file" id="video" name="video" accept="video/*" required/>
|
||||
</div>
|
||||
<progress id="upload-progress" value="0" max="100"></progress>
|
||||
<button type="submit">Upload</button>
|
||||
</form>
|
||||
<div id="upload-result"></div>
|
||||
<script>
|
||||
document.getElementById('upload-form').addEventListener('submit', function (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
var form = evt.target;
|
||||
var fileInput = document.getElementById('video');
|
||||
var file = fileInput.files[0];
|
||||
var resultEl = document.getElementById('upload-result');
|
||||
var progressEl = document.getElementById('upload-progress');
|
||||
var button = form.querySelector('button[type="submit"]');
|
||||
|
||||
function showError(message) {
|
||||
resultEl.innerHTML = '<div class="result result-error"><p>' + message + '</p></div>';
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
showError('A video file is required.');
|
||||
return;
|
||||
}
|
||||
|
||||
var contentType = file.type || 'application/octet-stream';
|
||||
|
||||
button.disabled = true;
|
||||
progressEl.value = 0;
|
||||
resultEl.innerHTML = '';
|
||||
|
||||
fetch('/videos/presign', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ fileName: file.name, contentType: contentType })
|
||||
}).then(function (res) {
|
||||
return res.json().then(function (body) {
|
||||
if (!res.ok) {
|
||||
throw new Error(body.error || 'Could not prepare upload.');
|
||||
}
|
||||
return body;
|
||||
});
|
||||
}).then(function (presigned) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('PUT', presigned.uploadUrl);
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.upload.addEventListener('progress', function (e) {
|
||||
if (e.lengthComputable) {
|
||||
progressEl.value = Math.round((e.loaded / e.total) * 100);
|
||||
}
|
||||
});
|
||||
xhr.onload = function () {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(presigned.key);
|
||||
} else {
|
||||
reject(new Error('Upload to storage failed.'));
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
reject(new Error('Upload to storage failed.'));
|
||||
};
|
||||
xhr.send(file);
|
||||
});
|
||||
}).then(function (key) {
|
||||
return fetch('/videos', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title: form.title.value.trim(),
|
||||
description: form.description.value.trim(),
|
||||
categories: Array.prototype.map.call(form.category.selectedOptions, function (o) { return o.value; }),
|
||||
tags: form.tags.value.trim(),
|
||||
fileName: file.name,
|
||||
key: key
|
||||
})
|
||||
});
|
||||
}).then(function (res) {
|
||||
return res.text().then(function (html) {
|
||||
resultEl.innerHTML = html;
|
||||
});
|
||||
}).catch(function (err) {
|
||||
showError(err.message);
|
||||
}).finally(function () {
|
||||
button.disabled = false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
|
||||
templ VideoUploadSuccess(v VideoMetadata) {
|
||||
<div class="result result-success">
|
||||
<h2>Upload complete</h2>
|
||||
<dl>
|
||||
<dt>Title</dt>
|
||||
<dd>{ v.Title }</dd>
|
||||
if v.Description != "" {
|
||||
<dt>Description</dt>
|
||||
<dd>{ v.Description }</dd>
|
||||
}
|
||||
<dt>Categories</dt>
|
||||
<dd>{ joinCategories(v.Categories) }</dd>
|
||||
<dt>Enqueue Job ID</dt>
|
||||
<dd>{ v.JobID }</dd>
|
||||
if v.Tags != "" {
|
||||
<dt>Tags</dt>
|
||||
<dd>{ v.Tags }</dd>
|
||||
}
|
||||
<dt>File</dt>
|
||||
<dd>
|
||||
{ v.FileName } ({ formatSize(v.SizeBytes) })
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ VideoUploadError(message string) {
|
||||
<div class="result result-error">
|
||||
<p style="color:green">{ message }</p>
|
||||
</div>
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.1020
|
||||
package views
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "thamanyah/cms/v2/internal/views/layouts"
|
||||
|
||||
func VideoUpload(categories []string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1>Upload a video</h1><p>Provide the video file along with its metadata.</p><form id=\"upload-form\"><div class=\"field\"><label for=\"title\">Title</label> <input type=\"text\" id=\"title\" name=\"title\" required></div><div class=\"field\"><label for=\"description\">Description</label> <textarea id=\"description\" name=\"description\" rows=\"4\"></textarea></div><div class=\"field\"><label for=\"category\">Categories</label> <select id=\"category\" name=\"category\" multiple>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, category := range categories {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<option value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(category)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 22, Col: 30}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(category)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 22, Col: 43}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</select></div><div class=\"field\"><label for=\"tags\">Tags</label> <input type=\"text\" id=\"tags\" name=\"tags\" placeholder=\"comma, separated, tags\"></div><div class=\"field\"><label for=\"video\">Video file</label> <input type=\"file\" id=\"video\" name=\"video\" accept=\"video/*\" required></div><progress id=\"upload-progress\" value=\"0\" max=\"100\"></progress> <button type=\"submit\">Upload</button></form><div id=\"upload-result\"></div><script>\n\t\t\t\tdocument.getElementById('upload-form').addEventListener('submit', function (evt) {\n\t\t\t\t\tevt.preventDefault();\n\n\t\t\t\t\tvar form = evt.target;\n\t\t\t\t\tvar fileInput = document.getElementById('video');\n\t\t\t\t\tvar file = fileInput.files[0];\n\t\t\t\t\tvar resultEl = document.getElementById('upload-result');\n\t\t\t\t\tvar progressEl = document.getElementById('upload-progress');\n\t\t\t\t\tvar button = form.querySelector('button[type=\"submit\"]');\n\n\t\t\t\t\tfunction showError(message) {\n\t\t\t\t\t\tresultEl.innerHTML = '<div class=\"result result-error\"><p>' + message + '</p></div>';\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!file) {\n\t\t\t\t\t\tshowError('A video file is required.');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar contentType = file.type || 'application/octet-stream';\n\n\t\t\t\t\tbutton.disabled = true;\n\t\t\t\t\tprogressEl.value = 0;\n\t\t\t\t\tresultEl.innerHTML = '';\n\n\t\t\t\t\tfetch('/videos/presign', {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify({ fileName: file.name, contentType: contentType })\n\t\t\t\t\t}).then(function (res) {\n\t\t\t\t\t\treturn res.json().then(function (body) {\n\t\t\t\t\t\t\tif (!res.ok) {\n\t\t\t\t\t\t\t\tthrow new Error(body.error || 'Could not prepare upload.');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn body;\n\t\t\t\t\t\t});\n\t\t\t\t\t}).then(function (presigned) {\n\t\t\t\t\t\treturn new Promise(function (resolve, reject) {\n\t\t\t\t\t\t\tvar xhr = new XMLHttpRequest();\n\t\t\t\t\t\t\txhr.open('PUT', presigned.uploadUrl);\n\t\t\t\t\t\t\txhr.setRequestHeader('Content-Type', contentType);\n\t\t\t\t\t\t\txhr.upload.addEventListener('progress', function (e) {\n\t\t\t\t\t\t\t\tif (e.lengthComputable) {\n\t\t\t\t\t\t\t\t\tprogressEl.value = Math.round((e.loaded / e.total) * 100);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\txhr.onload = function () {\n\t\t\t\t\t\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\t\t\t\t\t\tresolve(presigned.key);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\treject(new Error('Upload to storage failed.'));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\txhr.onerror = function () {\n\t\t\t\t\t\t\t\treject(new Error('Upload to storage failed.'));\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\txhr.send(file);\n\t\t\t\t\t\t});\n\t\t\t\t\t}).then(function (key) {\n\t\t\t\t\t\treturn fetch('/videos', {\n\t\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t\t\ttitle: form.title.value.trim(),\n\t\t\t\t\t\t\t\tdescription: form.description.value.trim(),\n\t\t\t\t\t\t\t\tcategories: Array.prototype.map.call(form.category.selectedOptions, function (o) { return o.value; }),\n\t\t\t\t\t\t\t\ttags: form.tags.value.trim(),\n\t\t\t\t\t\t\t\tfileName: file.name,\n\t\t\t\t\t\t\t\tkey: key\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t});\n\t\t\t\t\t}).then(function (res) {\n\t\t\t\t\t\treturn res.text().then(function (html) {\n\t\t\t\t\t\t\tresultEl.innerHTML = html;\n\t\t\t\t\t\t});\n\t\t\t\t\t}).catch(function (err) {\n\t\t\t\t\t\tshowError(err.message);\n\t\t\t\t\t}).finally(function () {\n\t\t\t\t\t\tbutton.disabled = false;\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t</script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = layouts.Layout("Upload Video · Thamanyah CMS").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func VideoUploadSuccess(v VideoMetadata) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var5 == nil {
|
||||
templ_7745c5c3_Var5 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"result result-success\"><h2>Upload complete</h2><dl><dt>Title</dt><dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(v.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 129, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if v.Description != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<dt>Description</dt><dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(v.Description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 132, Col: 23}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<dt>Categories</dt><dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(joinCategories(v.Categories))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 135, Col: 37}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</dd><dt>Enqueue Job ID</dt><dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(v.JobID)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 137, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if v.Tags != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<dt>Tags</dt><dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(v.Tags)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 140, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<dt>File</dt><dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(v.FileName)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 144, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " (")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(formatSize(v.SizeBytes))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 144, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, ")</dd></dl></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func VideoUploadError(message string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var13 == nil {
|
||||
templ_7745c5c3_Var13 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div class=\"result result-error\"><p style=\"color:green\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(message)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/video.templ`, Line: 152, Col: 34}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</p></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
+3
-6
@@ -78,13 +78,10 @@ func runServer() {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
mux.HandleFunc("GET /", handlers.Home)
|
||||
mux.HandleFunc("GET /api/hello", handlers.Hello)
|
||||
mux.HandleFunc("GET /health", handlers.Health)
|
||||
mux.HandleFunc("GET /videos/new", handlers.NewVideo)
|
||||
mux.HandleFunc("POST /videos/presign", handlers.PresignVideoUpload)
|
||||
mux.HandleFunc("POST /videos", handlers.CompleteVideoUpload)
|
||||
mux.HandleFunc("GET /api/categories", handlers.ListCategories)
|
||||
mux.HandleFunc("POST /api/videos/presign", handlers.PresignVideoUpload)
|
||||
mux.HandleFunc("POST /api/videos", handlers.CompleteVideoUpload)
|
||||
|
||||
addr := ":8081"
|
||||
log.Printf("listening on %s", addr)
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 40rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.field label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.field input,
|
||||
.field textarea,
|
||||
.field select {
|
||||
padding: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
progress {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.htmx-indicator {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in;
|
||||
}
|
||||
|
||||
.htmx-request.htmx-indicator,
|
||||
.htmx-request .htmx-indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.result-success {
|
||||
border-color: #2f9e44;
|
||||
}
|
||||
|
||||
.result-error {
|
||||
border-color: #e03131;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0.75rem 0;
|
||||
}
|
||||
Reference in New Issue
Block a user