10-WebsiteModel #13
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -28,3 +28,6 @@ go.work | |||||||
| .air.toml | .air.toml | ||||||
| /tmp | /tmp | ||||||
| tls/ | tls/ | ||||||
|  | test.db.old | ||||||
|  | .gitignore | ||||||
|  | .nvim/session | ||||||
|  | |||||||
| @ -2,10 +2,14 @@ package main | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 
 | ||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" | 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (app *application) home(w http.ResponseWriter, r *http.Request) { | func (app *application) home(w http.ResponseWriter, r *http.Request) { | ||||||
|     data := app.newCommonData(r) | 	if app.isAuthenticated(r) { | ||||||
|     views.Home("Home", data).Render(r.Context(), w) | 		http.Redirect(w, r, "/websites", http.StatusSeeOther) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	views.Home("Home", app.newCommonData(r)).Render(r.Context(), w) | ||||||
| } | } | ||||||
|  | |||||||
| @ -11,35 +11,6 @@ import ( | |||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" | 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (app *application) getGuestbookCreate(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	data := app.newCommonData(r) |  | ||||||
| 	views.GuestbookCreate("New Guestbook", data).Render(r.Context(), w) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (app *application) postGuestbookCreate(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId") |  | ||||||
| 	err := r.ParseForm() |  | ||||||
| 	if err != nil { |  | ||||||
| 		app.serverError(w, r, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	siteUrl := r.Form.Get("siteurl") |  | ||||||
| 	shortId := app.createShortId() |  | ||||||
| 	_, err = app.guestbooks.Insert(shortId, siteUrl, userId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		app.serverError(w, r, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	app.sessionManager.Put(r.Context(), "flash", "Guestbook successfully created!") |  | ||||||
| 	if r.Header.Get("HX-Request") == "true" { |  | ||||||
| 		w.Header().Add("HX-Trigger", "newGuestbook") |  | ||||||
| 		data := app.newTemplateData(r) |  | ||||||
| 		app.renderHTMX(w, r, http.StatusOK, "guestbookcreatebutton.part.html", data) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	http.Redirect(w, r, fmt.Sprintf("/guestbooks/%s", shortIdToSlug(shortId)), http.StatusSeeOther) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (app *application) getGuestbookList(w http.ResponseWriter, r *http.Request) { | func (app *application) getGuestbookList(w http.ResponseWriter, r *http.Request) { | ||||||
| 	userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId") | 	userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId") | ||||||
| 	guestbooks, err := app.guestbooks.GetAll(userId) | 	guestbooks, err := app.guestbooks.GetAll(userId) | ||||||
| @ -53,7 +24,7 @@ func (app *application) getGuestbookList(w http.ResponseWriter, r *http.Request) | |||||||
| 
 | 
 | ||||||
| func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) { | func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) { | ||||||
| 	slug := r.PathValue("id") | 	slug := r.PathValue("id") | ||||||
| 	guestbook, err := app.guestbooks.Get(slugToShortId(slug)) | 	website, err := app.websites.Get(slugToShortId(slug)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { | 		if errors.Is(err, models.ErrNoRecord) { | ||||||
| 			http.NotFound(w, r) | 			http.NotFound(w, r) | ||||||
| @ -62,42 +33,18 @@ func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) { | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	comments, err := app.guestbookComments.GetAll(guestbook.ID) | 	comments, err := app.guestbookComments.GetAll(website.Guestbook.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		app.serverError(w, r, err) | 		app.serverError(w, r, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	data := app.newCommonData(r) | 	data := app.newCommonData(r) | ||||||
| 	views.GuestbookView("Guestbook", data, guestbook, comments, forms.CommentCreateForm{}).Render(r.Context(), w) | 	views.GuestbookView("Guestbook", data, website, website.Guestbook, comments, forms.CommentCreateForm{}).Render(r.Context(), w) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (app *application) getGuestbookDashboard(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	slug := r.PathValue("id") |  | ||||||
| 	guestbook, err := app.guestbooks.Get(slugToShortId(slug)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	user := app.getCurrentUser(r) |  | ||||||
| 	if user.ID != guestbook.UserId { |  | ||||||
| 		app.clientError(w, http.StatusUnauthorized) |  | ||||||
| 	} |  | ||||||
| 	comments, err := app.guestbookComments.GetAll(guestbook.ID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		app.serverError(w, r, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	data := app.newCommonData(r) |  | ||||||
| 	views.GuestbookDashboardView("Guestbook", data, guestbook, comments).Render(r.Context(), w) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Request) { | func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Request) { | ||||||
| 	slug := r.PathValue("id") | 	slug := r.PathValue("id") | ||||||
| 	guestbook, err := app.guestbooks.Get(slugToShortId(slug)) | 	website, err := app.websites.Get(slugToShortId(slug)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { | 		if errors.Is(err, models.ErrNoRecord) { | ||||||
| 			http.NotFound(w, r) | 			http.NotFound(w, r) | ||||||
| @ -106,19 +53,19 @@ func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Requ | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	comments, err := app.guestbookComments.GetAll(guestbook.ID) | 	comments, err := app.guestbookComments.GetAll(website.Guestbook.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		app.serverError(w, r, err) | 		app.serverError(w, r, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	data := app.newCommonData(r) | 	data := app.newCommonData(r) | ||||||
| 	views.GuestbookDashboardCommentsView("Comments", data, guestbook, comments).Render(r.Context(), w) | 	views.GuestbookDashboardCommentsView("Comments", data, website, website.Guestbook, comments).Render(r.Context(), w) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) { | func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) { | ||||||
| 	// TODO: This will be the embeddable form | 	// TODO: This will be the embeddable form | ||||||
| 	slug := r.PathValue("id") | 	slug := r.PathValue("id") | ||||||
| 	guestbook, err := app.guestbooks.Get(slugToShortId(slug)) | 	website, err := app.websites.Get(slugToShortId(slug)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { | 		if errors.Is(err, models.ErrNoRecord) { | ||||||
| 			http.NotFound(w, r) | 			http.NotFound(w, r) | ||||||
| @ -127,15 +74,15 @@ func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	data := app.newTemplateData(r) | 
 | ||||||
| 	data.Guestbook = guestbook | 	data := app.newCommonData(r) | ||||||
| 	data.Form = forms.CommentCreateForm{} | 	form := forms.CommentCreateForm{} | ||||||
| 	app.render(w, r, http.StatusOK, "commentcreate.view.tmpl.html", data) | 	views.CreateGuestbookComment("New Comment", data, website, website.Guestbook, form).Render(r.Context(), w) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) { | func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) { | ||||||
| 	guestbookSlug := r.PathValue("id") | 	slug := r.PathValue("id") | ||||||
| 	guestbook, err := app.guestbooks.Get(slugToShortId(guestbookSlug)) | 	website, err := app.websites.Get(slugToShortId(slug)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { | 		if errors.Is(err, models.ErrNoRecord) { | ||||||
| 			http.NotFound(w, r) | 			http.NotFound(w, r) | ||||||
| @ -161,21 +108,24 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt | |||||||
| 	form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank") | 	form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank") | ||||||
| 
 | 
 | ||||||
| 	if !form.Valid() { | 	if !form.Valid() { | ||||||
| 		data := app.newTemplateData(r) | 		comments, err := app.guestbookComments.GetAll(website.Guestbook.ID) | ||||||
| 		data.Guestbook = guestbook | 		if err != nil { | ||||||
| 		data.Form = form | 			app.serverError(w, r, err) | ||||||
| 		app.render(w, r, http.StatusUnprocessableEntity, "commentcreate.view.tmpl.html", data) | 			return | ||||||
|  | 		} | ||||||
|  | 		data := app.newCommonData(r) | ||||||
|  | 		views.GuestbookView("Guestbook", data, website, website.Guestbook, comments, forms.CommentCreateForm{}).Render(r.Context(), w) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	shortId := app.createShortId() | 	shortId := app.createShortId() | ||||||
| 	_, err = app.guestbookComments.Insert(shortId, guestbook.ID, 0, form.AuthorName, form.AuthorEmail, form.AuthorSite, form.Content, "", true) | 	_, err = app.guestbookComments.Insert(shortId, website.Guestbook.ID, 0, form.AuthorName, form.AuthorEmail, form.AuthorSite, form.Content, "", true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		app.serverError(w, r, err) | 		app.serverError(w, r, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	// app.sessionManager.Put(r.Context(), "flash", "Comment successfully posted!") | 	// app.sessionManager.Put(r.Context(), "flash", "Comment successfully posted!") | ||||||
| 	http.Redirect(w, r, fmt.Sprintf("/guestbooks/%s", guestbookSlug), http.StatusSeeOther) | 	http.Redirect(w, r, fmt.Sprintf("/websites/%s/guestbook", slug), http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) updateGuestbookComment(w http.ResponseWriter, r *http.Request) { | func (app *application) updateGuestbookComment(w http.ResponseWriter, r *http.Request) { | ||||||
| @ -188,8 +138,8 @@ func (app *application) deleteGuestbookComment(w http.ResponseWriter, r *http.Re | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) { | func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) { | ||||||
| 	guestbookSlug := r.PathValue("id") | 	slug := r.PathValue("id") | ||||||
| 	guestbook, err := app.guestbooks.Get(slugToShortId(guestbookSlug)) | 	website, err := app.websites.Get(slugToShortId(slug)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { | 		if errors.Is(err, models.ErrNoRecord) { | ||||||
| 			http.NotFound(w, r) | 			http.NotFound(w, r) | ||||||
| @ -199,7 +149,7 @@ func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	comments, err := app.guestbookComments.GetQueue(guestbook.ID) | 	comments, err := app.guestbookComments.GetQueue(website.Guestbook.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { | 		if errors.Is(err, models.ErrNoRecord) { | ||||||
| 			http.NotFound(w, r) | 			http.NotFound(w, r) | ||||||
| @ -210,7 +160,7 @@ func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	data := app.newCommonData(r) | 	data := app.newCommonData(r) | ||||||
| 	views.GuestbookDashboardCommentsView("Message Queue", data, guestbook, comments).Render(r.Context(), w) | 	views.GuestbookDashboardCommentsView("Message Queue", data, website, website.Guestbook, comments).Render(r.Context(), w) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) putHideGuestbookComment(w http.ResponseWriter, r *http.Request) { | func (app *application) putHideGuestbookComment(w http.ResponseWriter, r *http.Request) { | ||||||
|  | |||||||
| @ -4,128 +4,127 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
|         "git.32bit.cafe/32bitcafe/guestbook/internal/forms" | 	"git.32bit.cafe/32bitcafe/guestbook/internal/forms" | ||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/internal/validator" | 	"git.32bit.cafe/32bitcafe/guestbook/internal/validator" | ||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" | 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (app *application) getUserRegister(w http.ResponseWriter, r *http.Request) { | func (app *application) getUserRegister(w http.ResponseWriter, r *http.Request) { | ||||||
|     form := forms.UserRegistrationForm{} | 	form := forms.UserRegistrationForm{} | ||||||
|     data := app.newCommonData(r) | 	data := app.newCommonData(r) | ||||||
|     views.UserRegistration("User Registration", data, form).Render(r.Context(), w) | 	views.UserRegistration("User Registration", data, form).Render(r.Context(), w) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| func (app *application) getUserLogin(w http.ResponseWriter, r *http.Request) { | func (app *application) getUserLogin(w http.ResponseWriter, r *http.Request) { | ||||||
|     views.UserLogin("Login", app.newCommonData(r), forms.UserLoginForm{}).Render(r.Context(), w) | 	views.UserLogin("Login", app.newCommonData(r), forms.UserLoginForm{}).Render(r.Context(), w) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) postUserRegister(w http.ResponseWriter, r *http.Request) { | func (app *application) postUserRegister(w http.ResponseWriter, r *http.Request) { | ||||||
|     var form forms.UserRegistrationForm | 	var form forms.UserRegistrationForm | ||||||
|     err := app.decodePostForm(r, &form) | 	err := app.decodePostForm(r, &form) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         app.clientError(w, http.StatusBadRequest) | 		app.clientError(w, http.StatusBadRequest) | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
|     form.CheckField(validator.NotBlank(form.Name), "name", "This field cannot be blank") | 	form.CheckField(validator.NotBlank(form.Name), "name", "This field cannot be blank") | ||||||
|     form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank") | 	form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank") | ||||||
|     form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address") | 	form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address") | ||||||
|     form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank") | 	form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank") | ||||||
|     form.CheckField(validator.MinChars(form.Password, 8), "password", "This field must be at least 8 characters long") | 	form.CheckField(validator.MinChars(form.Password, 8), "password", "This field must be at least 8 characters long") | ||||||
|     if !form.Valid() { | 	if !form.Valid() { | ||||||
|         data := app.newCommonData(r) | 		data := app.newCommonData(r) | ||||||
|         w.WriteHeader(http.StatusUnprocessableEntity) | 		w.WriteHeader(http.StatusUnprocessableEntity) | ||||||
|         views.UserRegistration("User Registration", data, form).Render(r.Context(), w) | 		views.UserRegistration("User Registration", data, form).Render(r.Context(), w) | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
|     shortId := app.createShortId() | 	shortId := app.createShortId() | ||||||
|     err = app.users.Insert(shortId, form.Name, form.Email, form.Password) | 	err = app.users.Insert(shortId, form.Name, form.Email, form.Password) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         if errors.Is(err, models.ErrDuplicateEmail) { | 		if errors.Is(err, models.ErrDuplicateEmail) { | ||||||
|             form.AddFieldError("email", "Email address is already in use") | 			form.AddFieldError("email", "Email address is already in use") | ||||||
|             data := app.newCommonData(r) | 			data := app.newCommonData(r) | ||||||
|             w.WriteHeader(http.StatusUnprocessableEntity) | 			w.WriteHeader(http.StatusUnprocessableEntity) | ||||||
|             views.UserRegistration("User Registration", data, form).Render(r.Context(), w) | 			views.UserRegistration("User Registration", data, form).Render(r.Context(), w) | ||||||
|         } else { | 		} else { | ||||||
|             app.serverError(w, r, err) | 			app.serverError(w, r, err) | ||||||
|         } | 		} | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
|     app.sessionManager.Put(r.Context(), "flash", "Registration successful. Please log in.") | 	app.sessionManager.Put(r.Context(), "flash", "Registration successful. Please log in.") | ||||||
|     http.Redirect(w, r, "/users/login", http.StatusSeeOther) | 	http.Redirect(w, r, "/users/login", http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) postUserLogin(w http.ResponseWriter, r *http.Request) { | func (app *application) postUserLogin(w http.ResponseWriter, r *http.Request) { | ||||||
|     var form forms.UserLoginForm | 	var form forms.UserLoginForm | ||||||
|     err := app.decodePostForm(r, &form) | 	err := app.decodePostForm(r, &form) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         app.clientError(w, http.StatusBadRequest) | 		app.clientError(w, http.StatusBadRequest) | ||||||
|     } | 	} | ||||||
|     form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank") | 	form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank") | ||||||
|     form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address") | 	form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address") | ||||||
|     form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank") | 	form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank") | ||||||
|     if !form.Valid() { | 	if !form.Valid() { | ||||||
|         data := app.newCommonData(r) | 		data := app.newCommonData(r) | ||||||
|         w.WriteHeader(http.StatusUnprocessableEntity) | 		w.WriteHeader(http.StatusUnprocessableEntity) | ||||||
|         views.UserLogin("Login", data, form).Render(r.Context(), w) | 		views.UserLogin("Login", data, form).Render(r.Context(), w) | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
|     id, err := app.users.Authenticate(form.Email, form.Password) | 	id, err := app.users.Authenticate(form.Email, form.Password) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         if errors.Is(err, models.ErrInvalidCredentials) { | 		if errors.Is(err, models.ErrInvalidCredentials) { | ||||||
|             form.AddNonFieldError("Email or password is incorrect") | 			form.AddNonFieldError("Email or password is incorrect") | ||||||
|             data := app.newCommonData(r) | 			data := app.newCommonData(r) | ||||||
|             views.UserLogin("Login", data, form).Render(r.Context(), w) | 			views.UserLogin("Login", data, form).Render(r.Context(), w) | ||||||
|         } else { | 		} else { | ||||||
|             app.serverError(w, r, err) | 			app.serverError(w, r, err) | ||||||
|         } | 		} | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
|     err = app.sessionManager.RenewToken(r.Context()) | 	err = app.sessionManager.RenewToken(r.Context()) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         app.serverError(w, r, err) | 		app.serverError(w, r, err) | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
|     app.sessionManager.Put(r.Context(), "authenticatedUserId", id) | 	app.sessionManager.Put(r.Context(), "authenticatedUserId", id) | ||||||
|     http.Redirect(w, r, "/", http.StatusSeeOther) | 	http.Redirect(w, r, "/", http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) postUserLogout(w http.ResponseWriter, r *http.Request) { | func (app *application) postUserLogout(w http.ResponseWriter, r *http.Request) { | ||||||
|     err := app.sessionManager.RenewToken(r.Context()) | 	err := app.sessionManager.RenewToken(r.Context()) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         app.serverError(w, r, err) | 		app.serverError(w, r, err) | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
|     app.sessionManager.Remove(r.Context(), "authenticatedUserId") | 	app.sessionManager.Remove(r.Context(), "authenticatedUserId") | ||||||
|     app.sessionManager.Put(r.Context(), "flash", "You've been logged out successfully!") | 	app.sessionManager.Put(r.Context(), "flash", "You've been logged out successfully!") | ||||||
|     http.Redirect(w, r, "/", http.StatusSeeOther) | 	http.Redirect(w, r, "/", http.StatusSeeOther) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) getUsersList(w http.ResponseWriter, r *http.Request) { | // func (app *application) getUsersList(w http.ResponseWriter, r *http.Request) { | ||||||
|     // skip templ conversion for this view, which will not be available in the final app | //     // skip templ conversion for this view, which will not be available in the final app | ||||||
|     // something similar will be available in the admin panel | //     // something similar will be available in the admin panel | ||||||
|     users, err := app.users.GetAll() | //     users, err := app.users.GetAll() | ||||||
|     if err != nil { | //     if err != nil { | ||||||
|         app.serverError(w, r, err) | //         app.serverError(w, r, err) | ||||||
|         return | //         return | ||||||
|     } | //     } | ||||||
|     data := app.newTemplateData(r) | //     data := app.newTemplateData(r) | ||||||
|     data.Users = users | //     data.Users = users | ||||||
|     app.render(w, r, http.StatusOK, "userlist.view.tmpl.html", data) | //     app.render(w, r, http.StatusOK, "userlist.view.tmpl.html", data) | ||||||
| } | // } | ||||||
| 
 | 
 | ||||||
| func (app *application) getUser(w http.ResponseWriter, r *http.Request) { | func (app *application) getUser(w http.ResponseWriter, r *http.Request) { | ||||||
|     slug := r.PathValue("id")  | 	slug := r.PathValue("id") | ||||||
|     user, err := app.users.Get(slugToShortId(slug)) | 	user, err := app.users.Get(slugToShortId(slug)) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         if errors.Is(err, models.ErrNoRecord) { | 		if errors.Is(err, models.ErrNoRecord) { | ||||||
|             http.NotFound(w, r) | 			http.NotFound(w, r) | ||||||
|         } else { | 		} else { | ||||||
|             app.serverError(w, r, err) | 			app.serverError(w, r, err) | ||||||
|         } | 		} | ||||||
|         return | 		return | ||||||
|     } | 	} | ||||||
|     data := app.newCommonData(r) | 	data := app.newCommonData(r) | ||||||
|     views.UserProfile(user.Username, data, user).Render(r.Context(), w) | 	views.UserProfile(user.Username, data, user).Render(r.Context(), w) | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										92
									
								
								cmd/web/handlers_website.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								cmd/web/handlers_website.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/forms" | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/internal/validator" | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (app *application) getWebsiteCreate(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	form := forms.WebsiteCreateForm{} | ||||||
|  | 	data := app.newCommonData(r) | ||||||
|  | 	views.WebsiteCreate("Add Website", data, form).Render(r.Context(), w) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (app *application) postWebsiteCreate(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId") | ||||||
|  | 	var form forms.WebsiteCreateForm | ||||||
|  | 	err := app.decodePostForm(r, &form) | ||||||
|  | 	if err != nil { | ||||||
|  | 		app.clientError(w, http.StatusBadRequest) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	form.CheckField(validator.NotBlank(form.AuthorName), "authorName", "This field cannot be blank") | ||||||
|  | 	form.CheckField(validator.MaxChars(form.AuthorName, 256), "authorName", "This field cannot exceed 256 characters") | ||||||
|  | 	form.CheckField(validator.NotBlank(form.Name), "sitename", "This field cannot be blank") | ||||||
|  | 	form.CheckField(validator.MaxChars(form.Name, 256), "sitename", "This field cannot exceed 256 characters") | ||||||
|  | 	form.CheckField(validator.NotBlank(form.SiteUrl), "siteurl", "This field cannot be blank") | ||||||
|  | 	form.CheckField(validator.MaxChars(form.SiteUrl, 512), "siteurl", "This field cannot exceed 512 characters") | ||||||
|  | 
 | ||||||
|  | 	if !form.Valid() { | ||||||
|  | 		data := app.newCommonData(r) | ||||||
|  | 		w.WriteHeader(http.StatusUnprocessableEntity) | ||||||
|  | 		views.WebsiteCreate("Add a Website", data, form).Render(r.Context(), w) | ||||||
|  | 	} | ||||||
|  | 	websiteShortID := app.createShortId() | ||||||
|  | 	websiteId, err := app.websites.Insert(websiteShortID, userId, form.Name, form.SiteUrl, form.AuthorName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		app.serverError(w, r, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// TODO: how to handle website creation success but guestbook creation failure? | ||||||
|  | 	guestbookShortID := app.createShortId() | ||||||
|  | 	_, err = app.guestbooks.Insert(guestbookShortID, userId, websiteId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		app.serverError(w, r, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	app.sessionManager.Put(r.Context(), "flash", "Website successfully registered!") | ||||||
|  | 	if r.Header.Get("HX-Request") == "true" { | ||||||
|  | 		w.Header().Add("HX-Trigger", "newWebsite") | ||||||
|  | 		views.WebsiteCreateButton().Render(r.Context(), w) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	http.Redirect(w, r, fmt.Sprintf("/websites/%s", shortIdToSlug(websiteShortID)), http.StatusSeeOther) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (app *application) getWebsiteDashboard(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	slug := r.PathValue("id") | ||||||
|  | 	user := app.getCurrentUser(r) | ||||||
|  | 	website, err := app.websites.Get(slugToShortId(slug)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, models.ErrNoRecord) { | ||||||
|  | 			http.NotFound(w, r) | ||||||
|  | 		} else { | ||||||
|  | 			app.serverError(w, r, err) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if user.ID != website.UserId { | ||||||
|  | 		app.clientError(w, http.StatusUnauthorized) | ||||||
|  | 	} | ||||||
|  | 	data := app.newCommonData(r) | ||||||
|  | 	views.WebsiteDashboard("Guestbook", data, website).Render(r.Context(), w) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (app *application) getWebsiteList(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 
 | ||||||
|  | 	userId := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId") | ||||||
|  | 	websites, err := app.websites.GetAll(userId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		app.serverError(w, r, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	data := app.newCommonData(r) | ||||||
|  | 	views.WebsiteList("My Websites", data, websites).Render(r.Context(), w) | ||||||
|  | } | ||||||
| @ -9,133 +9,101 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" | ||||||
| 	"github.com/gorilla/schema" | 	"github.com/gorilla/schema" | ||||||
|  | 	"github.com/justinas/nosurf" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (app *application) serverError(w http.ResponseWriter, r *http.Request, err error) { | func (app *application) serverError(w http.ResponseWriter, r *http.Request, err error) { | ||||||
|     var ( | 	var ( | ||||||
|         method = r.Method | 		method = r.Method | ||||||
|         uri = r.URL.RequestURI() | 		uri    = r.URL.RequestURI() | ||||||
|     ) | 	) | ||||||
| 
 | 
 | ||||||
|     app.logger.Error(err.Error(), "method", method, "uri", uri) | 	app.logger.Error(err.Error(), "method", method, "uri", uri) | ||||||
|     http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) | 	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) clientError(w http.ResponseWriter, status int) { | func (app *application) clientError(w http.ResponseWriter, status int) { | ||||||
|     http.Error(w, http.StatusText(status), status) | 	http.Error(w, http.StatusText(status), status) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) renderHTMX(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) { | func (app *application) nextSequence() uint16 { | ||||||
|     ts, ok := app.templateCacheHTMX[page] | 	val := app.sequence | ||||||
|     if !ok { | 	if app.sequence == math.MaxUint16 { | ||||||
|         err := fmt.Errorf("the template %s does not exist", page) | 		app.sequence = 0 | ||||||
|         app.serverError(w, r, err) | 	} else { | ||||||
|         return | 		app.sequence += 1 | ||||||
|     } | 	} | ||||||
| 
 | 	return val | ||||||
|     w.WriteHeader(status) |  | ||||||
|     err := ts.Execute(w, data) |  | ||||||
|     if err != nil { |  | ||||||
|         app.serverError(w, r, err) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) renderFullPage(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) { | func (app *application) createShortId() uint64 { | ||||||
|     ts, ok := app.templateCache[page] | 	now := time.Now().UTC() | ||||||
|     if !ok { | 	epoch, err := time.Parse(time.RFC822Z, "01 Jan 20 00:00 -0000") | ||||||
|         err := fmt.Errorf("the template %s does not exist", page) | 	if err != nil { | ||||||
|         app.serverError(w, r, err) | 		fmt.Println(err) | ||||||
|         return | 		return 0 | ||||||
|     } | 	} | ||||||
| 
 | 	d := now.Sub(epoch) | ||||||
|     w.WriteHeader(status) | 	ms := d.Milliseconds() | ||||||
|     err := ts.ExecuteTemplate(w, "base", data) | 	seq := app.nextSequence() | ||||||
|     if err != nil { | 	return (uint64(ms) & 0x0FFFFFFFFFFFFFFF) | (uint64(seq) << 48) | ||||||
|         app.serverError(w, r, err) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) { |  | ||||||
|     ts, ok := app.templateCache[page] |  | ||||||
|     if !ok { |  | ||||||
|         err := fmt.Errorf("the template %s does not exist", page) |  | ||||||
|         app.serverError(w, r, err) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     w.WriteHeader(status) |  | ||||||
|     err := ts.ExecuteTemplate(w, "base", data) |  | ||||||
|     if err != nil { |  | ||||||
|         app.serverError(w, r, err) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (app *application) nextSequence () uint16 { |  | ||||||
|     val := app.sequence |  | ||||||
|     if app.sequence == math.MaxUint16 { |  | ||||||
|         app.sequence = 0 |  | ||||||
|     } else { |  | ||||||
|         app.sequence += 1 |  | ||||||
|     } |  | ||||||
|     return val |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (app *application) createShortId () uint64 { |  | ||||||
|     now := time.Now().UTC() |  | ||||||
|     epoch, err := time.Parse(time.RFC822Z, "01 Jan 20 00:00 -0000") |  | ||||||
|     if err != nil { |  | ||||||
|         fmt.Println(err) |  | ||||||
|         return 0 |  | ||||||
|     } |  | ||||||
|     d := now.Sub(epoch) |  | ||||||
|     ms := d.Milliseconds() |  | ||||||
|     seq := app.nextSequence() |  | ||||||
|     return (uint64(ms) & 0x0FFFFFFFFFFFFFFF) | (uint64(seq) << 48) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func shortIdToSlug(id uint64) string { | func shortIdToSlug(id uint64) string { | ||||||
|     slug := strconv.FormatUint(id, 36) | 	slug := strconv.FormatUint(id, 36) | ||||||
|     return slug | 	return slug | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func slugToShortId(slug string) uint64 { | func slugToShortId(slug string) uint64 { | ||||||
|     id, _ := strconv.ParseUint(slug, 36, 64) | 	id, _ := strconv.ParseUint(slug, 36, 64) | ||||||
|     return id | 	return id | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) decodePostForm(r *http.Request, dst any) error { | func (app *application) decodePostForm(r *http.Request, dst any) error { | ||||||
|     err := r.ParseForm() | 	err := r.ParseForm() | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         return err | 		return err | ||||||
|     } | 	} | ||||||
| 
 | 
 | ||||||
|     err = app.formDecoder.Decode(dst, r.PostForm) | 	err = app.formDecoder.Decode(dst, r.PostForm) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         var multiErrors *schema.MultiError | 		var multiErrors *schema.MultiError | ||||||
|         if !errors.As(err, &multiErrors) { | 		if !errors.As(err, &multiErrors) { | ||||||
|             panic(err) | 			panic(err) | ||||||
|         } | 		} | ||||||
|         return err | 		return err | ||||||
|     } | 	} | ||||||
|     return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) isAuthenticated(r *http.Request) bool { | func (app *application) isAuthenticated(r *http.Request) bool { | ||||||
|     isAuthenticated, ok := r.Context().Value(isAuthenticatedContextKey).(bool) | 	isAuthenticated, ok := r.Context().Value(isAuthenticatedContextKey).(bool) | ||||||
|     if !ok { | 	if !ok { | ||||||
|         return false | 		return false | ||||||
|     } | 	} | ||||||
|     return isAuthenticated | 	return isAuthenticated | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *application) getCurrentUser(r *http.Request) *models.User { | func (app *application) getCurrentUser(r *http.Request) *models.User { | ||||||
|     if !app.isAuthenticated(r) { | 	if !app.isAuthenticated(r) { | ||||||
|         return nil | 		return nil | ||||||
|     } | 	} | ||||||
|     user, ok := r.Context().Value(userNameContextKey).(models.User) | 	user, ok := r.Context().Value(userNameContextKey).(models.User) | ||||||
|     if !ok { | 	if !ok { | ||||||
|         return nil | 		return nil | ||||||
|     } | 	} | ||||||
|     return &user | 	return &user | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (app *application) newCommonData(r *http.Request) views.CommonData { | ||||||
|  | 	return views.CommonData{ | ||||||
|  | 		CurrentYear:     time.Now().Year(), | ||||||
|  | 		Flash:           app.sessionManager.PopString(r.Context(), "flash"), | ||||||
|  | 		IsAuthenticated: app.isAuthenticated(r), | ||||||
|  | 		CSRFToken:       nosurf.Token(r), | ||||||
|  | 		CurrentUser:     app.getCurrentUser(r), | ||||||
|  | 		IsHtmx:          r.Header.Get("Hx-Request") == "true", | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										129
									
								
								cmd/web/main.go
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								cmd/web/main.go
									
									
									
									
									
								
							| @ -7,7 +7,6 @@ import ( | |||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| 	"text/template" |  | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
| @ -18,90 +17,76 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type application struct { | type application struct { | ||||||
|     sequence uint16 | 	sequence          uint16 | ||||||
|     logger *slog.Logger | 	logger            *slog.Logger | ||||||
|     templateCache map[string]*template.Template | 	websites          *models.WebsiteModel | ||||||
|     templateCacheHTMX map[string]*template.Template | 	guestbooks        *models.GuestbookModel | ||||||
|     guestbooks *models.GuestbookModel | 	users             *models.UserModel | ||||||
|     users *models.UserModel | 	guestbookComments *models.GuestbookCommentModel | ||||||
|     guestbookComments *models.GuestbookCommentModel | 	sessionManager    *scs.SessionManager | ||||||
|     sessionManager *scs.SessionManager | 	formDecoder       *schema.Decoder | ||||||
|     formDecoder *schema.Decoder |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
|     addr := flag.String("addr", ":3000", "HTTP network address") | 	addr := flag.String("addr", ":3000", "HTTP network address") | ||||||
|     dsn := flag.String("dsn", "guestbook.db", "data source name") | 	dsn := flag.String("dsn", "guestbook.db", "data source name") | ||||||
|     flag.Parse() | 	flag.Parse() | ||||||
| 
 | 
 | ||||||
|     logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) | 	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) | ||||||
| 
 | 
 | ||||||
|     db, err := openDB(*dsn) | 	db, err := openDB(*dsn) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         logger.Error(err.Error()) | 		logger.Error(err.Error()) | ||||||
|         os.Exit(1) | 		os.Exit(1) | ||||||
|     } | 	} | ||||||
|     defer db.Close() | 	defer db.Close() | ||||||
| 
 | 
 | ||||||
|     templateCache, err := newTemplateCache() | 	sessionManager := scs.New() | ||||||
|     if err != nil { | 	sessionManager.Store = sqlite3store.New(db) | ||||||
|         logger.Error(err.Error()) | 	sessionManager.Lifetime = 12 * time.Hour | ||||||
|         os.Exit(1) |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     templateCacheHTMX, err := newHTMXTemplateCache() | 	formDecoder := schema.NewDecoder() | ||||||
|     if err != nil { | 	formDecoder.IgnoreUnknownKeys(true) | ||||||
|         logger.Error(err.Error()) |  | ||||||
|         os.Exit(1) |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     sessionManager := scs.New() | 	app := &application{ | ||||||
|     sessionManager.Store = sqlite3store.New(db) | 		sequence:          0, | ||||||
|     sessionManager.Lifetime = 12 * time.Hour | 		logger:            logger, | ||||||
|  | 		sessionManager:    sessionManager, | ||||||
|  | 		websites:          &models.WebsiteModel{DB: db}, | ||||||
|  | 		guestbooks:        &models.GuestbookModel{DB: db}, | ||||||
|  | 		users:             &models.UserModel{DB: db}, | ||||||
|  | 		guestbookComments: &models.GuestbookCommentModel{DB: db}, | ||||||
|  | 		formDecoder:       formDecoder, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|     formDecoder := schema.NewDecoder() | 	tlsConfig := &tls.Config{ | ||||||
|     formDecoder.IgnoreUnknownKeys(true) | 		CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|     app := &application{ | 	srv := &http.Server{ | ||||||
|         sequence: 0, | 		Addr:         *addr, | ||||||
|         templateCache: templateCache, | 		Handler:      app.routes(), | ||||||
|         templateCacheHTMX: templateCacheHTMX, | 		ErrorLog:     slog.NewLogLogger(logger.Handler(), slog.LevelError), | ||||||
|         logger: logger, | 		TLSConfig:    tlsConfig, | ||||||
|         sessionManager: sessionManager, | 		IdleTimeout:  time.Minute, | ||||||
|         guestbooks: &models.GuestbookModel{DB: db}, | 		ReadTimeout:  5 * time.Second, | ||||||
|         users: &models.UserModel{DB: db}, | 		WriteTimeout: 10 * time.Second, | ||||||
|         guestbookComments: &models.GuestbookCommentModel{DB: db}, | 	} | ||||||
|         formDecoder: formDecoder, |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     tlsConfig := &tls.Config{ | 	logger.Info("Starting server", slog.Any("addr", *addr)) | ||||||
|         CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     srv := &http.Server { | 	err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem") | ||||||
|         Addr: *addr, | 	logger.Error(err.Error()) | ||||||
|         Handler: app.routes(), | 	os.Exit(1) | ||||||
|         ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), |  | ||||||
|         TLSConfig: tlsConfig, |  | ||||||
|         IdleTimeout: time.Minute, |  | ||||||
|         ReadTimeout: 5 * time.Second, |  | ||||||
|         WriteTimeout: 10 * time.Second, |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     logger.Info("Starting server", slog.Any("addr", *addr)) |  | ||||||
| 
 |  | ||||||
|     err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem") |  | ||||||
|     logger.Error(err.Error()) |  | ||||||
|     os.Exit(1) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func openDB(dsn string) (*sql.DB, error) { | func openDB(dsn string) (*sql.DB, error) { | ||||||
|     db, err := sql.Open("sqlite3", dsn) | 	db, err := sql.Open("sqlite3", dsn) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
|         return nil, err | 		return nil, err | ||||||
|     } | 	} | ||||||
|     if err = db.Ping(); err != nil { | 	if err = db.Ping(); err != nil { | ||||||
|         return nil, err | 		return nil, err | ||||||
|     } | 	} | ||||||
|     return db, nil | 	return db, nil | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,35 +7,33 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (app *application) routes() http.Handler { | func (app *application) routes() http.Handler { | ||||||
|     mux := http.NewServeMux() | 	mux := http.NewServeMux() | ||||||
|     fileServer := http.FileServer(http.Dir("./ui/static")) | 	fileServer := http.FileServer(http.Dir("./ui/static")) | ||||||
|     mux.Handle("GET /static/", http.StripPrefix("/static", fileServer)) | 	mux.Handle("GET /static/", http.StripPrefix("/static", fileServer)) | ||||||
|      |  | ||||||
|     dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate) |  | ||||||
|     standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) |  | ||||||
| 
 | 
 | ||||||
|     mux.Handle("/{$}", dynamic.ThenFunc(app.home)) | 	dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate) | ||||||
|     mux.Handle("POST /guestbooks/{id}/comments/create", standard.ThenFunc(app.postGuestbookCommentCreate)) | 	standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) | ||||||
|     mux.Handle("GET /guestbooks/{id}", dynamic.ThenFunc(app.getGuestbook)) |  | ||||||
|     mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister)) |  | ||||||
|     mux.Handle("POST /users/register", dynamic.ThenFunc(app.postUserRegister)) |  | ||||||
|     mux.Handle("GET /users/login", dynamic.ThenFunc(app.getUserLogin)) |  | ||||||
|     mux.Handle("POST /users/login", dynamic.ThenFunc(app.postUserLogin)) |  | ||||||
| 
 | 
 | ||||||
|     protected := dynamic.Append(app.requireAuthentication) | 	mux.Handle("/{$}", dynamic.ThenFunc(app.home)) | ||||||
|  | 	mux.Handle("POST /websites/{id}/guestbook/comments/create", standard.ThenFunc(app.postGuestbookCommentCreate)) | ||||||
|  | 	mux.Handle("GET /websites/{id}/guestbook", dynamic.ThenFunc(app.getGuestbook)) | ||||||
|  | 	mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister)) | ||||||
|  | 	mux.Handle("POST /users/register", dynamic.ThenFunc(app.postUserRegister)) | ||||||
|  | 	mux.Handle("GET /users/login", dynamic.ThenFunc(app.getUserLogin)) | ||||||
|  | 	mux.Handle("POST /users/login", dynamic.ThenFunc(app.postUserLogin)) | ||||||
| 
 | 
 | ||||||
|     mux.Handle("GET /users", protected.ThenFunc(app.getUsersList)) | 	protected := dynamic.Append(app.requireAuthentication) | ||||||
|     mux.Handle("GET /users/{id}", protected.ThenFunc(app.getUser)) |  | ||||||
|     mux.Handle("POST /users/logout", protected.ThenFunc(app.postUserLogout)) |  | ||||||
|     mux.Handle("GET /guestbooks", protected.ThenFunc(app.getGuestbookList)) |  | ||||||
|     mux.Handle("GET /guestbooks/create", protected.ThenFunc(app.getGuestbookCreate)) |  | ||||||
|     mux.Handle("POST /guestbooks/create", protected.ThenFunc(app.postGuestbookCreate)) |  | ||||||
|     mux.Handle("GET /guestbooks/{id}/dashboard", protected.ThenFunc(app.getGuestbookDashboard)) |  | ||||||
|     mux.Handle("GET /guestbooks/{id}/dashboard/comments", protected.ThenFunc(app.getGuestbookComments)) |  | ||||||
|     mux.Handle("GET /guestbooks/{id}/comments/create", protected.ThenFunc(app.getGuestbookCommentCreate)) |  | ||||||
|     mux.Handle("GET /guestbooks/{id}/dashboard/comments/queue", protected.ThenFunc(app.getCommentQueue)) |  | ||||||
| 
 | 
 | ||||||
|  | 	// mux.Handle("GET /users", protected.ThenFunc(app.getUsersList)) | ||||||
|  | 	mux.Handle("GET /users/{id}", protected.ThenFunc(app.getUser)) | ||||||
|  | 	mux.Handle("POST /users/logout", protected.ThenFunc(app.postUserLogout)) | ||||||
|  | 	mux.Handle("GET /websites", protected.ThenFunc(app.getWebsiteList)) | ||||||
|  | 	mux.Handle("GET /websites/create", protected.ThenFunc(app.getWebsiteCreate)) | ||||||
|  | 	mux.Handle("POST /websites/create", protected.ThenFunc(app.postWebsiteCreate)) | ||||||
|  | 	mux.Handle("GET /websites/{id}/dashboard", protected.ThenFunc(app.getWebsiteDashboard)) | ||||||
|  | 	mux.Handle("GET /websites/{id}/dashboard/guestbook/comments", protected.ThenFunc(app.getGuestbookComments)) | ||||||
|  | 	mux.Handle("GET /websites/{id}/dashboard/guestbook/comments/queue", protected.ThenFunc(app.getCommentQueue)) | ||||||
|  | 	mux.Handle("GET /websites/{id}/guestbook/comments/create", protected.ThenFunc(app.getGuestbookCommentCreate)) | ||||||
| 
 | 
 | ||||||
|     return standard.Then(mux) | 	return standard.Then(mux) | ||||||
| } | } | ||||||
| 
 |  | ||||||
|  | |||||||
| @ -1,100 +0,0 @@ | |||||||
| package main |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"text/template" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" |  | ||||||
| 	"git.32bit.cafe/32bitcafe/guestbook/ui/views" |  | ||||||
| 	"github.com/justinas/nosurf" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type templateData struct { |  | ||||||
|     CurrentYear int |  | ||||||
|     User models.User |  | ||||||
|     Users []models.User |  | ||||||
|     Guestbook models.Guestbook |  | ||||||
|     Guestbooks []models.Guestbook |  | ||||||
|     Comment models.GuestbookComment |  | ||||||
|     Comments []models.GuestbookComment |  | ||||||
|     Flash string |  | ||||||
|     Form any |  | ||||||
|     IsAuthenticated bool |  | ||||||
|     CSRFToken string |  | ||||||
|     CurrentUser *models.User |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func humanDate(t time.Time) string { |  | ||||||
|     return t.Format("02 Jan 2006 at 15:04") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var functions = template.FuncMap { |  | ||||||
|     "humanDate": humanDate, |  | ||||||
|     "shortIdToSlug": shortIdToSlug, |  | ||||||
|     "slugToShortId": slugToShortId, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func newHTMXTemplateCache() (map[string]*template.Template, error) { |  | ||||||
|     cache := map[string]*template.Template{} |  | ||||||
|     pages, err := filepath.Glob("./ui/html/htmx/*.part.html") |  | ||||||
|     if err != nil { |  | ||||||
|         return nil, err |  | ||||||
|     } |  | ||||||
|     for _, page := range pages { |  | ||||||
|         name := filepath.Base(page) |  | ||||||
|         ts, err := template.New(name).Funcs(functions).ParseFiles(page) |  | ||||||
|         if err != nil { |  | ||||||
|             return nil, err |  | ||||||
|         } |  | ||||||
|         cache[name] = ts |  | ||||||
|     } |  | ||||||
|     return cache, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func newTemplateCache() (map[string]*template.Template, error) { |  | ||||||
|     cache := map[string]*template.Template{} |  | ||||||
|     pages, err := filepath.Glob("./ui/html/pages/*.tmpl.html") |  | ||||||
|     if err != nil { |  | ||||||
|         return nil, err |  | ||||||
|     } |  | ||||||
|     for _, page := range pages { |  | ||||||
|         name := filepath.Base(page) |  | ||||||
|         ts, err := template.New(name).Funcs(functions).ParseFiles("./ui/html/base.tmpl.html") |  | ||||||
|         if err != nil { |  | ||||||
|             return nil, err |  | ||||||
|         } |  | ||||||
|         ts, err = ts.ParseGlob("./ui/html/partials/*.tmpl.html") |  | ||||||
|         if err != nil { |  | ||||||
|             return nil, err |  | ||||||
|         } |  | ||||||
|         ts, err = ts.ParseFiles(page) |  | ||||||
|         if err != nil { |  | ||||||
|             return nil, err |  | ||||||
|         } |  | ||||||
|         cache[name] = ts |  | ||||||
|     } |  | ||||||
|     return cache, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (app *application) newCommonData(r *http.Request) views.CommonData { |  | ||||||
|     return views.CommonData { |  | ||||||
|         CurrentYear: time.Now().Year(), |  | ||||||
|         Flash: app.sessionManager.PopString(r.Context(), "flash"), |  | ||||||
|         IsAuthenticated: app.isAuthenticated(r), |  | ||||||
|         CSRFToken: nosurf.Token(r), |  | ||||||
|         CurrentUser: app.getCurrentUser(r), |  | ||||||
|         IsHtmx: r.Header.Get("Hx-Request") == "true", |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (app *application) newTemplateData(r *http.Request) templateData { |  | ||||||
|     return templateData { |  | ||||||
|         CurrentYear: time.Now().Year(), |  | ||||||
|         Flash: app.sessionManager.PopString(r.Context(), "flash"), |  | ||||||
|         IsAuthenticated: app.isAuthenticated(r), |  | ||||||
|         CSRFToken: nosurf.Token(r), |  | ||||||
|         CurrentUser: app.getCurrentUser(r), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,5 +0,0 @@ | |||||||
| CREATE TABLE sessions ( |  | ||||||
|     token CHAR(43) primary key, |  | ||||||
|     data BLOB NOT NULL, |  | ||||||
|     expiry TEXT NOT NULL |  | ||||||
| ); |  | ||||||
| @ -9,10 +9,24 @@ CREATE TABLE users ( | |||||||
|     Created datetime NOT NULL |     Created datetime NOT NULL | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | CREATE TABLE websites ( | ||||||
|  |     Id integer primary key autoincrement, | ||||||
|  |     ShortId integer UNIQUE NOT NULL, | ||||||
|  |     Name varchar(256) NOT NULL, | ||||||
|  |     SiteUrl varchar(512) NOT NULL, | ||||||
|  |     AuthorName varchar(512) NOT NULL, | ||||||
|  |     UserId integer NOT NULL, | ||||||
|  |     Created datetime NOT NULL, | ||||||
|  |     Deleted datetime, | ||||||
|  |     FOREIGN KEY (UserId) REFERENCES users(Id) | ||||||
|  |         ON DELETE RESTRICT | ||||||
|  |         ON UPDATE RESTRICT | ||||||
|  | ); | ||||||
|  | 
 | ||||||
| CREATE TABLE guestbooks ( | CREATE TABLE guestbooks ( | ||||||
|     Id integer primary key autoincrement, |     Id integer primary key autoincrement, | ||||||
|     ShortId integer UNIQUE NOT NULL, |     ShortId integer UNIQUE NOT NULL, | ||||||
|     SiteUrl varchar(512) NOT NULL, |     WebsiteId integer UNIQUE NOT NULL, | ||||||
|     UserId integer NOT NULL, |     UserId integer NOT NULL, | ||||||
|     Created datetime NOT NULL, |     Created datetime NOT NULL, | ||||||
|     IsDeleted boolean NOT NULL DEFAULT FALSE, |     IsDeleted boolean NOT NULL DEFAULT FALSE, | ||||||
| @ -20,6 +34,9 @@ CREATE TABLE guestbooks ( | |||||||
|     FOREIGN KEY (UserId) REFERENCES users(Id) |     FOREIGN KEY (UserId) REFERENCES users(Id) | ||||||
|         ON DELETE RESTRICT |         ON DELETE RESTRICT | ||||||
|         ON UPDATE RESTRICT |         ON UPDATE RESTRICT | ||||||
|  |     FOREIGN KEY (WebsiteId) REFERENCES websites(Id) | ||||||
|  |         ON DELETE RESTRICT | ||||||
|  |         ON UPDATE RESTRICT | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| CREATE TABLE guestbook_comments ( | CREATE TABLE guestbook_comments ( | ||||||
| @ -44,3 +61,9 @@ CREATE TABLE guestbook_comments ( | |||||||
|         ON DELETE RESTRICT |         ON DELETE RESTRICT | ||||||
|         ON UPDATE RESTRICT |         ON UPDATE RESTRICT | ||||||
| ); | ); | ||||||
|  | 
 | ||||||
|  | CREATE TABLE sessions ( | ||||||
|  |     token CHAR(43) primary key, | ||||||
|  |     data BLOB NOT NULL, | ||||||
|  |     expiry TEXT NOT NULL | ||||||
|  | ); | ||||||
|  | |||||||
| @ -3,23 +3,29 @@ package forms | |||||||
| import "git.32bit.cafe/32bitcafe/guestbook/internal/validator" | import "git.32bit.cafe/32bitcafe/guestbook/internal/validator" | ||||||
| 
 | 
 | ||||||
| type UserRegistrationForm struct { | type UserRegistrationForm struct { | ||||||
|     Name        string  `schema:"username"` | 	Name                string `schema:"username"` | ||||||
|     Email       string  `schema:"email"` | 	Email               string `schema:"email"` | ||||||
|     Password    string  `schema:"password"` | 	Password            string `schema:"password"` | ||||||
|     validator.Validator `schema:"-"` | 	validator.Validator `schema:"-"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type UserLoginForm struct { | type UserLoginForm struct { | ||||||
|     Email       string  `schema:"email"` | 	Email               string `schema:"email"` | ||||||
|     Password    string  `schema:"password"` | 	Password            string `schema:"password"` | ||||||
|     validator.Validator `schema:"-"` | 	validator.Validator `schema:"-"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CommentCreateForm struct { | type CommentCreateForm struct { | ||||||
|     AuthorName  string  `schema:"authorname"` | 	AuthorName          string `schema:"authorname"` | ||||||
|     AuthorEmail string  `schema:"authoremail"` | 	AuthorEmail         string `schema:"authoremail"` | ||||||
|     AuthorSite  string  `schema:"authorsite"` | 	AuthorSite          string `schema:"authorsite"` | ||||||
|     Content     string  `schema:"content,required"` | 	Content             string `schema:"content,required"` | ||||||
|     validator.Validator `schema:"-"` | 	validator.Validator `schema:"-"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type WebsiteCreateForm struct { | ||||||
|  | 	Name                string `schema:"sitename"` | ||||||
|  | 	SiteUrl             string `schema:"siteurl"` | ||||||
|  | 	AuthorName          string `schema:"authorname"` | ||||||
|  | 	validator.Validator `schema:"-"` | ||||||
|  | } | ||||||
|  | |||||||
| @ -8,8 +8,8 @@ import ( | |||||||
| type Guestbook struct { | type Guestbook struct { | ||||||
| 	ID        int64 | 	ID        int64 | ||||||
| 	ShortId   uint64 | 	ShortId   uint64 | ||||||
| 	SiteUrl   string |  | ||||||
| 	UserId    int64 | 	UserId    int64 | ||||||
|  | 	WebsiteId int64 | ||||||
| 	Created   time.Time | 	Created   time.Time | ||||||
| 	IsDeleted bool | 	IsDeleted bool | ||||||
| 	IsActive  bool | 	IsActive  bool | ||||||
| @ -19,10 +19,10 @@ type GuestbookModel struct { | |||||||
| 	DB *sql.DB | 	DB *sql.DB | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *GuestbookModel) Insert(shortId uint64, siteUrl string, userId int64) (int64, error) { | func (m *GuestbookModel) Insert(shortId uint64, userId int64, websiteId int64) (int64, error) { | ||||||
| 	stmt := `INSERT INTO guestbooks (ShortId, SiteUrl, UserId, Created, IsDeleted, IsActive) | 	stmt := `INSERT INTO guestbooks (ShortId, UserId, WebsiteId, Created, IsDeleted, IsActive) | ||||||
|     VALUES(?, ?, ?, ?, FALSE, TRUE)` |     VALUES(?, ?, ?, ?, FALSE, TRUE)` | ||||||
| 	result, err := m.DB.Exec(stmt, shortId, siteUrl, userId, time.Now().UTC()) | 	result, err := m.DB.Exec(stmt, shortId, userId, websiteId, time.Now().UTC()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return -1, err | 		return -1, err | ||||||
| 	} | 	} | ||||||
| @ -34,11 +34,11 @@ func (m *GuestbookModel) Insert(shortId uint64, siteUrl string, userId int64) (i | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *GuestbookModel) Get(shortId uint64) (Guestbook, error) { | func (m *GuestbookModel) Get(shortId uint64) (Guestbook, error) { | ||||||
| 	stmt := `SELECT Id, ShortId, SiteUrl, UserId, Created, IsDeleted, IsActive FROM guestbooks | 	stmt := `SELECT Id, ShortId, UserId, WebsiteId, Created, IsDeleted, IsActive FROM guestbooks | ||||||
|     WHERE ShortId = ?` |     WHERE ShortId = ?` | ||||||
| 	row := m.DB.QueryRow(stmt, shortId) | 	row := m.DB.QueryRow(stmt, shortId) | ||||||
| 	var g Guestbook | 	var g Guestbook | ||||||
| 	err := row.Scan(&g.ID, &g.ShortId, &g.SiteUrl, &g.UserId, &g.Created, &g.IsDeleted, &g.IsActive) | 	err := row.Scan(&g.ID, &g.ShortId, &g.UserId, &g.WebsiteId, &g.Created, &g.IsDeleted, &g.IsActive) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return Guestbook{}, err | 		return Guestbook{}, err | ||||||
| 	} | 	} | ||||||
| @ -47,7 +47,7 @@ func (m *GuestbookModel) Get(shortId uint64) (Guestbook, error) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) { | func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) { | ||||||
| 	stmt := `SELECT Id, ShortId, SiteUrl, UserId, Created, IsDeleted, IsActive FROM guestbooks | 	stmt := `SELECT Id, ShortId, UserId, WebsiteId, Created, IsDeleted, IsActive FROM guestbooks | ||||||
|     WHERE UserId = ?` |     WHERE UserId = ?` | ||||||
| 	rows, err := m.DB.Query(stmt, userId) | 	rows, err := m.DB.Query(stmt, userId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -56,7 +56,7 @@ func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) { | |||||||
| 	var guestbooks []Guestbook | 	var guestbooks []Guestbook | ||||||
| 	for rows.Next() { | 	for rows.Next() { | ||||||
| 		var g Guestbook | 		var g Guestbook | ||||||
| 		err = rows.Scan(&g.ID, &g.ShortId, &g.SiteUrl, &g.UserId, &g.Created, &g.IsDeleted, &g.IsActive) | 		err = rows.Scan(&g.ID, &g.ShortId, &g.UserId, &g.WebsiteId, &g.Created, &g.IsDeleted, &g.IsActive) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -6,110 +6,110 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type GuestbookComment struct { | type GuestbookComment struct { | ||||||
|     ID int64 | 	ID          int64 | ||||||
|     ShortId uint64 | 	ShortId     uint64 | ||||||
|     GuestbookId int64 | 	GuestbookId int64 | ||||||
|     ParentId int64 | 	ParentId    int64 | ||||||
|     AuthorName string | 	AuthorName  string | ||||||
|     AuthorEmail string | 	AuthorEmail string | ||||||
|     AuthorSite string | 	AuthorSite  string | ||||||
|     CommentText string | 	CommentText string | ||||||
|     PageUrl string | 	PageUrl     string | ||||||
|     Created time.Time | 	Created     time.Time | ||||||
|     IsPublished bool | 	IsPublished bool | ||||||
|     IsDeleted bool | 	IsDeleted   bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type GuestbookCommentModel struct { | type GuestbookCommentModel struct { | ||||||
|     DB *sql.DB | 	DB *sql.DB | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *GuestbookCommentModel) Insert(shortId uint64, guestbookId, parentId int64, authorName, | func (m *GuestbookCommentModel) Insert(shortId uint64, guestbookId, parentId int64, authorName, | ||||||
|     authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) { | 	authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) { | ||||||
|     stmt := `INSERT INTO guestbook_comments (ShortId, GuestbookId, ParentId, AuthorName, | 	stmt := `INSERT INTO guestbook_comments (ShortId, GuestbookId, ParentId, AuthorName, | ||||||
|     AuthorEmail, AuthorSite, CommentText, PageUrl, Created, IsPublished, IsDeleted) |     AuthorEmail, AuthorSite, CommentText, PageUrl, Created, IsPublished, IsDeleted) | ||||||
|     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE)` |     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE)` | ||||||
|     result, err := m.DB.Exec(stmt, shortId, guestbookId, parentId, authorName, authorEmail, | 	result, err := m.DB.Exec(stmt, shortId, guestbookId, parentId, authorName, authorEmail, | ||||||
| 	authorSite, commentText, pageUrl, time.Now().UTC(), isPublished) | 		authorSite, commentText, pageUrl, time.Now().UTC(), isPublished) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
| 	return -1, err | 		return -1, err | ||||||
|     } | 	} | ||||||
|     id, err := result.LastInsertId() | 	id, err := result.LastInsertId() | ||||||
|     if err != nil { | 	if err != nil { | ||||||
| 	return -1, err | 		return -1, err | ||||||
|     } | 	} | ||||||
|     return id, nil | 	return id, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *GuestbookCommentModel) Get(shortId uint64) (GuestbookComment, error) { | func (m *GuestbookCommentModel) Get(shortId uint64) (GuestbookComment, error) { | ||||||
|     stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, | 	stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, | ||||||
|     CommentText, PageUrl, Created, IsPublished, IsDeleted FROM guestbook_comments WHERE ShortId = ?` |     CommentText, PageUrl, Created, IsPublished, IsDeleted FROM guestbook_comments WHERE ShortId = ?` | ||||||
|     row := m.DB.QueryRow(stmt, shortId) | 	row := m.DB.QueryRow(stmt, shortId) | ||||||
|     var c GuestbookComment | 	var c GuestbookComment | ||||||
|     err := row.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted) | 	err := row.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
| 	return GuestbookComment{}, err | 		return GuestbookComment{}, err | ||||||
|     } | 	} | ||||||
|     return c, nil | 	return c, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, error) { | func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, error) { | ||||||
|     stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, | 	stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, | ||||||
|     CommentText, PageUrl, Created, IsPublished, IsDeleted  |     CommentText, PageUrl, Created, IsPublished, IsDeleted  | ||||||
| 	    FROM guestbook_comments  | 	    FROM guestbook_comments  | ||||||
| 	    WHERE GuestbookId = ? AND IsDeleted = FALSE AND IsPublished = TRUE | 	    WHERE GuestbookId = ? AND IsDeleted = FALSE AND IsPublished = TRUE | ||||||
| 	    ORDER BY Created DESC` | 	    ORDER BY Created DESC` | ||||||
|     rows, err := m.DB.Query(stmt, guestbookId) | 	rows, err := m.DB.Query(stmt, guestbookId) | ||||||
|     if err != nil { |  | ||||||
| 	return nil, err |  | ||||||
|     } |  | ||||||
|     var comments []GuestbookComment |  | ||||||
|     for rows.Next() { |  | ||||||
| 	var c GuestbookComment |  | ||||||
| 	err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 	    return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	comments = append(comments, c) | 	var comments []GuestbookComment | ||||||
|     } | 	for rows.Next() { | ||||||
|     if err = rows.Err(); err != nil { | 		var c GuestbookComment | ||||||
| 	return nil, err | 		err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted) | ||||||
|     } | 		if err != nil { | ||||||
|     return comments, nil | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		comments = append(comments, c) | ||||||
|  | 	} | ||||||
|  | 	if err = rows.Err(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return comments, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *GuestbookCommentModel) GetQueue(guestbookId int64) ([]GuestbookComment, error) { | func (m *GuestbookCommentModel) GetQueue(guestbookId int64) ([]GuestbookComment, error) { | ||||||
|     stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, | 	stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, | ||||||
|     CommentText, PageUrl, Created, IsPublished, IsDeleted  |     CommentText, PageUrl, Created, IsPublished, IsDeleted  | ||||||
| 	    FROM guestbook_comments  | 	    FROM guestbook_comments  | ||||||
| 	    WHERE GuestbookId = ? AND IsDeleted = FALSE AND IsPublished = FALSE | 	    WHERE GuestbookId = ? AND IsDeleted = FALSE AND IsPublished = FALSE | ||||||
| 	    ORDER BY Created DESC` | 	    ORDER BY Created DESC` | ||||||
|     rows, err := m.DB.Query(stmt, guestbookId) | 	rows, err := m.DB.Query(stmt, guestbookId) | ||||||
|     if err != nil { |  | ||||||
| 	return nil, err |  | ||||||
|     } |  | ||||||
|     var comments []GuestbookComment |  | ||||||
|     for rows.Next() { |  | ||||||
| 	var c GuestbookComment |  | ||||||
| 	err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 	    return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	comments = append(comments, c) | 	var comments []GuestbookComment | ||||||
|     } | 	for rows.Next() { | ||||||
|     if err = rows.Err(); err != nil { | 		var c GuestbookComment | ||||||
| 	return nil, err | 		err = rows.Scan(&c.ID, &c.ShortId, &c.GuestbookId, &c.ParentId, &c.AuthorName, &c.AuthorEmail, &c.AuthorSite, &c.CommentText, &c.PageUrl, &c.Created, &c.IsPublished, &c.IsDeleted) | ||||||
|     } | 		if err != nil { | ||||||
|     return comments, nil | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		comments = append(comments, c) | ||||||
|  | 	} | ||||||
|  | 	if err = rows.Err(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return comments, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *GuestbookCommentModel) UpdateComment(comment *GuestbookComment) error { | func (m *GuestbookCommentModel) UpdateComment(comment *GuestbookComment) error { | ||||||
|     stmt := `UPDATE guestbook_comments (CommentText, PageUrl, IsPublished, IsDeleted) | 	stmt := `UPDATE guestbook_comments (CommentText, PageUrl, IsPublished, IsDeleted) | ||||||
| 		VALUES (?, ?, ?, ?) | 		VALUES (?, ?, ?, ?) | ||||||
| 		WHERE Id = ?` | 		WHERE Id = ?` | ||||||
|     _, err := m.DB.Exec(stmt, comment.CommentText, comment.PageUrl, comment.IsPublished, comment.IsDeleted, comment.ID) | 	_, err := m.DB.Exec(stmt, comment.CommentText, comment.PageUrl, comment.IsPublished, comment.IsDeleted, comment.ID) | ||||||
|     if err != nil { | 	if err != nil { | ||||||
| 	return err | 		return err | ||||||
|     } | 	} | ||||||
|     return nil | 	return nil | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										102
									
								
								internal/models/website.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								internal/models/website.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | |||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"database/sql" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Website struct { | ||||||
|  | 	ID         int64 | ||||||
|  | 	ShortId    uint64 | ||||||
|  | 	Name       string | ||||||
|  | 	SiteUrl    string | ||||||
|  | 	AuthorName string | ||||||
|  | 	UserId     int64 | ||||||
|  | 	Created    time.Time | ||||||
|  | 	Deleted    time.Time | ||||||
|  | 	Guestbook  Guestbook | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type WebsiteModel struct { | ||||||
|  | 	DB *sql.DB | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) { | ||||||
|  | 	stmt := `INSERT INTO websites (ShortId, Name, SiteUrl, AuthorName, UserId, Created) | ||||||
|  | 			VALUES (?, ?, ?, ?, ?, ?)` | ||||||
|  | 	result, err := m.DB.Exec(stmt, shortId, siteName, siteUrl, authorName, userId, time.Now().UTC()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return -1, err | ||||||
|  | 	} | ||||||
|  | 	id, err := result.LastInsertId() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return -1, err | ||||||
|  | 	} | ||||||
|  | 	return id, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) Get(shortId uint64) (Website, error) { | ||||||
|  | 	stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, w.Deleted, | ||||||
|  | 	g.Id, g.ShortId, g.Created, g.IsDeleted, g.IsActive | ||||||
|  | 	FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId | ||||||
|  | 	WHERE w.ShortId = ?` | ||||||
|  | 	row := m.DB.QueryRow(stmt, shortId) | ||||||
|  | 	var t sql.NullTime | ||||||
|  | 	var w Website | ||||||
|  | 	err := row.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, &t, | ||||||
|  | 		&w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsDeleted, &w.Guestbook.IsActive) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return Website{}, err | ||||||
|  | 	} | ||||||
|  | 	// handle if Deleted is null | ||||||
|  | 	if t.Valid { | ||||||
|  | 		w.Deleted = t.Time | ||||||
|  | 	} | ||||||
|  | 	return w, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) GetById(id int64) (Website, error) { | ||||||
|  | 	stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, w.Deleted, | ||||||
|  | 	g.Id, g.ShortId, g.Created, g.IsDeleted, g.IsActive | ||||||
|  | 	FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId | ||||||
|  | 	WHERE w.Id = ?` | ||||||
|  | 	row := m.DB.QueryRow(stmt, id) | ||||||
|  | 	var t sql.NullTime | ||||||
|  | 	var w Website | ||||||
|  | 	err := row.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, &t, | ||||||
|  | 		&w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsDeleted, &w.Guestbook.IsActive) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return Website{}, err | ||||||
|  | 	} | ||||||
|  | 	// handle if Deleted is null | ||||||
|  | 	if t.Valid { | ||||||
|  | 		w.Deleted = t.Time | ||||||
|  | 	} | ||||||
|  | 	return w, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) GetAll(userId int64) ([]Website, error) { | ||||||
|  | 	stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, w.Deleted, | ||||||
|  | 	g.Id, g.ShortId, g.Created, g.IsDeleted, g.IsActive | ||||||
|  | 	FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId | ||||||
|  | 	WHERE w.UserId = ?` | ||||||
|  | 	rows, err := m.DB.Query(stmt, userId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var websites []Website | ||||||
|  | 	for rows.Next() { | ||||||
|  | 		var t sql.NullTime | ||||||
|  | 		var w Website | ||||||
|  | 		err := rows.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, &t, | ||||||
|  | 			&w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsDeleted, &w.Guestbook.IsActive) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		websites = append(websites, w) | ||||||
|  | 	} | ||||||
|  | 	if err = rows.Err(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return websites, nil | ||||||
|  | } | ||||||
| @ -1,27 +0,0 @@ | |||||||
| {{ define "base" }} |  | ||||||
| <!DOCTYPE html> |  | ||||||
| <html lang="en"> |  | ||||||
|     <head> |  | ||||||
|         <title>{{ template "title" }} - webweav.ing</title> |  | ||||||
|         <meta charset="UTF-8"> |  | ||||||
|         <meta name="viewport" content="width=device-width, initial-scale=1"> |  | ||||||
|         <link href="/static/css/style.css" rel="stylesheet"> |  | ||||||
|         <script src="/static/js/htmx.min.js"></script> |  | ||||||
|     </head> |  | ||||||
|     <body> |  | ||||||
|         <header> |  | ||||||
|             <h1><a href="/">webweav.ing</a></h1> |  | ||||||
|         </header> |  | ||||||
|         {{ template "nav" . }} |  | ||||||
|         <main> |  | ||||||
|             {{ with .Flash }} |  | ||||||
|                 <div class="flash">{{ . }}</div> |  | ||||||
|             {{ end }} |  | ||||||
|             {{ template "main" . }} |  | ||||||
|         </main> |  | ||||||
|         <footer> |  | ||||||
|             <p>A 32bit.cafe Project</p> |  | ||||||
|         </footer> |  | ||||||
|     </body> |  | ||||||
| </html> |  | ||||||
| {{ end }} |  | ||||||
| @ -1,6 +0,0 @@ | |||||||
| <form hx-post="/guestbooks/create" hx-target="closest div"> |  | ||||||
|     <input type="hidden" name="csrf_token" value="{{.CSRFToken}}"> |  | ||||||
|     <label for="siteurl">Site URL: </label> |  | ||||||
|     <input type="text" name="siteurl" id="siteurl" required /> |  | ||||||
|     <button type="submit">Submit</button> |  | ||||||
| </form> |  | ||||||
| @ -1 +0,0 @@ | |||||||
| <button hx-get="/guestbooks/create" hx-target="closest">New Guestbook</button> |  | ||||||
| @ -1,7 +0,0 @@ | |||||||
| <ul id="guestbooks" hx-get="/guestbooks" hx-trigger="newGuestbook from:body"> |  | ||||||
|     {{ range .Guestbooks }} |  | ||||||
|     <li><a href="/guestbooks/{{ shortIdToSlug .ShortId }}">{{ with .SiteUrl }}{{ . }}{{ else }}Untitled{{ end }}</a></li> |  | ||||||
|     {{ else }} |  | ||||||
|     <p>No Guestbooks yet</p> |  | ||||||
|     {{ end }} |  | ||||||
| </ul> |  | ||||||
| @ -1,5 +0,0 @@ | |||||||
| {{ with .Guestbook }} |  | ||||||
| <li> |  | ||||||
|     <a href="/guestbooks/{{ shortIdToSlug .ShortId }}">{{ with .SiteUrl }}{{ . }}{{ else }}Untitled{{ end }}</a> |  | ||||||
| </li> |  | ||||||
| {{ end }} |  | ||||||
| @ -1,37 +0,0 @@ | |||||||
| {{ define "title" }}New Comment{{ end }} |  | ||||||
| {{ define "main" }} |  | ||||||
| <form action="/guestbooks/{{ shortIdToSlug .Guestbook.ShortId }}/comments/create" method="post"> |  | ||||||
|     <input type="hidden" name="csrf_token" value="{{.CSRFToken}}"> |  | ||||||
|     <div> |  | ||||||
|         <label for="authorname">Name: </label> |  | ||||||
|         {{ with .Form.FieldErrors.authorName }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <input type="text" name="authorname" id="authorname" > |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <label for="authoremail">Email: </label> |  | ||||||
|         {{ with .Form.FieldErrors.authorEmail }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <input type="text" name="authoremail" id="authoremail" > |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <label for="authorsite">Site Url: </label> |  | ||||||
|         {{ with .Form.FieldErrors.authorSite }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <input type="text" name="authorsite" id="authorsite" > |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <label for="content">Comment: </label> |  | ||||||
|         {{ with .Form.FieldErrors.content }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <textarea name="content" id="content"></textarea> |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <input type="submit" value="Post"> |  | ||||||
|     </div> |  | ||||||
| </form> |  | ||||||
| {{ end }} |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| {{ define "title" }} Guestbook View {{ end }} |  | ||||||
| {{ define "main" }} |  | ||||||
| <h1>Guestbook for {{ .Guestbook.SiteUrl }}</h1> |  | ||||||
| <p> |  | ||||||
|     <a href="/guestbooks/{{ shortIdToSlug .Guestbook.ShortId }}/comments/create">New Comment</a> |  | ||||||
| </p> |  | ||||||
| {{ range .Comments }} |  | ||||||
|     <div> |  | ||||||
|         <strong> {{ .AuthorName }} </strong> |  | ||||||
|         {{ .Created.Local.Format "01-02-2006 03:04PM" }} |  | ||||||
|         <p> |  | ||||||
|             {{ .CommentText }} |  | ||||||
|         </p> |  | ||||||
|     </div> |  | ||||||
| {{ else }} |  | ||||||
| <p>No comments yet!</p> |  | ||||||
| {{ end }} |  | ||||||
| {{ end }} |  | ||||||
| @ -1,4 +0,0 @@ | |||||||
| {{ define "title" }}Create a Guestbook{{ end }} |  | ||||||
| {{ define "main" }} |  | ||||||
| {{ template "guestbookcreate" }} |  | ||||||
| {{ end }} |  | ||||||
| @ -1,14 +0,0 @@ | |||||||
| {{ define "title" }} Guestbooks  {{ end }} |  | ||||||
| {{ define "main" }} |  | ||||||
| <h1>Guestbooks run by {{ .User.Username }}</h1> |  | ||||||
| <div> |  | ||||||
|     <button hx-get="/guestbooks/create" hx-target="closest div">New Guestbook</button> |  | ||||||
| </div> |  | ||||||
| <ul id="guestbooks" hx-get="/guestbooks" hx-trigger="newGuestbook from:body" hx-swap="outerHTML"> |  | ||||||
|     {{ range .Guestbooks }} |  | ||||||
|     <li><a href="/guestbooks/{{ shortIdToSlug .ShortId }}">{{ with .SiteUrl }}{{ . }}{{ else }}Untitled{{ end }}</a></li> |  | ||||||
|     {{ else }} |  | ||||||
|     <p>No Guestbooks yet</p> |  | ||||||
|     {{ end }} |  | ||||||
| </ul> |  | ||||||
| {{ end }} |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| {{ define "title" }}Home{{ end }} |  | ||||||
| {{ define "main" }} |  | ||||||
| {{ if .IsAuthenticated }} |  | ||||||
| <h2>Tools</h2> |  | ||||||
| <p> |  | ||||||
|     <a href="/guestbooks">Guestbooks</a> |  | ||||||
| </p> |  | ||||||
| {{ else }} |  | ||||||
| <h2>Welcome</h2> |  | ||||||
| Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>. |  | ||||||
| {{ end }} |  | ||||||
| {{ end }} |  | ||||||
| @ -1,26 +0,0 @@ | |||||||
| {{define "title"}}Login{{end}} |  | ||||||
| {{define "main"}} |  | ||||||
| <form action="/users/login" method="POST" novalidate> |  | ||||||
|     <input type="hidden" name="csrf_token" value="{{.CSRFToken}}"> |  | ||||||
|     {{ range .Form.NonFieldErrors }} |  | ||||||
|         <div class="error">{{.}}</div> |  | ||||||
|     {{ end }} |  | ||||||
|     <div> |  | ||||||
|         <label>Email: </label> |  | ||||||
|         {{ with .Form.FieldErrors.email }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <input type="email" name="email" value="{{.Form.Email}}"> |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <label>Password: </label> |  | ||||||
|         {{ with .Form.FieldErrors.password }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <input type="password" name="password"> |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <input type="submit" value="login"> |  | ||||||
|     </div> |  | ||||||
| </form> |  | ||||||
| {{end}} |  | ||||||
| @ -1,5 +0,0 @@ | |||||||
| {{ define "title" }}{{ .User.Username }}{{ end }} |  | ||||||
| {{ define "main" }} |  | ||||||
| <h1>{{ .User.Username }}</h1> |  | ||||||
| <p>{{ .User.Email }}</p> |  | ||||||
| {{ end }} |  | ||||||
| @ -1,30 +0,0 @@ | |||||||
| {{ define "title" }}User Registration{{ end }} |  | ||||||
| {{ define "main" }} |  | ||||||
| <form action="/users/register" method="post"> |  | ||||||
|     <input type="hidden" name="csrf_token" value="{{.CSRFToken}}"> |  | ||||||
|     <div> |  | ||||||
|         <label for="username">Username: </label> |  | ||||||
|         {{ with .Form.FieldErrors.name }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <input type="text" name="username" id="username" value="{{ .Form.Name }}" required /> |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <label for="email">Email: </label> |  | ||||||
|         {{ with .Form.FieldErrors.email }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <input type="text" name="email" id="email" value="{{ .Form.Email }}" required /> |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <label for="password">Password: </label> |  | ||||||
|         {{ with .Form.FieldErrors.password }} |  | ||||||
|             <label class="error">{{.}}</label> |  | ||||||
|         {{ end }} |  | ||||||
|         <input type="password" name="password" id="password" /> |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         <input type="submit" value="Register"/> |  | ||||||
|     </div>  |  | ||||||
| </form> |  | ||||||
| {{ end }} |  | ||||||
| @ -1,9 +0,0 @@ | |||||||
| {{ define "title" }}Users{{ end }} |  | ||||||
| {{ define "main" }} |  | ||||||
|     <h1>Users</h1> |  | ||||||
|     {{ range .Users }} |  | ||||||
|         <p> |  | ||||||
|         <a href="/users/{{ shortIdToSlug .ShortId }}">{{ .Username }}</a> |  | ||||||
|         </p> |  | ||||||
|     {{ end }} |  | ||||||
| {{ end }} |  | ||||||
| @ -1,8 +0,0 @@ | |||||||
| {{ define "guestbookcreate" }} |  | ||||||
| <form action="/guestbooks/create" method="post"> |  | ||||||
|     <input type="hidden" name="csrf_token" value="{{.CSRFToken}}"> |  | ||||||
|     <label for="siteurl">Site URL: </label> |  | ||||||
|     <input type="text" name="siteurl" id="siteurl" required /> |  | ||||||
|     <input type="submit" /> |  | ||||||
| </form> |  | ||||||
| {{ end }} |  | ||||||
| @ -1,22 +0,0 @@ | |||||||
| {{ define "nav" }} |  | ||||||
| <nav> |  | ||||||
|     <div> |  | ||||||
|     <a href="/">Home</a> |  | ||||||
|     <a href="/users">Users</a> |  | ||||||
|     </div> |  | ||||||
|     <div> |  | ||||||
|         {{ if .IsAuthenticated }} |  | ||||||
|             {{ with .CurrentUser }} |  | ||||||
|                 Welcome, {{ .Username }} |  | ||||||
|             {{ end }} |  | ||||||
|             <form action="/users/logout" method="post"> |  | ||||||
|                 <input type="hidden" name="csrf_token" value="{{.CSRFToken}}"> |  | ||||||
|                 <button>Logout</button> |  | ||||||
|             </form> |  | ||||||
|         {{ else }} |  | ||||||
|             <a href="/users/register">Register</a> |  | ||||||
|             <a href="/users/login">Login</a> |  | ||||||
|         {{ end }} |  | ||||||
|     </div> |  | ||||||
| </nav> |  | ||||||
| {{ end }} |  | ||||||
| @ -30,14 +30,13 @@ templ commonHeader() { | |||||||
| templ topNav(data CommonData) { | templ topNav(data CommonData) { | ||||||
|     <nav> |     <nav> | ||||||
|         <div> |         <div> | ||||||
|             <ul> |             if data.IsAuthenticated { | ||||||
|                 <li><a href="/guestbooks">Guestbooks</a></li> |                 Welcome, { data.CurrentUser.Username } | ||||||
|                 <li>RSS Feeds</li> |             } | ||||||
|             </ul> |  | ||||||
|         </div> |         </div> | ||||||
|         <div> |         <div> | ||||||
|             if data.IsAuthenticated { |             if data.IsAuthenticated { | ||||||
|                 Welcome, { data.CurrentUser.Username } |                 <a href="/websites">My Websites</a> |  | ||||||
|                 <a href="/users/settings">Settings</a> |  |                 <a href="/users/settings">Settings</a> |  | ||||||
|                 <form action="/users/logout" method="post"> |                 <form action="/users/logout" method="post"> | ||||||
|                     <input type="hidden" name="csrf_token" value={ data.CSRFToken }> |                     <input type="hidden" name="csrf_token" value={ data.CSRFToken }> | ||||||
|  | |||||||
| @ -79,7 +79,7 @@ func topNav(data CommonData) templ.Component { | |||||||
| 			templ_7745c5c3_Var2 = templ.NopComponent | 			templ_7745c5c3_Var2 = templ.NopComponent | ||||||
| 		} | 		} | ||||||
| 		ctx = templ.ClearChildren(ctx) | 		ctx = templ.ClearChildren(ctx) | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav><div><ul><li><a href=\"/guestbooks\">Guestbooks</a></li><li>RSS Feeds</li></ul></div><div>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav><div>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -91,36 +91,42 @@ func topNav(data CommonData) templ.Component { | |||||||
| 			var templ_7745c5c3_Var3 string | 			var templ_7745c5c3_Var3 string | ||||||
| 			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username) | 			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 40, Col: 52} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 34, Col: 52} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " <a href=\"/users/settings\">Settings</a> | <form action=\"/users/logout\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"") | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div><div>") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		if data.IsAuthenticated { | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<a href=\"/websites\">My Websites</a> |  <a href=\"/users/settings\">Settings</a> | <form action=\"/users/logout\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			var templ_7745c5c3_Var4 string | 			var templ_7745c5c3_Var4 string | ||||||
| 			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) | 			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 43, Col: 81} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 42, Col: 81} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"> <button>Logout</button></form>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"> <button>Logout</button></form>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<a href=\"/users/register\">Create an Account</a> |  <a href=\"/users/login\">Login</a>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"/users/register\">Create an Account</a> |  <a href=\"/users/login\">Login</a>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div></nav>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></nav>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -149,7 +155,7 @@ func commonFooter() templ.Component { | |||||||
| 			templ_7745c5c3_Var5 = templ.NopComponent | 			templ_7745c5c3_Var5 = templ.NopComponent | ||||||
| 		} | 		} | ||||||
| 		ctx = templ.ClearChildren(ctx) | 		ctx = templ.ClearChildren(ctx) | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<footer><p>Generated with Templ</p><p>A <a href=\"https://32bit.cafe\">32-bit cafe</a> Project</p></footer>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<footer><p>Generated with Templ</p><p>A <a href=\"https://32bit.cafe\">32-bit cafe</a> Project</p></footer>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -178,20 +184,20 @@ func base(title string, data CommonData) templ.Component { | |||||||
| 			templ_7745c5c3_Var6 = templ.NopComponent | 			templ_7745c5c3_Var6 = templ.NopComponent | ||||||
| 		} | 		} | ||||||
| 		ctx = templ.ClearChildren(ctx) | 		ctx = templ.ClearChildren(ctx) | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<!doctype html><html lang=\"en\"><head><title>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<!doctype html><html lang=\"en\"><head><title>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		var templ_7745c5c3_Var7 string | 		var templ_7745c5c3_Var7 string | ||||||
| 		templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) | 		templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 66, Col: 26} | 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 65, Col: 26} | ||||||
| 		} | 		} | ||||||
| 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -203,25 +209,25 @@ func base(title string, data CommonData) templ.Component { | |||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<main>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<main>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		if data.Flash != "" { | 		if data.Flash != "" { | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"flash\">") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"flash\">") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			var templ_7745c5c3_Var8 string | 			var templ_7745c5c3_Var8 string | ||||||
| 			templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) | 			templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 77, Col: 51} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 76, Col: 51} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| @ -230,7 +236,7 @@ func base(title string, data CommonData) templ.Component { | |||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</main>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</main>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -238,7 +244,7 @@ func base(title string, data CommonData) templ.Component { | |||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</body></html>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</body></html>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| <header><h1><a href=\"/\">webweav.ing</a></h1></header> | <header><h1><a href=\"/\">webweav.ing</a></h1></header> | ||||||
| <nav><div><ul><li><a href=\"/guestbooks\">Guestbooks</a></li><li>RSS Feeds</li></ul></div><div> | <nav><div> | ||||||
| Welcome,  | Welcome,  | ||||||
|  <a href=\"/users/settings\">Settings</a> | <form action=\"/users/logout\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\" | </div><div> | ||||||
|  | <a href=\"/websites\">My Websites</a> |  <a href=\"/users/settings\">Settings</a> | <form action=\"/users/logout\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\" | ||||||
| \"> <button>Logout</button></form> | \"> <button>Logout</button></form> | ||||||
| <a href=\"/users/register\">Create an Account</a> |  <a href=\"/users/login\">Login</a> | <a href=\"/users/register\">Create an Account</a> |  <a href=\"/users/login\">Login</a> | ||||||
| </div></nav> | </div></nav> | ||||||
|  | |||||||
| @ -22,11 +22,6 @@ templ gbList(guestbooks []models.Guestbook) { | |||||||
|         <ul id="guestbooks" hx-get="/guestbooks" hx-trigger="newGuestbook from:body" hx-swap="outerHTML"> |         <ul id="guestbooks" hx-get="/guestbooks" hx-trigger="newGuestbook from:body" hx-swap="outerHTML"> | ||||||
|             for _, gb := range guestbooks { |             for _, gb := range guestbooks { | ||||||
|                 <li><a href={ templ.URL(gbUrl(gb) + "/dashboard") }> |                 <li><a href={ templ.URL(gbUrl(gb) + "/dashboard") }> | ||||||
|                     if len(gb.SiteUrl) == 0 { |  | ||||||
|                         Untitled |  | ||||||
|                     } else { |  | ||||||
|                         { gb.SiteUrl } |  | ||||||
|                     } |  | ||||||
|                 </a></li> |                 </a></li> | ||||||
| 
 | 
 | ||||||
|             } |             } | ||||||
| @ -62,56 +57,12 @@ templ GuestbookCreate(title string, data CommonData) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ sidebar(guestbook models.Guestbook) { | templ GuestbookDashboardCommentsView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment) { | ||||||
|     {{ dashUrl := gbUrl(guestbook) + "/dashboard" }} |  | ||||||
|     <nav> |  | ||||||
|         <div> |  | ||||||
|             <a href={ templ.URL(gbUrl(guestbook)) } target="_blank">View Guestbook</a> |  | ||||||
|             <h3>Messages</h3> |  | ||||||
|             <ul> |  | ||||||
|                 <li><a href={ templ.URL(dashUrl) }>Dashboard</a></li> |  | ||||||
|                 <li><a href={ templ.URL(dashUrl + "/comments") }>Manage messages</a></li> |  | ||||||
|                 <li><a href={ templ.URL(dashUrl + "/comments/queue") }>Review message queue</a></li> |  | ||||||
|                 <li><a href={ templ.URL(dashUrl + "/blocklist") }>Block users</a></li> |  | ||||||
|                 <li><a href={ templ.URL(dashUrl + "/comments/trash") }>Trash</a></li> |  | ||||||
|             </ul> |  | ||||||
|         </div> |  | ||||||
|         <div> |  | ||||||
|             <h3>Design</h3> |  | ||||||
|             <ul> |  | ||||||
|                 <li><a href={ templ.URL(dashUrl + "/themes") }>Themes</a></li> |  | ||||||
|                 <li><a href={ templ.URL(dashUrl + "/customize") }>Custom CSS</a></li> |  | ||||||
|             </ul> |  | ||||||
|         </div> |  | ||||||
|         <div> |  | ||||||
|             <h3>Account</h3> |  | ||||||
|             <ul> |  | ||||||
|                 <li><a href="/users/settings">Settings</a></li> |  | ||||||
|                 <li><a href="/users/privacy">Privacy</a></li> |  | ||||||
|                 <li><a href="/help">Help</a></li> |  | ||||||
|             </ul> |  | ||||||
|         </div> |  | ||||||
|     </nav> |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| templ GuestbookDashboardView(title string, data CommonData, guestbook models.Guestbook, comments []models.GuestbookComment) { |  | ||||||
|     @base(title, data) { |     @base(title, data) { | ||||||
|         <div id="dashboard"> |         <div id="dashboard"> | ||||||
|             @sidebar(guestbook) |             @wSidebar(website) | ||||||
|             <div> |             <div> | ||||||
|                 <h1>Guestbook for { guestbook.SiteUrl }</h1> |                 <h1>Comments on { website.SiteUrl }</h1> | ||||||
|                 <p>Stats and stuff will go here</p> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| templ GuestbookDashboardCommentsView(title string, data CommonData, guestbook models.Guestbook, comments []models.GuestbookComment) { |  | ||||||
|     @base(title, data) { |  | ||||||
|         <div id="dashboard"> |  | ||||||
|             @sidebar(guestbook) |  | ||||||
|             <div> |  | ||||||
|                 <h1>Comments on { guestbook.SiteUrl }</h1> |  | ||||||
|                 if len(comments) == 0 { |                 if len(comments) == 0 { | ||||||
|                     <p>No comments yet!</p> |                     <p>No comments yet!</p> | ||||||
|                 } |                 } | ||||||
| @ -129,65 +80,90 @@ templ GuestbookDashboardCommentsView(title string, data CommonData, guestbook mo | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ commentForm(data CommonData, gb models.Guestbook, form forms.CommentCreateForm) { | templ commentForm(form forms.CommentCreateForm) { | ||||||
|     {{ postUrl := fmt.Sprintf("/guestbooks/%s/comments/create", shortIdToSlug(gb.ShortId)) }} |     <div> | ||||||
|     <form action={ templ.URL(postUrl) } method="post"> |         <label for="authorname">Name: </label> | ||||||
|         <input type="hidden" name="csrf_token" value={data.CSRFToken}> |         {{ error, exists := form.FieldErrors["authorName"] }} | ||||||
|         <div> |         if exists { | ||||||
|             <label for="authorname">Name: </label> |         <label class="error">{ error }</label> | ||||||
|             {{ error, exists := form.FieldErrors["authorName"] }} |         } | ||||||
|             if exists { |         <input type="text" name="authorname" id="authorname"> | ||||||
|                 <label class="error">{ error }</label> |     </div> | ||||||
|             } |     <div> | ||||||
|             <input type="text" name="authorname" id="authorname" > |         <label for="authoremail">Email: </label> | ||||||
|         </div> |         {{ error, exists = form.FieldErrors["authorEmail"] }} | ||||||
|         <div> |         if exists { | ||||||
|             <label for="authoremail">Email: </label> |         <label class="error">{ error }</label> | ||||||
|             {{ error, exists = form.FieldErrors["authorEmail"] }} |         } | ||||||
|             if exists { |         <input type="text" name="authoremail" id="authoremail"> | ||||||
|                 <label class="error">{ error }</label> |     </div> | ||||||
|             } |     <div> | ||||||
|             <input type="text" name="authoremail" id="authoremail" > |         <label for="authorsite">Site Url: </label> | ||||||
|         </div> |         {{ error, exists = form.FieldErrors["authorSite"] }} | ||||||
|         <div> |         if exists { | ||||||
|             <label for="authorsite">Site Url: </label> |         <label class="error">{ error }</label> | ||||||
|             {{ error, exists = form.FieldErrors["authorSite"] }} |         } | ||||||
|             if exists { |         <input type="text" name="authorsite" id="authorsite"> | ||||||
|                 <label class="error">{ error }</label> |     </div> | ||||||
|             } |     <div> | ||||||
|             <input type="text" name="authorsite" id="authorsite" > |         <label for="content">Comment: </label> | ||||||
|         </div> |         {{ error, exists = form.FieldErrors["content"] }} | ||||||
|         <div> |         if exists { | ||||||
|             <label for="content">Comment: </label> |         <label class="error">{ error }</label> | ||||||
|             {{ error, exists = form.FieldErrors["content"] }} |         } | ||||||
|             if exists { |         <textarea name="content" id="content"></textarea> | ||||||
|                 <label class="error">{ error }</label> |     </div> | ||||||
|             } |     <div> | ||||||
|             <textarea name="content" id="content"></textarea> |         <input type="submit" value="Submit"> | ||||||
|         </div> |     </div> | ||||||
|         <div> | } | ||||||
|             <input type="submit" value="Submit"> |  | ||||||
|         </div> |  | ||||||
|     </form> |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
| templ GuestbookView(title string, data CommonData, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) { | templ GuestbookCommentList(comments []models.GuestbookComment) { | ||||||
|  |     if len(comments) == 0 { | ||||||
|  |         <p>No comments yet!</p> | ||||||
|  |     } | ||||||
|  |     for _, c := range comments { | ||||||
|  |         <div> | ||||||
|  |             <strong>{ c.AuthorName }</strong> | ||||||
|  |             { c.Created.Format("01-02-2006 03:04PM") } | ||||||
|  |             <p> | ||||||
|  |                 { c.CommentText } | ||||||
|  |             </p> | ||||||
|  |         </div> | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ GuestbookView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) { | ||||||
|  |     {{ postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) }} | ||||||
|  |     if data.IsHtmx { | ||||||
|  |         @commentForm(form) | ||||||
|  |     } else { | ||||||
|         <div id="main"> |         <div id="main"> | ||||||
|             <div> |             <div> | ||||||
|                 <h1>Guestbook for { guestbook.SiteUrl }</h1> |                 <h1>Guestbook for { website.SiteUrl }</h1> | ||||||
|                 @commentForm(data, guestbook, form) |                 <form action={ templ.URL(postUrl) } method="post"> | ||||||
|                 if len(comments) == 0 { |                     <input type="hidden" name="csrf_token" value={data.CSRFToken}> | ||||||
|                     <p>No comments yet!</p> |                     @commentForm(form) | ||||||
|                 } |                 </form> | ||||||
|                 for  _, c := range comments { |             </div> | ||||||
|                     <div> |             <div id="comments"> | ||||||
|                         <strong>{ c.AuthorName }</strong> |                 @GuestbookCommentList(comments) | ||||||
|                         { c.Created.Format("01-02-2006 03:04PM") } |  | ||||||
|                         <p> |  | ||||||
|                             { c.CommentText } |  | ||||||
|                         </p> |  | ||||||
|                     </div> |  | ||||||
|                 } |  | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ CreateGuestbookComment(title string, data CommonData, website models.Website, guestbook models.Guestbook, form forms.CommentCreateForm) { | ||||||
|  |     {{ postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) }} | ||||||
|  |     if data.IsHtmx { | ||||||
|  |         <form hx-post={ postUrl } hx-target="closest div"> | ||||||
|  |             @commentForm(form) | ||||||
|  |         </form> | ||||||
|  |     } else { | ||||||
|  |         @base(title, data) { | ||||||
|  |             <form action={ templ.URL(postUrl) } method="post"> | ||||||
|  |                 @commentForm(form) | ||||||
|  |             </form> | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -3,27 +3,13 @@ | |||||||
| <p>No Guestbooks yet</p> | <p>No Guestbooks yet</p> | ||||||
| <ul id=\"guestbooks\" hx-get=\"/guestbooks\" hx-trigger=\"newGuestbook from:body\" hx-swap=\"outerHTML\"> | <ul id=\"guestbooks\" hx-get=\"/guestbooks\" hx-trigger=\"newGuestbook from:body\" hx-swap=\"outerHTML\"> | ||||||
| <li><a href=\" | <li><a href=\" | ||||||
| \"> | \"></a></li> | ||||||
| Untitled |  | ||||||
| </a></li> |  | ||||||
| </ul> | </ul> | ||||||
| <h1>My Guestbooks</h1><div><button hx-get=\"/guestbooks/create\" hx-target=\"closest div\">New Guestbook</button></div> | <h1>My Guestbooks</h1><div><button hx-get=\"/guestbooks/create\" hx-target=\"closest div\">New Guestbook</button></div> | ||||||
| <form hx-post=\"/guestbooks/create\" hx-target=\"closest div\"> | <form hx-post=\"/guestbooks/create\" hx-target=\"closest div\"> | ||||||
| </form> | </form> | ||||||
| <form action=\"/guestbooks/create\" method=\"post\"> | <form action=\"/guestbooks/create\" method=\"post\"> | ||||||
| </form> | </form> | ||||||
| <nav><div><a href=\" |  | ||||||
| \" target=\"_blank\">View Guestbook</a><h3>Messages</h3><ul><li><a href=\" |  | ||||||
| \">Dashboard</a></li><li><a href=\" |  | ||||||
| \">Manage messages</a></li><li><a href=\" |  | ||||||
| \">Review message queue</a></li><li><a href=\" |  | ||||||
| \">Block users</a></li><li><a href=\" |  | ||||||
| \">Trash</a></li></ul></div><div><h3>Design</h3><ul><li><a href=\" |  | ||||||
| \">Themes</a></li><li><a href=\" |  | ||||||
| \">Custom CSS</a></li></ul></div><div><h3>Account</h3><ul><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"/users/privacy\">Privacy</a></li><li><a href=\"/help\">Help</a></li></ul></div></nav> |  | ||||||
| <div id=\"dashboard\"> |  | ||||||
| <div><h1>Guestbook for  |  | ||||||
| </h1><p>Stats and stuff will go here</p></div></div> |  | ||||||
| <div id=\"dashboard\"> | <div id=\"dashboard\"> | ||||||
| <div><h1>Comments on  | <div><h1>Comments on  | ||||||
| </h1> | </h1> | ||||||
| @ -33,9 +19,7 @@ Untitled | |||||||
| <p> | <p> | ||||||
| </p></div> | </p></div> | ||||||
| </div></div> | </div></div> | ||||||
| <form action=\" | <div><label for=\"authorname\">Name: </label> | ||||||
| \" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\" |  | ||||||
| \"><div><label for=\"authorname\">Name: </label> |  | ||||||
| <label class=\"error\"> | <label class=\"error\"> | ||||||
| </label>  | </label>  | ||||||
| <input type=\"text\" name=\"authorname\" id=\"authorname\"></div><div><label for=\"authoremail\">Email: </label> | <input type=\"text\" name=\"authorname\" id=\"authorname\"></div><div><label for=\"authoremail\">Email: </label> | ||||||
| @ -47,12 +31,21 @@ Untitled | |||||||
| <input type=\"text\" name=\"authorsite\" id=\"authorsite\"></div><div><label for=\"content\">Comment: </label> | <input type=\"text\" name=\"authorsite\" id=\"authorsite\"></div><div><label for=\"content\">Comment: </label> | ||||||
| <label class=\"error\"> | <label class=\"error\"> | ||||||
| </label>  | </label>  | ||||||
| <textarea name=\"content\" id=\"content\"></textarea></div><div><input type=\"submit\" value=\"Submit\"></div></form> | <textarea name=\"content\" id=\"content\"></textarea></div><div><input type=\"submit\" value=\"Submit\"></div> | ||||||
| <div id=\"main\"><div><h1>Guestbook for  |  | ||||||
| </h1> |  | ||||||
| <p>No comments yet!</p> | <p>No comments yet!</p> | ||||||
| <div><strong> | <div><strong> | ||||||
| </strong>  | </strong>  | ||||||
| <p> | <p> | ||||||
| </p></div> | </p></div> | ||||||
| </div></div> | <div id=\"main\"><div><h1>Guestbook for  | ||||||
|  | </h1><form action=\" | ||||||
|  | \" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\" | ||||||
|  | \"> | ||||||
|  | </form></div><div id=\"comments\"> | ||||||
|  | </div></div> | ||||||
|  | <form hx-post=\" | ||||||
|  | \" hx-target=\"closest div\"> | ||||||
|  | </form> | ||||||
|  | <form action=\" | ||||||
|  | \" method=\"post\"> | ||||||
|  | </form> | ||||||
| @ -1,25 +1,10 @@ | |||||||
| package views | package views | ||||||
| 
 | 
 | ||||||
| templ loggedInHome() { |  | ||||||
|     <h2>Tools</h2> |  | ||||||
|     <p> |  | ||||||
|         <a href="/guestbooks">Guestbooks</a> |  | ||||||
|     </p> |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| templ loggedOutHome() { |  | ||||||
|     <h2>Welcome</h2> |  | ||||||
|     <p> |  | ||||||
|         Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>. |  | ||||||
|     </p> |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| templ Home(title string, data CommonData) { | templ Home(title string, data CommonData) { | ||||||
|     @base(title, data) { |     @base(title, data) { | ||||||
|         if data.IsAuthenticated { |         <h2>Welcome</h2> | ||||||
|             @loggedInHome() |         <p> | ||||||
|         } else { |             Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>. | ||||||
|             @loggedOutHome() |         </p> | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ package views | |||||||
| import "github.com/a-h/templ" | import "github.com/a-h/templ" | ||||||
| import templruntime "github.com/a-h/templ/runtime" | import templruntime "github.com/a-h/templ/runtime" | ||||||
| 
 | 
 | ||||||
| func loggedInHome() templ.Component { | func Home(title string, data CommonData) templ.Component { | ||||||
| 	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | 	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 | 		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | ||||||
| 		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | 		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | ||||||
| @ -29,65 +29,7 @@ func loggedInHome() templ.Component { | |||||||
| 			templ_7745c5c3_Var1 = templ.NopComponent | 			templ_7745c5c3_Var1 = templ.NopComponent | ||||||
| 		} | 		} | ||||||
| 		ctx = templ.ClearChildren(ctx) | 		ctx = templ.ClearChildren(ctx) | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h2>Tools</h2><p><a href=\"/guestbooks\">Guestbooks</a></p>") | 		templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | ||||||
| 		if templ_7745c5c3_Err != nil { |  | ||||||
| 			return templ_7745c5c3_Err |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func loggedOutHome() 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_Var2 := templ.GetChildren(ctx) |  | ||||||
| 		if templ_7745c5c3_Var2 == nil { |  | ||||||
| 			templ_7745c5c3_Var2 = templ.NopComponent |  | ||||||
| 		} |  | ||||||
| 		ctx = templ.ClearChildren(ctx) |  | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p>") |  | ||||||
| 		if templ_7745c5c3_Err != nil { |  | ||||||
| 			return templ_7745c5c3_Err |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func Home(title string, data CommonData) 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_Var4 := 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_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | ||||||
| 			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | 			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | ||||||
| 			if !templ_7745c5c3_IsBuffer { | 			if !templ_7745c5c3_IsBuffer { | ||||||
| @ -99,20 +41,13 @@ func Home(title string, data CommonData) templ.Component { | |||||||
| 				}() | 				}() | ||||||
| 			} | 			} | ||||||
| 			ctx = templ.InitializeContext(ctx) | 			ctx = templ.InitializeContext(ctx) | ||||||
| 			if data.IsAuthenticated { | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p>") | ||||||
| 				templ_7745c5c3_Err = loggedInHome().Render(ctx, templ_7745c5c3_Buffer) | 			if templ_7745c5c3_Err != nil { | ||||||
| 				if templ_7745c5c3_Err != nil { | 				return templ_7745c5c3_Err | ||||||
| 					return templ_7745c5c3_Err |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				templ_7745c5c3_Err = loggedOutHome().Render(ctx, templ_7745c5c3_Buffer) |  | ||||||
| 				if templ_7745c5c3_Err != nil { |  | ||||||
| 					return templ_7745c5c3_Err |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 			return nil | 			return nil | ||||||
| 		}) | 		}) | ||||||
| 		templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer) | 		templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -1,2 +1 @@ | |||||||
| <h2>Tools</h2><p><a href=\"/guestbooks\">Guestbooks</a></p> |  | ||||||
| <h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p> | <h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p> | ||||||
							
								
								
									
										139
									
								
								ui/views/websites.templ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								ui/views/websites.templ
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,139 @@ | |||||||
|  | package views | ||||||
|  | 
 | ||||||
|  | import "fmt" | ||||||
|  | import "git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | import "git.32bit.cafe/32bitcafe/guestbook/internal/forms" | ||||||
|  | 
 | ||||||
|  | func wUrl (w models.Website) string { | ||||||
|  |     return fmt.Sprintf("/websites/%s", shortIdToSlug(w.ShortId)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ wSidebar(website models.Website) { | ||||||
|  |     {{ dashUrl := wUrl(website) + "/dashboard" }} | ||||||
|  |     {{ gbUrl := wUrl(website) + "/guestbook" }} | ||||||
|  |     <nav> | ||||||
|  |         <div> | ||||||
|  |             <ul> | ||||||
|  |                 <li><a href={ templ.URL(dashUrl) }>Dashboard</a></li> | ||||||
|  |                 <li><a href={ templ.URL(website.SiteUrl) }>View Website</a></li> | ||||||
|  |             </ul> | ||||||
|  |             <h3>Guestbook</h3> | ||||||
|  |             <ul> | ||||||
|  |                 <li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li> | ||||||
|  |             </ul> | ||||||
|  |             <ul> | ||||||
|  |                 <li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage messages</a></li> | ||||||
|  |                 <li><a href={ templ.URL(dashUrl + "/guestbook/comments/queue") }>Review message queue</a></li> | ||||||
|  |                 <li><a href={ templ.URL(dashUrl + "/guestbook/blocklist") }>Block users</a></li> | ||||||
|  |                 <li><a href={ templ.URL(dashUrl + "/guestbook/comments/trash") }>Trash</a></li> | ||||||
|  |             </ul> | ||||||
|  |             <ul> | ||||||
|  |                 <li><a href={ templ.URL(dashUrl + "/guestbook/themes") }>Themes</a></li> | ||||||
|  |                 <li><a href={ templ.URL(dashUrl + "/guestbook/customize") }>Custom CSS</a></li> | ||||||
|  |             </ul> | ||||||
|  |         </div> | ||||||
|  |         <div> | ||||||
|  |             <h3>Feeds</h3> | ||||||
|  |             <p>Coming Soon</p> | ||||||
|  |         </div> | ||||||
|  |         <div> | ||||||
|  |             <h3>Account</h3> | ||||||
|  |             <ul> | ||||||
|  |                 <li><a href="/users/settings">Settings</a></li> | ||||||
|  |                 <li><a href="/users/privacy">Privacy</a></li> | ||||||
|  |                 <li><a href="/help">Help</a></li> | ||||||
|  |             </ul> | ||||||
|  |         </div> | ||||||
|  |     </nav> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ displayWebsites (websites []models.Website) { | ||||||
|  |     if len(websites) == 0 { | ||||||
|  |         <p>No Websites yet. <a href="">Register a website.</a></p> | ||||||
|  |     } else { | ||||||
|  |         <ul id="websites" hx-get="/websites" hx-trigger="newWebsite from:body" hx-swap="outerHTML"> | ||||||
|  |             for _, w := range websites { | ||||||
|  |                 <li> | ||||||
|  |                     <a href={ templ.URL(wUrl(w) + "/dashboard")}>{ w.Name }</a> | ||||||
|  |                 </li> | ||||||
|  |             } | ||||||
|  |         </ul> | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) { | ||||||
|  |     <input type="hidden" name="csrf_token" value={csrfToken}> | ||||||
|  |     <div> | ||||||
|  |         {{ err, exists := form.FieldErrors["sitename"]}} | ||||||
|  |         <label for="sitename">Site Name: </label> | ||||||
|  |         if exists { | ||||||
|  |             <label class="error">{ err }</label> | ||||||
|  |         } | ||||||
|  |         <input type="text" name="sitename" id="sitename" required /> | ||||||
|  |     </div> | ||||||
|  |     <div> | ||||||
|  |         {{ err, exists = form.FieldErrors["siteurl"] }} | ||||||
|  |         <label for="siteurl">Site URL: </label> | ||||||
|  |         if exists { | ||||||
|  |             <label class="error">{ err }</label> | ||||||
|  |         } | ||||||
|  |         <input type="text" name="siteurl" id="siteurl" required /> | ||||||
|  |     </div> | ||||||
|  |     <div> | ||||||
|  |         {{ err, exists = form.FieldErrors["authorname"] }} | ||||||
|  |         <label for="authorname">Site Author: </label> | ||||||
|  |         if exists { | ||||||
|  |             <label class="error">{ err }</label> | ||||||
|  |         } | ||||||
|  |         <input type="text" name="authorname" id="authorname" required /> | ||||||
|  |     </div> | ||||||
|  |     <div> | ||||||
|  |         <button type="submit">Submit</button> | ||||||
|  |     </div> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ WebsiteCreateButton() { | ||||||
|  |     <button hx-get="/websites/create" hx-target="closest div">Add Website</button> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ WebsiteList(title string, data CommonData, websites []models.Website) { | ||||||
|  |     if data.IsHtmx { | ||||||
|  |         @displayWebsites(websites) | ||||||
|  |     } else { | ||||||
|  |         @base(title, data) { | ||||||
|  |             <h1>My Websites</h1> | ||||||
|  |             <div> | ||||||
|  |                 @WebsiteCreateButton() | ||||||
|  |             </div> | ||||||
|  |             <div> | ||||||
|  |                 @displayWebsites(websites) | ||||||
|  |             </div> | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ WebsiteDashboard(title string, data CommonData, website models.Website) { | ||||||
|  |     @base(title, data) { | ||||||
|  |         <div id="dashboard"> | ||||||
|  |             @wSidebar(website)  | ||||||
|  |             <div> | ||||||
|  |                 <h1>{ website.Name }</h1> | ||||||
|  |                 <p> | ||||||
|  |                     Stats and stuff will go here. | ||||||
|  |                 </p> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) { | ||||||
|  |     if data.IsHtmx { | ||||||
|  |         <form hx-post="/websites/create" hx-target="closest div"> | ||||||
|  |             @websiteCreateForm(data.CSRFToken, form) | ||||||
|  |         </form> | ||||||
|  |     } else { | ||||||
|  |         <form action="/websites/create" method="post"> | ||||||
|  |             @websiteCreateForm(data.CSRFToken, form) | ||||||
|  |         </form> | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										544
									
								
								ui/views/websites_templ.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										544
									
								
								ui/views/websites_templ.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,544 @@ | |||||||
|  | // Code generated by templ - DO NOT EDIT. | ||||||
|  | 
 | ||||||
|  | // templ: version: v0.3.833 | ||||||
|  | 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 "fmt" | ||||||
|  | import "git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | import "git.32bit.cafe/32bitcafe/guestbook/internal/forms" | ||||||
|  | 
 | ||||||
|  | func wUrl(w models.Website) string { | ||||||
|  | 	return fmt.Sprintf("/websites/%s", shortIdToSlug(w.ShortId)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func wSidebar(website models.Website) 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) | ||||||
|  | 		dashUrl := wUrl(website) + "/dashboard" | ||||||
|  | 		gbUrl := wUrl(website) + "/guestbook" | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav><div><ul><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(dashUrl) | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">Dashboard</a></li><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(website.SiteUrl) | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">View Website</a></li></ul><h3>Guestbook</h3><ul><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(gbUrl) | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" target=\"_blank\">View Guestbook</a></li></ul><ul><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(dashUrl + "/guestbook/comments") | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">Manage messages</a></li><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(dashUrl + "/guestbook/comments/queue") | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Review message queue</a></li><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var7 templ.SafeURL = templ.URL(dashUrl + "/guestbook/blocklist") | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\">Block users</a></li><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var8 templ.SafeURL = templ.URL(dashUrl + "/guestbook/comments/trash") | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">Trash</a></li></ul><ul><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var9 templ.SafeURL = templ.URL(dashUrl + "/guestbook/themes") | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">Themes</a></li><li><a href=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var10 templ.SafeURL = templ.URL(dashUrl + "/guestbook/customize") | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var10))) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">Custom CSS</a></li></ul></div><div><h3>Feeds</h3><p>Coming Soon</p></div><div><h3>Account</h3><ul><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"/users/privacy\">Privacy</a></li><li><a href=\"/help\">Help</a></li></ul></div></nav>") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func displayWebsites(websites []models.Website) 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_Var11 := templ.GetChildren(ctx) | ||||||
|  | 		if templ_7745c5c3_Var11 == nil { | ||||||
|  | 			templ_7745c5c3_Var11 = templ.NopComponent | ||||||
|  | 		} | ||||||
|  | 		ctx = templ.ClearChildren(ctx) | ||||||
|  | 		if len(websites) == 0 { | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p>No Websites yet. <a href=\"\">Register a website.</a></p>") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<ul id=\"websites\" hx-get=\"/websites\" hx-trigger=\"newWebsite from:body\" hx-swap=\"outerHTML\">") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			for _, w := range websites { | ||||||
|  | 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<li><a href=\"") | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				var templ_7745c5c3_Var12 templ.SafeURL = templ.URL(wUrl(w) + "/dashboard") | ||||||
|  | 				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12))) | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">") | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				var templ_7745c5c3_Var13 string | ||||||
|  | 				templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(w.Name) | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 57, Col: 73} | ||||||
|  | 				} | ||||||
|  | 				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</a></li>") | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</ul>") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) 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_Var14 := templ.GetChildren(ctx) | ||||||
|  | 		if templ_7745c5c3_Var14 == nil { | ||||||
|  | 			templ_7745c5c3_Var14 = templ.NopComponent | ||||||
|  | 		} | ||||||
|  | 		ctx = templ.ClearChildren(ctx) | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<input type=\"hidden\" name=\"csrf_token\" value=\"") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		var templ_7745c5c3_Var15 string | ||||||
|  | 		templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(csrfToken) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 65, Col: 59} | ||||||
|  | 		} | ||||||
|  | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\"><div>") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		err, exists := form.FieldErrors["sitename"] | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<label for=\"sitename\">Site Name: </label> ") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		if exists { | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<label class=\"error\">") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			var templ_7745c5c3_Var16 string | ||||||
|  | 			templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(err) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 70, Col: 38} | ||||||
|  | 			} | ||||||
|  | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</label> ") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<input type=\"text\" name=\"sitename\" id=\"sitename\" required></div><div>") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		err, exists = form.FieldErrors["siteurl"] | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<label for=\"siteurl\">Site URL: </label> ") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		if exists { | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<label class=\"error\">") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			var templ_7745c5c3_Var17 string | ||||||
|  | 			templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(err) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 78, Col: 38} | ||||||
|  | 			} | ||||||
|  | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</label> ") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<input type=\"text\" name=\"siteurl\" id=\"siteurl\" required></div><div>") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		err, exists = form.FieldErrors["authorname"] | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<label for=\"authorname\">Site Author: </label> ") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		if exists { | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<label class=\"error\">") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			var templ_7745c5c3_Var18 string | ||||||
|  | 			templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(err) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 86, Col: 38} | ||||||
|  | 			} | ||||||
|  | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</label> ") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<input type=\"text\" name=\"authorname\" id=\"authorname\" required></div><div><button type=\"submit\">Submit</button></div>") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WebsiteCreateButton() 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_Var19 := templ.GetChildren(ctx) | ||||||
|  | 		if templ_7745c5c3_Var19 == nil { | ||||||
|  | 			templ_7745c5c3_Var19 = templ.NopComponent | ||||||
|  | 		} | ||||||
|  | 		ctx = templ.ClearChildren(ctx) | ||||||
|  | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<button hx-get=\"/websites/create\" hx-target=\"closest div\">Add Website</button>") | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WebsiteList(title string, data CommonData, websites []models.Website) 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_Var20 := templ.GetChildren(ctx) | ||||||
|  | 		if templ_7745c5c3_Var20 == nil { | ||||||
|  | 			templ_7745c5c3_Var20 = templ.NopComponent | ||||||
|  | 		} | ||||||
|  | 		ctx = templ.ClearChildren(ctx) | ||||||
|  | 		if data.IsHtmx { | ||||||
|  | 			templ_7745c5c3_Err = displayWebsites(websites).Render(ctx, templ_7745c5c3_Buffer) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			templ_7745c5c3_Var21 := 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, 32, "<h1>My Websites</h1><div>") | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				templ_7745c5c3_Err = WebsiteCreateButton().Render(ctx, templ_7745c5c3_Buffer) | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</div><div>") | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				templ_7745c5c3_Err = displayWebsites(websites).Render(ctx, templ_7745c5c3_Buffer) | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div>") | ||||||
|  | 				if templ_7745c5c3_Err != nil { | ||||||
|  | 					return templ_7745c5c3_Err | ||||||
|  | 				} | ||||||
|  | 				return nil | ||||||
|  | 			}) | ||||||
|  | 			templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WebsiteDashboard(title string, data CommonData, website models.Website) 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_Var22 := templ.GetChildren(ctx) | ||||||
|  | 		if templ_7745c5c3_Var22 == nil { | ||||||
|  | 			templ_7745c5c3_Var22 = templ.NopComponent | ||||||
|  | 		} | ||||||
|  | 		ctx = templ.ClearChildren(ctx) | ||||||
|  | 		templ_7745c5c3_Var23 := 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, 35, "<div id=\"dashboard\">") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = wSidebar(website).Render(ctx, templ_7745c5c3_Buffer) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<div><h1>") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			var templ_7745c5c3_Var24 string | ||||||
|  | 			templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 120, Col: 34} | ||||||
|  | 			} | ||||||
|  | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</h1><p>Stats and stuff will go here.</p></div></div>") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  | 		templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var23), templ_7745c5c3_Buffer) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) 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_Var25 := templ.GetChildren(ctx) | ||||||
|  | 		if templ_7745c5c3_Var25 == nil { | ||||||
|  | 			templ_7745c5c3_Var25 = templ.NopComponent | ||||||
|  | 		} | ||||||
|  | 		ctx = templ.ClearChildren(ctx) | ||||||
|  | 		if data.IsHtmx { | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<form hx-post=\"/websites/create\" hx-target=\"closest div\">") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = websiteCreateForm(data.CSRFToken, form).Render(ctx, templ_7745c5c3_Buffer) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</form>") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<form action=\"/websites/create\" method=\"post\">") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = websiteCreateForm(data.CSRFToken, form).Render(ctx, templ_7745c5c3_Buffer) | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</form>") | ||||||
|  | 			if templ_7745c5c3_Err != nil { | ||||||
|  | 				return templ_7745c5c3_Err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ = templruntime.GeneratedTemplate | ||||||
							
								
								
									
										41
									
								
								ui/views/websites_templ.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								ui/views/websites_templ.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | <nav><div><ul><li><a href=\" | ||||||
|  | \">Dashboard</a></li><li><a href=\" | ||||||
|  | \">View Website</a></li></ul><h3>Guestbook</h3><ul><li><a href=\" | ||||||
|  | \" target=\"_blank\">View Guestbook</a></li></ul><ul><li><a href=\" | ||||||
|  | \">Manage messages</a></li><li><a href=\" | ||||||
|  | \">Review message queue</a></li><li><a href=\" | ||||||
|  | \">Block users</a></li><li><a href=\" | ||||||
|  | \">Trash</a></li></ul><ul><li><a href=\" | ||||||
|  | \">Themes</a></li><li><a href=\" | ||||||
|  | \">Custom CSS</a></li></ul></div><div><h3>Feeds</h3><p>Coming Soon</p></div><div><h3>Account</h3><ul><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"/users/privacy\">Privacy</a></li><li><a href=\"/help\">Help</a></li></ul></div></nav> | ||||||
|  | <p>No Websites yet. <a href=\"\">Register a website.</a></p> | ||||||
|  | <ul id=\"websites\" hx-get=\"/websites\" hx-trigger=\"newWebsite from:body\" hx-swap=\"outerHTML\"> | ||||||
|  | <li><a href=\" | ||||||
|  | \"> | ||||||
|  | </a></li> | ||||||
|  | </ul> | ||||||
|  | <input type=\"hidden\" name=\"csrf_token\" value=\" | ||||||
|  | \"><div> | ||||||
|  | <label for=\"sitename\">Site Name: </label>  | ||||||
|  | <label class=\"error\"> | ||||||
|  | </label>  | ||||||
|  | <input type=\"text\" name=\"sitename\" id=\"sitename\" required></div><div> | ||||||
|  | <label for=\"siteurl\">Site URL: </label>  | ||||||
|  | <label class=\"error\"> | ||||||
|  | </label>  | ||||||
|  | <input type=\"text\" name=\"siteurl\" id=\"siteurl\" required></div><div> | ||||||
|  | <label for=\"authorname\">Site Author: </label>  | ||||||
|  | <label class=\"error\"> | ||||||
|  | </label>  | ||||||
|  | <input type=\"text\" name=\"authorname\" id=\"authorname\" required></div><div><button type=\"submit\">Submit</button></div> | ||||||
|  | <button hx-get=\"/websites/create\" hx-target=\"closest div\">Add Website</button> | ||||||
|  | <h1>My Websites</h1><div> | ||||||
|  | </div><div> | ||||||
|  | </div> | ||||||
|  | <div id=\"dashboard\"> | ||||||
|  | <div><h1> | ||||||
|  | </h1><p>Stats and stuff will go here.</p></div></div> | ||||||
|  | <form hx-post=\"/websites/create\" hx-target=\"closest div\"> | ||||||
|  | </form> | ||||||
|  | <form action=\"/websites/create\" method=\"post\"> | ||||||
|  | </form> | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user