Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions app/actions/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions app/handlers/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion app/handlers/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 8 additions & 7 deletions app/models/cmd/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 19 additions & 18 deletions app/models/entity/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions app/services/sqlstore/dbEntities/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -63,6 +64,7 @@ func (t *Tenant) ToModel() *entity.Tenant {
PreventIndexing: t.PreventIndexing,
IsModerationEnabled: t.IsModerationEnabled,
HasCommercialFeatures: hasCommercialFeatures,
DefaultSort: t.DefaultSort,
}

return tenant
Expand Down
39 changes: 20 additions & 19 deletions app/services/sqlstore/postgres/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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
})
Expand Down Expand Up @@ -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
Expand All @@ -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")
}
Expand All @@ -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)
}
Expand Down
4 changes: 4 additions & 0 deletions migrations/202602170001_add_default_sort.sql
Original file line number Diff line number Diff line change
@@ -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';
1 change: 1 addition & 0 deletions public/models/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface Tenant {
isFeedEnabled: boolean
isModerationEnabled: boolean
hasCommercialFeatures: boolean
defaultSort: string
}

export enum TenantStatus {
Expand Down
16 changes: 15 additions & 1 deletion public/pages/Administration/pages/GeneralSettings.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ const GeneralSettingsPage = () => {
const [logo, setLogo] = useState<ImageUpload | undefined>(undefined)
const [cname, setCNAME] = useState<string>(fider.session.tenant.cname)
const [locale, setLocale] = useState<string>(fider.session.tenant.locale)
const [defaultSort, setDefaultSort] = useState<string>(fider.session.tenant.defaultSort)
const [error, setError] = useState<Failure | undefined>(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 = `/`
Expand Down Expand Up @@ -143,6 +144,19 @@ const GeneralSettingsPage = () => {
)}
</Select>

<Select
label="Default Sort"
field="defaultSort"
defaultValue={defaultSort}
options={[
{ value: "most-wanted", label: "Most Wanted" },
{ value: "trending", label: "Trending" },
{ value: "most-discussed", label: "Most Discussed" },
{ value: "recent", label: "Recent" },
]}
onChange={(o) => setDefaultSort(o?.value || "trending")}
/>

<div className="field">
<Button disabled={!fider.session.user.isAdministrator} variant="primary" onClick={handleSave}>
Save
Expand Down
6 changes: 3 additions & 3 deletions public/pages/Home/components/PostsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from "react"

import { Post, Tag, CurrentUser } from "@fider/models"
import { Loader, Input } from "@fider/components"
import { actions, navigator, querystring } from "@fider/services"
import { actions, navigator, querystring, Fider } from "@fider/services"
import IconSearch from "@fider/assets/images/heroicons-search.svg"
import IconX from "@fider/assets/images/heroicons-x.svg"
import { PostFilter } from "./PostFilter"
Expand Down Expand Up @@ -42,7 +42,7 @@ export class PostsContainer extends React.Component<PostsContainerProps, PostsCo
constructor(props: PostsContainerProps) {
super(props)

const view = querystring.get("view")
const view = querystring.get("view") || Fider.session.tenant.defaultSort

this.state = {
posts: this.props.posts,
Expand Down Expand Up @@ -78,7 +78,7 @@ export class PostsContainer extends React.Component<PostsContainerProps, PostsCo

this.searchPosts(
query,
this.state.view || "trending",
this.state.view || Fider.session.tenant.defaultSort,
this.state.limit,
this.state.filterState.tags,
this.state.filterState.statuses,
Expand Down
3 changes: 2 additions & 1 deletion public/pages/Home/components/PostsSort.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import IconChat from "@fider/assets/images/heroicons-chat-alt-2.svg"
import IconClock from "@fider/assets/images/heroicons-clock.svg"
import { HStack } from "@fider/components/layout"


interface PostsSortProps {
value: string
onChange: (value: string) => void
}

export const PostsSort: React.FC<PostsSortProps> = ({ value = "trending", onChange }) => {
export const PostsSort: React.FC<PostsSortProps> = ({ value, onChange }) => {
const options = [
{ value: "trending", label: i18n._({ id: "home.postfilter.option.trending", message: "Trending" }), icon: IconSparkles },
{ value: "most-wanted", label: i18n._({ id: "home.postfilter.option.mostwanted", message: "Most Wanted" }), icon: IconThumbsUp },
Expand Down
1 change: 1 addition & 0 deletions public/services/actions/tenant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface UpdateTenantSettingsRequest {
welcomeHeader: string
cname: string
locale: string
defaultSort: string
}

export const updateTenantSettings = async (request: UpdateTenantSettingsRequest): Promise<Result> => {
Expand Down