Skip to content

Commit 475d763

Browse files
authored
feat: Implement ATOM feeds for posts and comments (#1287)
Adds ATOM feed support, providing two new endpoints: - A global feed for all posts at /feed/global.atom - Per-post feeds for comments at /feed/posts/<post_id>.atom Includes settings to enable/disable feeds and integrates feed discovery and links into the UI. Fixes #1286
1 parent b116fce commit 475d763

File tree

32 files changed

+761
-73
lines changed

32 files changed

+761
-73
lines changed

app/actions/tenant.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"github.com/getfider/fider/app/pkg/validate"
1616
)
1717

18-
//CreateTenant is the input model used to create a tenant
18+
// CreateTenant is the input model used to create a tenant
1919
type CreateTenant struct {
2020
Token string `json:"token"`
2121
Name string `json:"name"`
@@ -89,27 +89,27 @@ func (action *CreateTenant) Validate(ctx context.Context, user *entity.User) *va
8989
return result
9090
}
9191

92-
//GetEmail returns the email being verified
92+
// GetEmail returns the email being verified
9393
func (action *CreateTenant) GetEmail() string {
9494
return action.Email
9595
}
9696

97-
//GetName returns the name of the email owner
97+
// GetName returns the name of the email owner
9898
func (action *CreateTenant) GetName() string {
9999
return action.Name
100100
}
101101

102-
//GetUser returns the current user performing this action
102+
// GetUser returns the current user performing this action
103103
func (action *CreateTenant) GetUser() *entity.User {
104104
return nil
105105
}
106106

107-
//GetKind returns EmailVerificationKindSignUp
107+
// GetKind returns EmailVerificationKindSignUp
108108
func (action *CreateTenant) GetKind() enum.EmailVerificationKind {
109109
return enum.EmailVerificationKindSignUp
110110
}
111111

