Implement guestbook settings #20
| @ -24,15 +24,6 @@ func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) { | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if !website.Guestbook.Settings.IsVisible { | 	if !website.Guestbook.Settings.IsVisible { | ||||||
| 		u := app.getCurrentUser(r) | 		u := app.getCurrentUser(r) | ||||||
| 		if u == nil { | 		if u == nil { | ||||||
| @ -63,14 +54,6 @@ func (app *application) getGuestbookSettings(w http.ResponseWriter, r *http.Requ | |||||||
| 			app.serverError(w, r, err) | 			app.serverError(w, r, err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	data := app.newCommonData(r) | 	data := app.newCommonData(r) | ||||||
| 	views.GuestbookSettingsView(data, website).Render(r.Context(), w) | 	views.GuestbookSettingsView(data, website).Render(r.Context(), w) | ||||||
| } | } | ||||||
| @ -121,7 +104,7 @@ func (app *application) putGuestbookSettings(w http.ResponseWriter, r *http.Requ | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		app.serverError(w, r, err) | 		app.serverError(w, r, err) | ||||||
| 	} | 	} | ||||||
| 	err = app.guestbooks.UpdateGuestbookSettings(website.Guestbook.ID, website.Guestbook.Settings) | 	err = app.websites.UpdateGuestbookSettings(website.Guestbook.ID, website.Guestbook.Settings) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		app.serverError(w, r, err) | 		app.serverError(w, r, err) | ||||||
| 		return | 		return | ||||||
| @ -144,14 +127,6 @@ func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Requ | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	comments, err := app.guestbookComments.GetAll(website.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) | ||||||
| @ -173,15 +148,6 @@ func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	data := app.newCommonData(r) | 	data := app.newCommonData(r) | ||||||
| 	form := forms.CommentCreateForm{} | 	form := forms.CommentCreateForm{} | ||||||
| 	views.CreateGuestbookComment("New Comment", data, website, website.Guestbook, form).Render(r.Context(), w) | 	views.CreateGuestbookComment("New Comment", data, website, website.Guestbook, form).Render(r.Context(), w) | ||||||
| @ -198,14 +164,6 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if !website.Guestbook.CanComment() { | 	if !website.Guestbook.CanComment() { | ||||||
| 		app.clientError(w, http.StatusForbidden) | 		app.clientError(w, http.StatusForbidden) | ||||||
| @ -228,6 +186,7 @@ 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() { | ||||||
|  | 		// TODO: use htmx to avoid getting comments again | ||||||
| 		comments, err := app.guestbookComments.GetAll(website.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) | ||||||
| @ -259,14 +218,6 @@ func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	comments, err := app.guestbookComments.GetUnpublished(website.Guestbook.ID) | 	comments, err := app.guestbookComments.GetUnpublished(website.Guestbook.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -293,14 +244,6 @@ func (app *application) getCommentTrash(w http.ResponseWriter, r *http.Request) | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	comments, err := app.guestbookComments.GetDeleted(website.Guestbook.ID) | 	comments, err := app.guestbookComments.GetDeleted(website.Guestbook.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -332,14 +275,6 @@ func (app *application) putHideGuestbookComment(w http.ResponseWriter, r *http.R | |||||||
| 	if user.ID != website.UserId { | 	if user.ID != website.UserId { | ||||||
| 		app.clientError(w, http.StatusUnauthorized) | 		app.clientError(w, http.StatusUnauthorized) | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	cSlug := r.PathValue("commentId") | 	cSlug := r.PathValue("commentId") | ||||||
| 	comment, err := app.guestbookComments.Get(slugToShortId(cSlug)) | 	comment, err := app.guestbookComments.Get(slugToShortId(cSlug)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -374,14 +309,6 @@ func (app *application) deleteGuestbookComment(w http.ResponseWriter, r *http.Re | |||||||
| 	if user.ID != website.UserId { | 	if user.ID != website.UserId { | ||||||
| 		app.clientError(w, http.StatusUnauthorized) | 		app.clientError(w, http.StatusUnauthorized) | ||||||
| 	} | 	} | ||||||
| 	website.Guestbook, err = app.guestbooks.Get(website.Guestbook.ShortId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, models.ErrNoRecord) { |  | ||||||
| 			http.NotFound(w, r) |  | ||||||
| 		} else { |  | ||||||
| 			app.serverError(w, r, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	cSlug := r.PathValue("commentId") | 	cSlug := r.PathValue("commentId") | ||||||
| 	comment, err := app.guestbookComments.Get(slugToShortId(cSlug)) | 	comment, err := app.guestbookComments.Get(slugToShortId(cSlug)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
| @ -39,28 +39,13 @@ func (app *application) postWebsiteCreate(w http.ResponseWriter, r *http.Request | |||||||
| 		views.WebsiteCreate("Add a Website", data, form).Render(r.Context(), w) | 		views.WebsiteCreate("Add a Website", data, form).Render(r.Context(), w) | ||||||
| 	} | 	} | ||||||
| 	websiteShortID := app.createShortId() | 	websiteShortID := app.createShortId() | ||||||
| 	websiteId, err := app.websites.Insert(websiteShortID, userId, form.Name, form.SiteUrl, form.AuthorName) | 	_, 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() |  | ||||||
| 	guestbookSettings := models.GuestbookSettings{ |  | ||||||
| 		IsCommentingEnabled: true, |  | ||||||
| 	} |  | ||||||
| 	_, err = app.guestbooks.Insert(guestbookShortID, userId, websiteId, guestbookSettings) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		app.serverError(w, r, err) | 		app.serverError(w, r, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	app.sessionManager.Put(r.Context(), "flash", "Website successfully registered!") | 	app.sessionManager.Put(r.Context(), "flash", "Website successfully registered!") | ||||||
| 	if r.Header.Get("HX-Request") == "true" { | 	http.Redirect(w, r, fmt.Sprintf("/websites/%s/dashboard", shortIdToSlug(websiteShortID)), http.StatusSeeOther) | ||||||
| 		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) { | func (app *application) getWebsiteDashboard(w http.ResponseWriter, r *http.Request) { | ||||||
| @ -91,7 +76,8 @@ func (app *application) getWebsiteList(w http.ResponseWriter, r *http.Request) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if r.Header.Get("HX-Request") == "true" { | 	if r.Header.Get("HX-Request") == "true" { | ||||||
| 		w.Header().Add("HX-Trigger", "newWebsite") | 		views.HxWebsiteList(websites) | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
| 	data := app.newCommonData(r) | 	data := app.newCommonData(r) | ||||||
| 	views.WebsiteList("My Websites", data, websites).Render(r.Context(), w) | 	views.WebsiteList("My Websites", data, websites).Render(r.Context(), w) | ||||||
|  | |||||||
| @ -23,8 +23,7 @@ func (app *application) serverError(w http.ResponseWriter, r *http.Request, err | |||||||
| 
 | 
 | ||||||
| 	app.logger.Error(err.Error(), "method", method, "uri", uri) | 	app.logger.Error(err.Error(), "method", method, "uri", uri) | ||||||
| 	if app.debug { | 	if app.debug { | ||||||
| 		http.Error(w, string(debug.Stack()), http.StatusInternalServerError) | 		http.Error(w, err.Error()+"\n"+string(debug.Stack()), http.StatusInternalServerError) | ||||||
| 		app.logger.Error(err.Error(), "method", method, "uri", uri, "stack", string(debug.Stack())) |  | ||||||
| 	} | 	} | ||||||
| 	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) | 	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) | ||||||
| } | } | ||||||
|  | |||||||
| @ -22,7 +22,6 @@ type application struct { | |||||||
| 	sequence          uint16 | 	sequence          uint16 | ||||||
| 	logger            *slog.Logger | 	logger            *slog.Logger | ||||||
| 	websites          *models.WebsiteModel | 	websites          *models.WebsiteModel | ||||||
| 	guestbooks        *models.GuestbookModel |  | ||||||
| 	users             *models.UserModel | 	users             *models.UserModel | ||||||
| 	guestbookComments *models.GuestbookCommentModel | 	guestbookComments *models.GuestbookCommentModel | ||||||
| 	sessionManager    *scs.SessionManager | 	sessionManager    *scs.SessionManager | ||||||
| @ -58,7 +57,6 @@ func main() { | |||||||
| 		logger:            logger, | 		logger:            logger, | ||||||
| 		sessionManager:    sessionManager, | 		sessionManager:    sessionManager, | ||||||
| 		websites:          &models.WebsiteModel{DB: db}, | 		websites:          &models.WebsiteModel{DB: db}, | ||||||
| 		guestbooks:        &models.GuestbookModel{DB: db}, |  | ||||||
| 		users:             &models.UserModel{DB: db, Settings: make(map[string]models.Setting)}, | 		users:             &models.UserModel{DB: db, Settings: make(map[string]models.Setting)}, | ||||||
| 		guestbookComments: &models.GuestbookCommentModel{DB: db}, | 		guestbookComments: &models.GuestbookCommentModel{DB: db}, | ||||||
| 		formDecoder:       formDecoder, | 		formDecoder:       formDecoder, | ||||||
| @ -71,7 +69,7 @@ func main() { | |||||||
| 		logger.Error(err.Error()) | 		logger.Error(err.Error()) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 	err = app.guestbooks.InitializeSettingsMap() | 	err = app.websites.InitializeSettingsMap() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logger.Error(err.Error()) | 		logger.Error(err.Error()) | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
|  | |||||||
| @ -1,284 +0,0 @@ | |||||||
| package models |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"database/sql" |  | ||||||
| 	"errors" |  | ||||||
| 	"strconv" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type GuestbookSettings struct { |  | ||||||
| 	IsCommentingEnabled   bool |  | ||||||
| 	ReenableCommenting    time.Time |  | ||||||
| 	IsVisible             bool |  | ||||||
| 	FilteredWords         []string |  | ||||||
| 	AllowRemoteHostAccess bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ValidDisableDurations = []string{"true", "false", "1h", "4h", "8h", "24h", "72h", "168h"} |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	SettingGbCommentingEnabled = "commenting_enabled" |  | ||||||
| 	SettingGbReenableComments  = "reenable_comments" |  | ||||||
| 	SettingGbVisible           = "is_visible" |  | ||||||
| 	SettingGbFilteredWords     = "filtered_words" |  | ||||||
| 	SettingGbAllowRemote       = "remote_enabled" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type Guestbook struct { |  | ||||||
| 	ID        int64 |  | ||||||
| 	ShortId   uint64 |  | ||||||
| 	UserId    int64 |  | ||||||
| 	WebsiteId int64 |  | ||||||
| 	Created   time.Time |  | ||||||
| 	Deleted   time.Time |  | ||||||
| 	IsActive  bool |  | ||||||
| 	Settings  GuestbookSettings |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (g Guestbook) CanComment() bool { |  | ||||||
| 	now := time.Now().UTC() |  | ||||||
| 	return g.Settings.IsCommentingEnabled && g.Settings.ReenableCommenting.Before(now) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type GuestbookModel struct { |  | ||||||
| 	DB       *sql.DB |  | ||||||
| 	Settings map[string]Setting |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) InitializeSettingsMap() error { |  | ||||||
| 	if m.Settings == nil { |  | ||||||
| 		m.Settings = make(map[string]Setting) |  | ||||||
| 	} |  | ||||||
| 	stmt := `SELECT settings.Id, settings.Description, Constrained, d.Id, d.Description, g.Id, g.Description, MinValue, MaxValue |  | ||||||
|         FROM settings |  | ||||||
|         LEFT JOIN setting_data_types d ON settings.DataType = d.Id |  | ||||||
|         LEFT JOIN setting_groups g ON settings.SettingGroup = g.Id |  | ||||||
|         WHERE SettingGroup = (SELECT Id FROM setting_groups WHERE Description = 'guestbook' LIMIT 1)` |  | ||||||
| 	result, err := m.DB.Query(stmt) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	for result.Next() { |  | ||||||
| 		var s Setting |  | ||||||
| 		var mn sql.NullString |  | ||||||
| 		var mx sql.NullString |  | ||||||
| 		err := result.Scan(&s.id, &s.description, &s.constrained, &s.dataType.id, &s.dataType.description, &s.settingGroup.id, &s.settingGroup.description, &mn, &mx) |  | ||||||
| 		if mn.Valid { |  | ||||||
| 			s.minValue = mn.String |  | ||||||
| 		} |  | ||||||
| 		if mx.Valid { |  | ||||||
| 			s.maxValue = mx.String |  | ||||||
| 		} |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		m.Settings[s.description] = s |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) Insert(shortId uint64, userId int64, websiteId int64, settings GuestbookSettings) (int64, error) { |  | ||||||
| 	stmt := `INSERT INTO guestbooks (ShortId, UserId, WebsiteId, Created, IsActive) |  | ||||||
|     VALUES(?, ?, ?, ?, TRUE)` |  | ||||||
| 	result, err := m.DB.Exec(stmt, shortId, userId, websiteId, time.Now().UTC()) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return -1, err |  | ||||||
| 	} |  | ||||||
| 	id, err := result.LastInsertId() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return -1, err |  | ||||||
| 	} |  | ||||||
| 	err = m.initializeGuestbookSettings(id, settings) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return id, err |  | ||||||
| 	} |  | ||||||
| 	return id, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) Get(shortId uint64) (Guestbook, error) { |  | ||||||
| 	stmt := `SELECT Id, ShortId, UserId, WebsiteId, Created, Deleted, IsActive FROM guestbooks |  | ||||||
|     WHERE ShortId = ?` |  | ||||||
| 	row := m.DB.QueryRow(stmt, shortId) |  | ||||||
| 	var g Guestbook |  | ||||||
| 	var t sql.NullTime |  | ||||||
| 	err := row.Scan(&g.ID, &g.ShortId, &g.UserId, &g.WebsiteId, &g.Created, &t, &g.IsActive) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if errors.Is(err, sql.ErrNoRows) { |  | ||||||
| 			return Guestbook{}, ErrNoRecord |  | ||||||
| 		} |  | ||||||
| 		return Guestbook{}, err |  | ||||||
| 	} |  | ||||||
| 	if t.Valid { |  | ||||||
| 		g.Deleted = t.Time |  | ||||||
| 	} |  | ||||||
| 	settings, err := m.GetSettings(g.ID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return g, err |  | ||||||
| 	} |  | ||||||
| 	g.Settings = settings |  | ||||||
| 	return g, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) { |  | ||||||
| 	stmt := `SELECT Id, ShortId, UserId, WebsiteId, Created, IsActive FROM guestbooks |  | ||||||
|     WHERE UserId = ? AND DELETED IS NULL` |  | ||||||
| 	rows, err := m.DB.Query(stmt, userId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	var guestbooks []Guestbook |  | ||||||
| 	for rows.Next() { |  | ||||||
| 		var g Guestbook |  | ||||||
| 		err = rows.Scan(&g.ID, &g.ShortId, &g.UserId, &g.WebsiteId, &g.Created, &g.IsActive) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		guestbooks = append(guestbooks, g) |  | ||||||
| 	} |  | ||||||
| 	if err = rows.Err(); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return guestbooks, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) GetSettings(guestbookId int64) (GuestbookSettings, error) { |  | ||||||
| 	stmt := `SELECT g.SettingId, a.ItemValue, g.UnconstrainedValue FROM guestbook_settings AS g |  | ||||||
| 			LEFT JOIN allowed_setting_values AS a ON g.AllowedSettingValueId = a.Id |  | ||||||
| 			WHERE GuestbookId = ?` |  | ||||||
| 	var settings GuestbookSettings |  | ||||||
| 	rows, err := m.DB.Query(stmt, guestbookId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return settings, err |  | ||||||
| 	} |  | ||||||
| 	for rows.Next() { |  | ||||||
| 		var id int |  | ||||||
| 		var itemValue sql.NullString |  | ||||||
| 		var unconstrainedValue sql.NullString |  | ||||||
| 		err = rows.Scan(&id, &itemValue, &unconstrainedValue) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return settings, err |  | ||||||
| 		} |  | ||||||
| 		switch id { |  | ||||||
| 		case m.Settings[SettingGbCommentingEnabled].id: |  | ||||||
| 			settings.IsCommentingEnabled, err = strconv.ParseBool(itemValue.String) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return settings, err |  | ||||||
| 			} |  | ||||||
| 			break |  | ||||||
| 		case m.Settings[SettingGbReenableComments].id: |  | ||||||
| 			settings.ReenableCommenting, err = time.Parse(time.RFC3339, unconstrainedValue.String) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return settings, err |  | ||||||
| 			} |  | ||||||
| 			break |  | ||||||
| 		case m.Settings[SettingGbVisible].id: |  | ||||||
| 			settings.IsVisible, err = strconv.ParseBool(itemValue.String) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return settings, err |  | ||||||
| 			} |  | ||||||
| 			break |  | ||||||
| 		case m.Settings[SettingGbAllowRemote].id: |  | ||||||
| 			settings.AllowRemoteHostAccess, err = strconv.ParseBool(itemValue.String) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return settings, err |  | ||||||
| 			} |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// if comment disable setting has expired, enable commenting |  | ||||||
| 	if time.Now().UTC().After(settings.ReenableCommenting) { |  | ||||||
| 		settings.IsCommentingEnabled = true |  | ||||||
| 		m.UpdateSetting(guestbookId, m.Settings[SettingGbCommentingEnabled], "true") |  | ||||||
| 	} |  | ||||||
| 	return settings, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) initializeGuestbookSettings(guestbookId int64, settings GuestbookSettings) error { |  | ||||||
| 	stmt := `INSERT INTO guestbook_settings (GuestbookId, SettingId, AllowedSettingValueId, UnconstrainedValue) VALUES  |  | ||||||
| 	(?, ?, ?, ?), |  | ||||||
| 	(?, ?, ?, ?), |  | ||||||
| 	(?, ?, ?, ?), |  | ||||||
| 	(?, ?, ?, ?), |  | ||||||
| 	(?, ?, ?, ?)` |  | ||||||
| 	_, err := m.DB.Exec(stmt, |  | ||||||
| 		guestbookId, m.Settings[SettingGbCommentingEnabled].id, settings.IsCommentingEnabled, nil, |  | ||||||
| 		guestbookId, m.Settings[SettingGbReenableComments].id, nil, settings.ReenableCommenting.String(), |  | ||||||
| 		guestbookId, m.Settings[SettingGbVisible].id, settings.IsVisible, nil, |  | ||||||
| 		guestbookId, m.Settings[SettingGbAllowRemote].id, settings.AllowRemoteHostAccess, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) UpdateGuestbookSettings(guestbookId int64, settings GuestbookSettings) error { |  | ||||||
| 	err := m.UpdateSetting(guestbookId, m.Settings[SettingGbVisible], strconv.FormatBool(settings.IsVisible)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = m.UpdateSetting(guestbookId, m.Settings[SettingGbAllowRemote], strconv.FormatBool(settings.AllowRemoteHostAccess)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = m.UpdateSetting(guestbookId, m.Settings[SettingGbCommentingEnabled], strconv.FormatBool(settings.IsCommentingEnabled)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	err = m.UpdateSetting(guestbookId, m.Settings[SettingGbReenableComments], settings.ReenableCommenting.Format(time.RFC3339)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) InsertSetting(guestbookId int64, setting Setting, value string) error { |  | ||||||
| 	stmt := ` |  | ||||||
| INSERT INTO guestbook_settings (GuestbookId, SettingId, AllowedSettingValueId, UnconstrainedValue) |  | ||||||
| SELECT  ?,  |  | ||||||
|         settings.Id, |  | ||||||
|         (SELECT Id FROM allowed_setting_values WHERE SettingId = settings.id AND ItemValue = ?), |  | ||||||
|         CASE WHEN NOT EXISTS (SELECT 1 FROM settings AS s where s.Id = settings.Id AND s.Constrained = 1) THEN ? ELSE NULL END |  | ||||||
| FROM settings |  | ||||||
| WHERE settings.id = ? |  | ||||||
| 	` |  | ||||||
| 	result, err := m.DB.Exec(stmt, guestbookId, value, value, setting.id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	_, err = result.LastInsertId() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) UpdateSetting(guestbookId int64, setting Setting, value string) error { |  | ||||||
| 	stmt := `UPDATE guestbook_settings SET |  | ||||||
| 				AllowedSettingValueId=IFNULL( |  | ||||||
| 					(SELECT Id FROM allowed_setting_values WHERE SettingId = guestbook_settings.SettingId AND ItemValue = ?), AllowedSettingValueId |  | ||||||
| 				),  |  | ||||||
| 				UnconstrainedValue=(SELECT ? FROM settings WHERE settings.Id = guestbook_settings.SettingId AND settings.Constrained=0) |  | ||||||
| 			WHERE GuestbookId = ? |  | ||||||
| 			AND SettingId = (SELECT Id from Settings WHERE Description=?);` |  | ||||||
| 	result, err := m.DB.Exec(stmt, value, value, guestbookId, setting.description) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	rows, err := result.RowsAffected() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if rows != 1 { |  | ||||||
| 		return ErrInvalidSettingValue |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) AddFilteredWord(guestbookId int64, word string) error { |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (m *GuestbookModel) RemoveFilteredWord(guestbookId int64, word string) error { |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @ -2,6 +2,8 @@ package models | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"database/sql" | 	"database/sql" | ||||||
|  | 	"errors" | ||||||
|  | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -17,51 +19,206 @@ type Website struct { | |||||||
| 	Guestbook  Guestbook | 	Guestbook  Guestbook | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type GuestbookSettings struct { | ||||||
|  | 	IsCommentingEnabled   bool | ||||||
|  | 	ReenableCommenting    time.Time | ||||||
|  | 	IsVisible             bool | ||||||
|  | 	FilteredWords         []string | ||||||
|  | 	AllowRemoteHostAccess bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ValidDisableDurations = []string{"true", "false", "1h", "4h", "8h", "24h", "72h", "168h"} | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	SettingGbCommentingEnabled = "commenting_enabled" | ||||||
|  | 	SettingGbReenableComments  = "reenable_comments" | ||||||
|  | 	SettingGbVisible           = "is_visible" | ||||||
|  | 	SettingGbFilteredWords     = "filtered_words" | ||||||
|  | 	SettingGbAllowRemote       = "remote_enabled" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Guestbook struct { | ||||||
|  | 	ID        int64 | ||||||
|  | 	ShortId   uint64 | ||||||
|  | 	UserId    int64 | ||||||
|  | 	WebsiteId int64 | ||||||
|  | 	Created   time.Time | ||||||
|  | 	Deleted   time.Time | ||||||
|  | 	IsActive  bool | ||||||
|  | 	Settings  GuestbookSettings | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (g Guestbook) CanComment() bool { | ||||||
|  | 	now := time.Now().UTC() | ||||||
|  | 	return g.Settings.IsCommentingEnabled && g.Settings.ReenableCommenting.Before(now) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type WebsiteModel struct { | type WebsiteModel struct { | ||||||
| 	DB       *sql.DB | 	DB       *sql.DB | ||||||
|  | 	Settings map[string]Setting | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) InitializeSettingsMap() error { | ||||||
|  | 	if m.Settings == nil { | ||||||
|  | 		m.Settings = make(map[string]Setting) | ||||||
|  | 	} | ||||||
|  | 	stmt := `SELECT settings.Id, settings.Description, Constrained, d.Id, d.Description, g.Id, g.Description, MinValue, MaxValue | ||||||
|  |         FROM settings | ||||||
|  |         LEFT JOIN setting_data_types d ON settings.DataType = d.Id | ||||||
|  |         LEFT JOIN setting_groups g ON settings.SettingGroup = g.Id | ||||||
|  |         WHERE SettingGroup = (SELECT Id FROM setting_groups WHERE Description = 'guestbook' LIMIT 1)` | ||||||
|  | 	result, err := m.DB.Query(stmt) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	for result.Next() { | ||||||
|  | 		var s Setting | ||||||
|  | 		var mn sql.NullString | ||||||
|  | 		var mx sql.NullString | ||||||
|  | 		err := result.Scan(&s.id, &s.description, &s.constrained, &s.dataType.id, &s.dataType.description, &s.settingGroup.id, &s.settingGroup.description, &mn, &mx) | ||||||
|  | 		if mn.Valid { | ||||||
|  | 			s.minValue = mn.String | ||||||
|  | 		} | ||||||
|  | 		if mx.Valid { | ||||||
|  | 			s.maxValue = mx.String | ||||||
|  | 		} | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		m.Settings[s.description] = s | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *WebsiteModel) Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) { | func (m *WebsiteModel) Insert(shortId uint64, userId int64, siteName, siteUrl, authorName string) (int64, error) { | ||||||
| 	stmt := `INSERT INTO websites (ShortId, Name, SiteUrl, AuthorName, UserId, Created) | 	stmt := `INSERT INTO websites (ShortId, Name, SiteUrl, AuthorName, UserId, Created) | ||||||
| 			VALUES (?, ?, ?, ?, ?, ?)` | 			VALUES (?, ?, ?, ?, ?, ?)` | ||||||
| 	result, err := m.DB.Exec(stmt, shortId, siteName, siteUrl, authorName, userId, time.Now().UTC()) | 	tx, err := m.DB.Begin() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return -1, err | 		return -1, err | ||||||
| 	} | 	} | ||||||
| 	id, err := result.LastInsertId() | 
 | ||||||
|  | 	result, err := tx.Exec(stmt, shortId, siteName, siteUrl, authorName, userId, time.Now().UTC()) | ||||||
|  | 	// result, err := m.DB.Exec(stmt, shortId, siteName, siteUrl, authorName, userId, time.Now().UTC()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		if rollbackError := tx.Rollback(); rollbackError != nil { | ||||||
| 			return -1, err | 			return -1, err | ||||||
| 		} | 		} | ||||||
| 	return id, nil | 		return -1, err | ||||||
|  | 	} | ||||||
|  | 	websiteId, err := result.LastInsertId() | ||||||
|  | 	if err != nil { | ||||||
|  | 		if rollbackError := tx.Rollback(); rollbackError != nil { | ||||||
|  | 			return -1, err | ||||||
|  | 		} | ||||||
|  | 		return -1, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	stmt = `INSERT INTO guestbooks (ShortId, UserId, WebsiteId, Created, IsActive) | ||||||
|  |     VALUES(?, ?, ?, ?, TRUE)` | ||||||
|  | 	result, err = tx.Exec(stmt, shortId, userId, websiteId, time.Now().UTC()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if rollbackError := tx.Rollback(); rollbackError != nil { | ||||||
|  | 			return -1, err | ||||||
|  | 		} | ||||||
|  | 		return -1, err | ||||||
|  | 	} | ||||||
|  | 	guestbookId, err := result.LastInsertId() | ||||||
|  | 	if err != nil { | ||||||
|  | 		if rollbackError := tx.Rollback(); rollbackError != nil { | ||||||
|  | 			return -1, err | ||||||
|  | 		} | ||||||
|  | 		return -1, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	settings := GuestbookSettings{ | ||||||
|  | 		IsCommentingEnabled:   true, | ||||||
|  | 		IsVisible:             true, | ||||||
|  | 		AllowRemoteHostAccess: true, | ||||||
|  | 	} | ||||||
|  | 	stmt = `INSERT INTO guestbook_settings (GuestbookId, SettingId, AllowedSettingValueId, UnconstrainedValue) VALUES  | ||||||
|  | 	(?, ?, ?, ?), | ||||||
|  | 	(?, ?, ?, ?), | ||||||
|  | 	(?, ?, ?, ?), | ||||||
|  | 	(?, ?, ?, ?)` | ||||||
|  | 	_, err = tx.Exec(stmt, | ||||||
|  | 		guestbookId, m.Settings[SettingGbCommentingEnabled].id, settings.IsCommentingEnabled, nil, | ||||||
|  | 		guestbookId, m.Settings[SettingGbReenableComments].id, nil, settings.ReenableCommenting.Format(time.RFC3339), | ||||||
|  | 		guestbookId, m.Settings[SettingGbVisible].id, settings.IsVisible, nil, | ||||||
|  | 		guestbookId, m.Settings[SettingGbAllowRemote].id, settings.AllowRemoteHostAccess, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if rollbackError := tx.Rollback(); rollbackError != nil { | ||||||
|  | 			return -1, err | ||||||
|  | 		} | ||||||
|  | 		return -1, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := tx.Commit(); err != nil { | ||||||
|  | 		return -1, err | ||||||
|  | 	} | ||||||
|  | 	return websiteId, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *WebsiteModel) Get(shortId uint64) (Website, error) { | func (m *WebsiteModel) Get(shortId uint64) (Website, error) { | ||||||
| 	stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, | 	stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created | ||||||
| 	g.Id, g.ShortId | 	FROM websites AS w | ||||||
| 	FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId |  | ||||||
| 	WHERE w.ShortId = ? AND w.DELETED IS NULL` | 	WHERE w.ShortId = ? AND w.DELETED IS NULL` | ||||||
| 	row := m.DB.QueryRow(stmt, shortId) | 	tx, err := m.DB.Begin() | ||||||
| 	var w Website |  | ||||||
| 	err := row.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, |  | ||||||
| 		&w.Guestbook.ID, &w.Guestbook.ShortId) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		return Website{}, nil | ||||||
|  | 	} | ||||||
|  | 	row := tx.QueryRow(stmt, shortId) | ||||||
|  | 	var w Website | ||||||
|  | 	err = row.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, sql.ErrNoRows) { | ||||||
|  | 			err = ErrNoRecord | ||||||
|  | 		} | ||||||
|  | 		if rollbackErr := tx.Rollback(); rollbackErr != nil { | ||||||
| 			return Website{}, err | 			return Website{}, err | ||||||
| 		} | 		} | ||||||
| 	return w, nil | 		return Website{}, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| func (m *WebsiteModel) GetById(id int64) (Website, error) { | 	stmt = `SELECT Id, ShortId, UserId, WebsiteId, Created, IsActive FROM guestbooks | ||||||
| 	stmt := `SELECT w.Id, w.ShortId, w.Name, w.SiteUrl, w.AuthorName, w.UserId, w.Created, |     WHERE WebsiteId = ? AND Deleted IS NULL` | ||||||
| 	g.Id, g.ShortId, g.Created, g.IsActive | 	row = tx.QueryRow(stmt, w.ID) | ||||||
| 	FROM websites AS w INNER JOIN guestbooks AS g ON w.Id = g.WebsiteId | 	var g Guestbook | ||||||
| 	WHERE w.Id = ? AND w.DELETED IS NULL` | 	err = row.Scan(&g.ID, &g.ShortId, &g.UserId, &g.WebsiteId, &g.Created, &g.IsActive) | ||||||
| 	row := m.DB.QueryRow(stmt, id) |  | ||||||
| 	var w Website |  | ||||||
| 	err := row.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, |  | ||||||
| 		&w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsActive) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		if errors.Is(err, sql.ErrNoRows) { | ||||||
|  | 			err = ErrNoRecord | ||||||
|  | 		} | ||||||
|  | 		if rollbackErr := tx.Rollback(); rollbackErr != nil { | ||||||
| 			return Website{}, err | 			return Website{}, err | ||||||
| 		} | 		} | ||||||
|  | 		return Website{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	gbSettings, err := m.getGuestbookSettings(tx, g.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, sql.ErrNoRows) { | ||||||
|  | 			err = ErrNoRecord | ||||||
|  | 		} | ||||||
|  | 		if rollbackErr := tx.Rollback(); rollbackErr != nil { | ||||||
|  | 			return Website{}, err | ||||||
|  | 		} | ||||||
|  | 		return Website{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = tx.Commit() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return Website{}, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// if comment disable setting has expired, enable commenting | ||||||
|  | 	commentingReenabled := time.Now().UTC().After(gbSettings.ReenableCommenting) | ||||||
|  | 	if commentingReenabled { | ||||||
|  | 		gbSettings.IsCommentingEnabled = true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	g.Settings = gbSettings | ||||||
|  | 	w.Guestbook = g | ||||||
| 	return w, nil | 	return w, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -113,3 +270,92 @@ func (m *WebsiteModel) GetAll() ([]Website, error) { | |||||||
| 	} | 	} | ||||||
| 	return websites, nil | 	return websites, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) getGuestbookSettings(tx *sql.Tx, guestbookId int64) (GuestbookSettings, error) { | ||||||
|  | 	stmt := `SELECT g.SettingId, a.ItemValue, g.UnconstrainedValue FROM guestbook_settings AS g | ||||||
|  | 			LEFT JOIN allowed_setting_values AS a ON g.AllowedSettingValueId = a.Id | ||||||
|  | 			WHERE GuestbookId = ?` | ||||||
|  | 	var settings GuestbookSettings | ||||||
|  | 	rows, err := tx.Query(stmt, guestbookId) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return settings, err | ||||||
|  | 	} | ||||||
|  | 	for rows.Next() { | ||||||
|  | 		var id int | ||||||
|  | 		var itemValue sql.NullString | ||||||
|  | 		var unconstrainedValue sql.NullString | ||||||
|  | 		err = rows.Scan(&id, &itemValue, &unconstrainedValue) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return settings, err | ||||||
|  | 		} | ||||||
|  | 		switch id { | ||||||
|  | 		case m.Settings[SettingGbCommentingEnabled].id: | ||||||
|  | 			settings.IsCommentingEnabled, err = strconv.ParseBool(itemValue.String) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return settings, err | ||||||
|  | 			} | ||||||
|  | 			break | ||||||
|  | 		case m.Settings[SettingGbReenableComments].id: | ||||||
|  | 			settings.ReenableCommenting, err = time.Parse(time.RFC3339, unconstrainedValue.String) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return settings, err | ||||||
|  | 			} | ||||||
|  | 			break | ||||||
|  | 		case m.Settings[SettingGbVisible].id: | ||||||
|  | 			settings.IsVisible, err = strconv.ParseBool(itemValue.String) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return settings, err | ||||||
|  | 			} | ||||||
|  | 			break | ||||||
|  | 		case m.Settings[SettingGbAllowRemote].id: | ||||||
|  | 			settings.AllowRemoteHostAccess, err = strconv.ParseBool(itemValue.String) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return settings, err | ||||||
|  | 			} | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return settings, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) UpdateGuestbookSettings(guestbookId int64, settings GuestbookSettings) error { | ||||||
|  | 	err := m.UpdateSetting(guestbookId, m.Settings[SettingGbVisible], strconv.FormatBool(settings.IsVisible)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = m.UpdateSetting(guestbookId, m.Settings[SettingGbAllowRemote], strconv.FormatBool(settings.AllowRemoteHostAccess)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = m.UpdateSetting(guestbookId, m.Settings[SettingGbCommentingEnabled], strconv.FormatBool(settings.IsCommentingEnabled)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = m.UpdateSetting(guestbookId, m.Settings[SettingGbReenableComments], settings.ReenableCommenting.Format(time.RFC3339)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *WebsiteModel) UpdateSetting(guestbookId int64, setting Setting, value string) error { | ||||||
|  | 	stmt := `UPDATE guestbook_settings SET | ||||||
|  | 				AllowedSettingValueId=IFNULL( | ||||||
|  | 					(SELECT Id FROM allowed_setting_values WHERE SettingId = guestbook_settings.SettingId AND ItemValue = ?), AllowedSettingValueId | ||||||
|  | 				),  | ||||||
|  | 				UnconstrainedValue=(SELECT ? FROM settings WHERE settings.Id = guestbook_settings.SettingId AND settings.Constrained=0) | ||||||
|  | 			WHERE GuestbookId = ? | ||||||
|  | 			AND SettingId = (SELECT Id from Settings WHERE Description=?);` | ||||||
|  | 	result, err := m.DB.Exec(stmt, value, value, guestbookId, setting.description) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	rows, err := result.RowsAffected() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if rows != 1 { | ||||||
|  | 		return ErrInvalidSettingValue | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | |||||||
| @ -98,20 +98,16 @@ templ WebsiteCreateButton() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ WebsiteList(title string, data CommonData, websites []models.Website) { | templ WebsiteList(title string, data CommonData, websites []models.Website) { | ||||||
| 	if data.IsHtmx { |  | ||||||
| 		@displayWebsites(websites) |  | ||||||
| 	} else { |  | ||||||
| 	@base(title, data) { | 	@base(title, data) { | ||||||
| 		<h1>My Websites</h1> | 		<h1>My Websites</h1> | ||||||
| 			<p> | 		<div> | ||||||
| 			@WebsiteCreateButton() | 			@WebsiteCreateButton() | ||||||
| 			</p> | 		</div> | ||||||
| 		<div> | 		<div> | ||||||
| 			@displayWebsites(websites) | 			@displayWebsites(websites) | ||||||
| 		</div> | 		</div> | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| templ WebsiteDashboard(title string, data CommonData, website models.Website) { | templ WebsiteDashboard(title string, data CommonData, website models.Website) { | ||||||
| 	@base(title, data) { | 	@base(title, data) { | ||||||
| @ -142,13 +138,7 @@ templ WebsiteDashboardComingSoon(title string, data CommonData, website models.W | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) { | templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) { | ||||||
| 	if data.IsHtmx { |  | ||||||
| 	<form hx-post="/websites/create" hx-target="closest div"> | 	<form hx-post="/websites/create" hx-target="closest div"> | ||||||
| 		@websiteCreateForm(data.CSRFToken, form) | 		@websiteCreateForm(data.CSRFToken, form) | ||||||
| 	</form> | 	</form> | ||||||
| 	} else { |  | ||||||
| 		<form action="/websites/create" method="post"> |  | ||||||
| 			@websiteCreateForm(data.CSRFToken, form) |  | ||||||
| 		</form> |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								ui/views/websites_hx.templ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								ui/views/websites_hx.templ
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | package views | ||||||
|  | 
 | ||||||
|  | import "git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | 
 | ||||||
|  | templ HxWebsiteList(websites []models.Website) { | ||||||
|  | 	@displayWebsites(websites) | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								ui/views/websites_hx_templ.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								ui/views/websites_hx_templ.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | // 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 "git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||||
|  | 
 | ||||||
|  | func HxWebsiteList(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_Var1 := templ.GetChildren(ctx) | ||||||
|  | 		if templ_7745c5c3_Var1 == nil { | ||||||
|  | 			templ_7745c5c3_Var1 = templ.NopComponent | ||||||
|  | 		} | ||||||
|  | 		ctx = templ.ClearChildren(ctx) | ||||||
|  | 		templ_7745c5c3_Err = displayWebsites(websites).Render(ctx, templ_7745c5c3_Buffer) | ||||||
|  | 		if templ_7745c5c3_Err != nil { | ||||||
|  | 			return templ_7745c5c3_Err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ = templruntime.GeneratedTemplate | ||||||
| @ -385,12 +385,6 @@ func WebsiteList(title string, data CommonData, websites []models.Website) templ | |||||||
| 			templ_7745c5c3_Var21 = templ.NopComponent | 			templ_7745c5c3_Var21 = templ.NopComponent | ||||||
| 		} | 		} | ||||||
| 		ctx = templ.ClearChildren(ctx) | 		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_Var22 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | 		templ_7745c5c3_Var22 := 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) | ||||||
| @ -403,7 +397,7 @@ func WebsiteList(title string, data CommonData, websites []models.Website) templ | |||||||
| 				}() | 				}() | ||||||
| 			} | 			} | ||||||
| 			ctx = templ.InitializeContext(ctx) | 			ctx = templ.InitializeContext(ctx) | ||||||
| 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<h1>My Websites</h1><p>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<h1>My Websites</h1><div>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| @ -411,7 +405,7 @@ func WebsiteList(title string, data CommonData, websites []models.Website) templ | |||||||
| 			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, 34, "</p><div>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div><div>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| @ -429,7 +423,6 @@ func WebsiteList(title string, data CommonData, websites []models.Website) templ | |||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
| 		return nil | 		return nil | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| @ -482,7 +475,7 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem | |||||||
| 			var templ_7745c5c3_Var25 string | 			var templ_7745c5c3_Var25 string | ||||||
| 			templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) | 			templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 121, Col: 22} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 117, Col: 22} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| @ -550,7 +543,7 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We | |||||||
| 			var templ_7745c5c3_Var28 string | 			var templ_7745c5c3_Var28 string | ||||||
| 			templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) | 			templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 135, Col: 22} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 131, Col: 22} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| @ -591,7 +584,6 @@ func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) | |||||||
| 			templ_7745c5c3_Var29 = templ.NopComponent | 			templ_7745c5c3_Var29 = templ.NopComponent | ||||||
| 		} | 		} | ||||||
| 		ctx = templ.ClearChildren(ctx) | 		ctx = templ.ClearChildren(ctx) | ||||||
| 		if data.IsHtmx { |  | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<form hx-post=\"/websites/create\" hx-target=\"closest div\">") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<form hx-post=\"/websites/create\" hx-target=\"closest div\">") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| @ -604,20 +596,6 @@ func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) | |||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		} else { |  | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<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, 45, "</form>") |  | ||||||
| 			if templ_7745c5c3_Err != nil { |  | ||||||
| 				return templ_7745c5c3_Err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return nil | 		return nil | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user