diff --git a/app/actions/tenant.go b/app/actions/tenant.go index 26bce8efc..7de4f239f 100644 --- a/app/actions/tenant.go +++ b/app/actions/tenant.go @@ -190,13 +190,14 @@ func (action *ResendSignUpEmail) GetKind() enum.EmailVerificationKind { // UpdateTenantSettings is the input model used to update tenant settings type UpdateTenantSettings struct { - Logo *dto.ImageUpload `json:"logo"` - Title string `json:"title"` - Invitation string `json:"invitation"` - WelcomeMessage string `json:"welcomeMessage"` - WelcomeHeader string `json:"welcomeHeader"` - Locale string `json:"locale"` - CNAME string `json:"cname" format:"lower"` + Logo *dto.ImageUpload `json:"logo"` + Title string `json:"title"` + Invitation string `json:"invitation"` + WelcomeMessage string `json:"welcomeMessage"` + WelcomeHeader string `json:"welcomeHeader"` + Locale string `json:"locale"` + CNAME string `json:"cname" format:"lower"` + DefaultSort string `json:"defaultSort"` } func NewUpdateTenantSettings() *UpdateTenantSettings { @@ -256,6 +257,18 @@ func (action *UpdateTenantSettings) Validate(ctx context.Context, user *entity.U result.AddFieldFailure("cname", messages...) } + validSorts := []string{"trending", "most-wanted", "most-discussed", "recent"} + sortValid := false + for _, s := range validSorts { + if action.DefaultSort == s { + sortValid = true + break + } + } + if !sortValid { + action.DefaultSort = "trending" + } + return result } diff --git a/app/handlers/admin.go b/app/handlers/admin.go index e21d3724a..9bd600552 100644 --- a/app/handlers/admin.go +++ b/app/handlers/admin.go @@ -66,6 +66,7 @@ func UpdateSettings() web.HandlerFunc { WelcomeHeader: action.WelcomeHeader, CNAME: action.CNAME, Locale: action.Locale, + DefaultSort: action.DefaultSort, }, ); err != nil { return c.Failure(err) diff --git a/app/handlers/post.go b/app/handlers/post.go index 5d2f837c5..24f5224d3 100644 --- a/app/handlers/post.go +++ b/app/handlers/post.go @@ -17,9 +17,14 @@ func Index() web.HandlerFunc { return func(c *web.Context) error { c.SetCanonicalURL("") + view := c.QueryParam("view") + if view == "" { + view = c.Tenant().DefaultSort + } + searchPosts := &query.SearchPosts{ Query: c.QueryParam("query"), - View: c.QueryParam("view"), + View: view, Limit: c.QueryParam("limit"), Tags: c.QueryParamAsArray("tags"), } @@ -77,6 +82,7 @@ func Index() web.HandlerFunc { return c.Page(http.StatusOK, web.Props{ Page: "Home/Home.page", + Title: "Feedback", Description: description, // Header: c.Tenant().WelcomeHeader, Data: data, diff --git a/app/models/cmd/tenant.go b/app/models/cmd/tenant.go index bd5cef599..0e7d32f93 100644 --- a/app/models/cmd/tenant.go +++ b/app/models/cmd/tenant.go @@ -27,13 +27,14 @@ type UpdateTenantEmailAuthAllowedSettings struct { } type UpdateTenantSettings struct { - Logo *dto.ImageUpload - Title string - Invitation string - WelcomeMessage string - WelcomeHeader string - CNAME string - Locale string + Logo *dto.ImageUpload + Title string + Invitation string + WelcomeMessage string + WelcomeHeader string + CNAME string + Locale string + DefaultSort string } type UpdateTenantAdvancedSettings struct { diff --git a/app/models/entity/tenant.go b/app/models/entity/tenant.go index 064714600..590819f53 100644 --- a/app/models/entity/tenant.go +++ b/app/models/entity/tenant.go @@ -6,24 +6,25 @@ import ( // Tenant represents a tenant type Tenant struct { - ID int `json:"id"` - Name string `json:"name"` - Subdomain string `json:"subdomain"` - Invitation string `json:"invitation"` - WelcomeMessage string `json:"welcomeMessage"` - WelcomeHeader string `json:"welcomeHeader"` - CNAME string `json:"cname"` - Status enum.TenantStatus `json:"status"` - Locale string `json:"locale"` - IsPrivate bool `json:"isPrivate"` - LogoBlobKey string `json:"logoBlobKey"` - CustomCSS string `json:"-"` - AllowedSchemes string `json:"allowedSchemes"` - IsEmailAuthAllowed bool `json:"isEmailAuthAllowed"` - IsFeedEnabled bool `json:"isFeedEnabled"` - PreventIndexing bool `json:"preventIndexing"` - IsModerationEnabled bool `json:"isModerationEnabled"` - HasCommercialFeatures bool `json:"hasCommercialFeatures"` + ID int `json:"id"` + Name string `json:"name"` + Subdomain string `json:"subdomain"` + Invitation string `json:"invitation"` + WelcomeMessage string `json:"welcomeMessage"` + WelcomeHeader string `json:"welcomeHeader"` + CNAME string `json:"cname"` + Status enum.TenantStatus `json:"status"` + Locale string `json:"locale"` + IsPrivate bool `json:"isPrivate"` + LogoBlobKey string `json:"logoBlobKey"` + CustomCSS string `json:"-"` + AllowedSchemes string `json:"allowedSchemes"` + IsEmailAuthAllowed bool `json:"isEmailAuthAllowed"` + IsFeedEnabled bool `json:"isFeedEnabled"` + PreventIndexing bool `json:"preventIndexing"` + IsModerationEnabled bool `json:"isModerationEnabled"` + HasCommercialFeatures bool `json:"hasCommercialFeatures"` + DefaultSort string `json:"defaultSort"` } func (t *Tenant) IsDisabled() bool { diff --git a/app/services/sqlstore/dbEntities/tenant.go b/app/services/sqlstore/dbEntities/tenant.go index dad5671d7..a377de2ca 100644 --- a/app/services/sqlstore/dbEntities/tenant.go +++ b/app/services/sqlstore/dbEntities/tenant.go @@ -27,6 +27,7 @@ type Tenant struct { IsModerationEnabled bool `db:"is_moderation_enabled"` IsPro bool `db:"is_pro"` HasPaddleSubscription bool `db:"has_paddle_subscription"` + DefaultSort string `db:"default_sort"` } func (t *Tenant) ToModel() *entity.Tenant { @@ -63,6 +64,7 @@ func (t *Tenant) ToModel() *entity.Tenant { PreventIndexing: t.PreventIndexing, IsModerationEnabled: t.IsModerationEnabled, HasCommercialFeatures: hasCommercialFeatures, + DefaultSort: t.DefaultSort, } return tenant diff --git a/app/services/sqlstore/postgres/tenant.go b/app/services/sqlstore/postgres/tenant.go index 657c83a23..9fd15994a 100644 --- a/app/services/sqlstore/postgres/tenant.go +++ b/app/services/sqlstore/postgres/tenant.go @@ -80,8 +80,8 @@ func updateTenantSettings(ctx context.Context, c *cmd.UpdateTenantSettings) erro c.Logo.BlobKey = "" } - query := "UPDATE tenants SET name = $1, invitation = $2, welcome_message = $3, welcome_header = $4, cname = $5, logo_bkey = $6, locale = $7 WHERE id = $8" - _, err := trx.Execute(query, c.Title, c.Invitation, c.WelcomeMessage, c.WelcomeHeader, c.CNAME, c.Logo.BlobKey, c.Locale, tenant.ID) + query := "UPDATE tenants SET name = $1, invitation = $2, welcome_message = $3, welcome_header = $4, cname = $5, logo_bkey = $6, locale = $7, default_sort = $8 WHERE id = $9" + _, err := trx.Execute(query, c.Title, c.Invitation, c.WelcomeMessage, c.WelcomeHeader, c.CNAME, c.Logo.BlobKey, c.Locale, c.DefaultSort, tenant.ID) if err != nil { return errors.Wrap(err, "failed update tenant settings") } @@ -91,6 +91,7 @@ func updateTenantSettings(ctx context.Context, c *cmd.UpdateTenantSettings) erro tenant.CNAME = c.CNAME tenant.WelcomeMessage = c.WelcomeMessage tenant.WelcomeHeader = c.WelcomeHeader + tenant.DefaultSort = c.DefaultSort return nil }) @@ -189,8 +190,8 @@ func createTenant(ctx context.Context, c *cmd.CreateTenant) error { var id int err := trx.Get(&id, - `INSERT INTO tenants (name, subdomain, created_at, cname, invitation, welcome_message, status, is_private, custom_css, logo_bkey, locale, is_email_auth_allowed, is_feed_enabled, prevent_indexing, is_moderation_enabled) - VALUES ($1, $2, $3, '', '', '', $4, false, '', '', $5, true, true, true, false) + `INSERT INTO tenants (name, subdomain, created_at, cname, invitation, welcome_message, status, is_private, custom_css, logo_bkey, locale, is_email_auth_allowed, is_feed_enabled, prevent_indexing, is_moderation_enabled, default_sort) + VALUES ($1, $2, $3, '', '', '', $4, false, '', '', $5, true, true, true, false, 'trending') RETURNING id`, c.Name, c.Subdomain, now, c.Status, env.Config.Locale) if err != nil { return err @@ -207,13 +208,13 @@ func getFirstTenant(ctx context.Context, q *query.GetFirstTenant) error { return using(ctx, func(trx *dbx.Trx, _ *entity.Tenant, _ *entity.User) error { tenant := dbEntities.Tenant{} - err := trx.Get(&tenant, ` - SELECT t.id, t.name, t.subdomain, t.cname, t.invitation, t.locale, t.welcome_message, t.welcome_header, t.status, t.is_private, t.logo_bkey, t.custom_css, t.allowed_schemes, t.is_email_auth_allowed, t.is_feed_enabled, t.is_moderation_enabled, t.prevent_indexing, t.is_pro, - (b.paddle_subscription_id IS NOT NULL AND b.stripe_subscription_id IS NULL) AS has_paddle_subscription - FROM tenants t - LEFT JOIN tenants_billing b ON b.tenant_id = t.id - ORDER BY t.id LIMIT 1 - `) + err := trx.Get(&tenant, ` + SELECT t.id, t.name, t.subdomain, t.cname, t.invitation, t.locale, t.welcome_message, t.welcome_header, t.status, t.is_private, t.logo_bkey, t.custom_css, t.allowed_schemes, t.is_email_auth_allowed, t.is_feed_enabled, t.is_moderation_enabled, t.prevent_indexing, t.is_pro, t.default_sort, + (b.paddle_subscription_id IS NOT NULL AND b.stripe_subscription_id IS NULL) AS has_paddle_subscription + FROM tenants t + LEFT JOIN tenants_billing b ON b.tenant_id = t.id + ORDER BY t.id LIMIT 1 + `) if err != nil { return errors.Wrap(err, "failed to get first tenant") } @@ -227,14 +228,14 @@ func getTenantByDomain(ctx context.Context, q *query.GetTenantByDomain) error { return using(ctx, func(trx *dbx.Trx, _ *entity.Tenant, _ *entity.User) error { tenant := dbEntities.Tenant{} - err := trx.Get(&tenant, ` - SELECT t.id, t.name, t.subdomain, t.cname, t.invitation, t.locale, t.welcome_message, t.welcome_header, t.status, t.is_private, t.logo_bkey, t.custom_css, t.allowed_schemes, t.is_email_auth_allowed, t.is_feed_enabled, t.is_moderation_enabled, t.prevent_indexing, t.is_pro, - (b.paddle_subscription_id IS NOT NULL AND b.stripe_subscription_id IS NULL) AS has_paddle_subscription - FROM tenants t - LEFT JOIN tenants_billing b ON b.tenant_id = t.id - WHERE t.subdomain = $1 OR t.subdomain = $2 OR t.cname = $3 - ORDER BY t.cname DESC - `, env.Subdomain(q.Domain), q.Domain, q.Domain) + err := trx.Get(&tenant, ` + SELECT t.id, t.name, t.subdomain, t.cname, t.invitation, t.locale, t.welcome_message, t.welcome_header, t.status, t.is_private, t.logo_bkey, t.custom_css, t.allowed_schemes, t.is_email_auth_allowed, t.is_feed_enabled, t.is_moderation_enabled, t.prevent_indexing, t.is_pro, t.default_sort, + (b.paddle_subscription_id IS NOT NULL AND b.stripe_subscription_id IS NULL) AS has_paddle_subscription + FROM tenants t + LEFT JOIN tenants_billing b ON b.tenant_id = t.id + WHERE t.subdomain = $1 OR t.subdomain = $2 OR t.cname = $3 + ORDER BY t.cname DESC + `, env.Subdomain(q.Domain), q.Domain, q.Domain) if err != nil { return errors.Wrap(err, "failed to get tenant with domain '%s'", q.Domain) } diff --git a/migrations/202602170001_add_default_sort.sql b/migrations/202602170001_add_default_sort.sql new file mode 100644 index 000000000..7e4e7f47a --- /dev/null +++ b/migrations/202602170001_add_default_sort.sql @@ -0,0 +1,4 @@ +ALTER TABLE tenants ADD default_sort VARCHAR(50) NULL; +UPDATE tenants SET default_sort = 'trending'; +ALTER TABLE tenants ALTER COLUMN default_sort SET NOT NULL; +ALTER TABLE tenants ALTER COLUMN default_sort SET DEFAULT 'trending'; diff --git a/public/models/identity.ts b/public/models/identity.ts index 582e1895e..eb8b9f98f 100644 --- a/public/models/identity.ts +++ b/public/models/identity.ts @@ -15,6 +15,7 @@ export interface Tenant { isFeedEnabled: boolean isModerationEnabled: boolean hasCommercialFeatures: boolean + defaultSort: string } export enum TenantStatus { diff --git a/public/pages/Administration/pages/GeneralSettings.page.tsx b/public/pages/Administration/pages/GeneralSettings.page.tsx index 1a44518fa..c021f82ad 100644 --- a/public/pages/Administration/pages/GeneralSettings.page.tsx +++ b/public/pages/Administration/pages/GeneralSettings.page.tsx @@ -16,10 +16,11 @@ const GeneralSettingsPage = () => { const [logo, setLogo] = useState(undefined) const [cname, setCNAME] = useState(fider.session.tenant.cname) const [locale, setLocale] = useState(fider.session.tenant.locale) + const [defaultSort, setDefaultSort] = useState(fider.session.tenant.defaultSort) const [error, setError] = useState(undefined) const handleSave = async (e: ButtonClickEvent) => { - const result = await actions.updateTenantSettings({ title, cname, welcomeMessage, welcomeHeader, invitation, logo, locale }) + const result = await actions.updateTenantSettings({ title, cname, welcomeMessage, welcomeHeader, invitation, logo, locale, defaultSort }) if (result.ok) { e.preventEnable() location.href = `/` @@ -143,6 +144,19 @@ const GeneralSettingsPage = () => { )} +