112-
//UpdateTenantSettings is the input model used to update tenant settings
112+
// UpdateTenantSettings is the input model used to update tenant settings
113113
type UpdateTenantSettings struct {
114114
Logo *dto.ImageUpload `json:"logo"`
115115
Title string `json:"title"`
@@ -175,7 +175,7 @@ func (action *UpdateTenantSettings) Validate(ctx context.Context, user *entity.U
175175
return result
176176
}
177177

178-
//UpdateTenantAdvancedSettings is the input model used to update tenant advanced settings
178+
// UpdateTenantAdvancedSettings is the input model used to update tenant advanced settings
179179
type UpdateTenantAdvancedSettings struct {
180180
CustomCSS string `json:"customCSS"`
181181
}
@@ -190,18 +190,22 @@ func (action *UpdateTenantAdvancedSettings) Validate(ctx context.Context, user *
190190
return validate.Success()
191191
}
192192

193-
//UpdateTenantPrivacy is the input model used to update tenant privacy settings
194-
type UpdateTenantPrivacy struct {
195-
IsPrivate bool `json:"isPrivate"`
193+
// UpdateTenantPrivacySettings is the input model used to update tenant privacy settings
194+
type UpdateTenantPrivacySettings struct {
195+
IsPrivate bool `json:"isPrivate"`
196+
IsFeedEnabled bool `json:"isFeedEnabled"`
196197
}
197198

198199
// IsAuthorized returns true if current user is authorized to perform this action
199-
func (action *UpdateTenantPrivacy) IsAuthorized(ctx context.Context, user *entity.User) bool {
200+
func (action *UpdateTenantPrivacySettings) IsAuthorized(ctx context.Context, user *entity.User) bool {
200201
return user != nil && user.Role == enum.RoleAdministrator
201202
}
202203

203204
// Validate if current model is valid
204-
func (action *UpdateTenantPrivacy) Validate(ctx context.Context, user *entity.User) *validate.Result {
205+
func (action *UpdateTenantPrivacySettings) Validate(ctx context.Context, user *entity.User) *validate.Result {
206+
if action.IsPrivate && action.IsFeedEnabled {
207+
return validate.Failed("Feed can not be enabled when set to private.")
208+
}
205209
return validate.Success()
206210
}
207211

app/cmd/routes.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ func routes(r *web.Engine) *web.Engine {
4444
assets.Static("/assets/*filepath", "dist")
4545
}
4646

47+
feed := r.Group()
48+
{
49+
feed.Use(middlewares.CORS())
50+
feed.Use(middlewares.WebSetup())
51+
feed.Use(middlewares.Tenant())
52+
feed.Use(middlewares.ClientCache(5 * time.Minute))
53+
54+
feed.Get("/feed/global.atom", handlers.GlobalFeed())
55+
feed.Get("/feed/posts/:path", handlers.CommentFeed())
56+
}
57+
4758
r.Use(middlewares.Session())
4859

4960
r.Get("/robots.txt", handlers.RobotsTXT())
@@ -164,7 +175,7 @@ func routes(r *web.Engine) *web.Engine {
164175
ui.Get("/_api/admin/webhook/props/:type", handlers.GetWebhookProps())
165176
ui.Post("/_api/admin/settings/general", handlers.UpdateSettings())
166177
ui.Post("/_api/admin/settings/advanced", handlers.UpdateAdvancedSettings())
167-
ui.Post("/_api/admin/settings/privacy", handlers.UpdatePrivacy())
178+
ui.Post("/_api/admin/settings/privacy", handlers.UpdatePrivacySettings())
168179
ui.Post("/_api/admin/settings/emailauth", handlers.UpdateEmailAuthAllowed())
169180
ui.Post("/_api/admin/oauth", handlers.SaveOAuthConfig())
170181
ui.Post("/_api/admin/roles/:role/users", handlers.ChangeUserRole())

app/handlers/admin.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,17 @@ func UpdateAdvancedSettings() web.HandlerFunc {
9292
}
9393
}
9494

95-
// UpdatePrivacy update current tenant's privacy settings
96-
func UpdatePrivacy() web.HandlerFunc {
95+
// UpdatePrivacySettings update current tenant's privacy settings
96+
func UpdatePrivacySettings() web.HandlerFunc {
9797
return func(c *web.Context) error {
98-
action := new(actions.UpdateTenantPrivacy)
98+
action := new(actions.UpdateTenantPrivacySettings)
9999
if result := c.BindTo(action); !result.Ok {
100100
return c.HandleValidation(result)
101101
}
102102

103103
updateSettings := &cmd.UpdateTenantPrivacySettings{
104-
IsPrivate: action.IsPrivate,
104+
IsPrivate: action.IsPrivate,
105+
IsFeedEnabled: action.IsFeedEnabled,
105106
}
106107
if err := bus.Dispatch(c, updateSettings); err != nil {
107108
return c.Failure(err)

app/handlers/admin_test.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func TestUpdateSettingsHandler_RemoveLogo(t *testing.T) {
134134
ExpectHandler(&cmd.UploadImage{}).CalledOnce()
135135
}
136136

137-
func TestUpdatePrivacyHandler(t *testing.T) {
137+
func TestUpdatePrivacySettingsHandler(t *testing.T) {
138138
RegisterT(t)
139139

140140
var updateCmd *cmd.UpdateTenantPrivacySettings
@@ -148,12 +148,53 @@ func TestUpdatePrivacyHandler(t *testing.T) {
148148
OnTenant(mock.DemoTenant).
149149
AsUser(mock.JonSnow).
150150
ExecutePost(
151-
handlers.UpdatePrivacy(),
152-
`{ "isPrivate": true }`,
151+
handlers.UpdatePrivacySettings(),
152+
`{ "isPrivate": true, "isFeedEnabled": false }`,
153153
)
154154

155155
Expect(code).Equals(http.StatusOK)
156156
Expect(updateCmd.IsPrivate).IsTrue()
157+
Expect(updateCmd.IsFeedEnabled).IsFalse()
158+
159+
server = mock.NewServer()
160+
code, _ = server.
161+
OnTenant(mock.DemoTenant).
162+
AsUser(mock.JonSnow).
163+
ExecutePost(
164+
handlers.UpdatePrivacySettings(),
165+
`{ "isPrivate": false, "isFeedEnabled": false }`,
166+
)
167+
168+
Expect(code).Equals(http.StatusOK)
169+
Expect(updateCmd.IsPrivate).IsFalse()
170+
Expect(updateCmd.IsFeedEnabled).IsFalse()
171+
172+
server = mock.NewServer()
173+
code, _ = server.
174+
OnTenant(mock.DemoTenant).
175+
AsUser(mock.JonSnow).
176+
ExecutePost(
177+
handlers.UpdatePrivacySettings(),
178+
`{ "isPrivate": false, "isFeedEnabled": true }`,
179+
)
180+
181+
Expect(code).Equals(http.StatusOK)
182+
Expect(updateCmd.IsPrivate).IsFalse()
183+
Expect(updateCmd.IsFeedEnabled).IsTrue()
184+
185+
server = mock.NewServer()
186+
code, _ = server.
187+
OnTenant(mock.DemoTenant).
188+
AsUser(mock.JonSnow).
189+
ExecutePost(
190+
handlers.UpdatePrivacySettings(),
191+
`{ "isPrivate": true, "isFeedEnabled": true }`,
192+
)
193+
194+
Expect(code).Equals(http.StatusBadRequest)
195+
// Should be same as last request
196+
Expect(updateCmd.IsPrivate).IsFalse()
197+
Expect(updateCmd.IsFeedEnabled).IsTrue()
157198
}
158199

159200
func TestManageMembersHandler(t *testing.T) {

0 commit comments

Comments
 (0)