Skip to content

Commit

Permalink
feat(incoming-webhook): add quota
Browse files Browse the repository at this point in the history
  • Loading branch information
ncarlier committed Jan 29, 2022
1 parent 7be8c01 commit 016b0c6
Show file tree
Hide file tree
Showing 15 changed files with 107 additions and 4 deletions.
3 changes: 3 additions & 0 deletions landing/components/Plans.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const Plans = ({ onChoosePlan }) => {
<ul>
<li>{t('up-to-art', { count: 200 })}</li>
<li>{t('up-to-cat', { count: 5 })}</li>
<li>{t('up-to-hook', { count: 1 })}</li>
</ul>
<footer>
<h2>
Expand All @@ -39,6 +40,7 @@ const Plans = ({ onChoosePlan }) => {
<ul>
<li>{t('up-to-art', { count: 2000 })}</li>
<li>{t('up-to-cat', { count: 20 })}</li>
<li>{t('up-to-hook', { count: 3 })}</li>
</ul>
<footer>
<h2>
Expand All @@ -57,6 +59,7 @@ const Plans = ({ onChoosePlan }) => {
<ul>
<li>{t('up-to-art', { count: 10000 })}</li>
<li>{t('up-to-cat', { count: 50 })}</li>
<li>{t('up-to-hook', { count: 5 })}</li>
<li>
RSS feeds<span>with a dedicated Feedpushr instance</span>
</li>
Expand Down
1 change: 1 addition & 0 deletions landing/locales/en/pricing.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"premium-desc": "Battery included.",
"up-to-art": "up to {{count}} articles",
"up-to-cat": "up to {{count}} categories",
"up-to-hook": "up to {{count}} incoming webhooks",
"subscribe": "Subscribe",
"get-started": "Get started",
"month": "month"
Expand Down
1 change: 1 addition & 0 deletions landing/locales/fr/pricing.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"premium-desc": "Batterie incluse.",
"up-to-art": "jusqu'à {{count}} articles",
"up-to-cat": "jusqu'à {{count}} catégories",
"up-to-hook": "jusqu'à {{count}} webhooks entrant",
"subscribe": "S'abonner",
"get-started": "Commencer",
"month": "mois"
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/readflow.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ consumer_key = "${READFLOW_POCKET_CONSUMER_KEY}"
#name = "free to play"
#total_articles = 200
#total_categories = 10
#total_webhooks = 1

#[[user_plans]]
#name = "friends and family"
#total_articles = 2000
#total_categories = 50
#total_webhooks = 5
1 change: 1 addition & 0 deletions pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type UserPlan struct {
Name string `toml:"name" json:"name"`
TotalArticles uint `toml:"total_articles" json:"total_articles"`
TotalCategories uint `toml:"total_categories" json:"total_categories"`
TotalWebhooks uint `toml:"total_webhooks" json:"total_webhooks"`
}

// GetUserPlan return an user plan by its name and fallback to first plan if missing
Expand Down
1 change: 1 addition & 0 deletions pkg/db/incoming-webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type IncomingWebhookRepository interface {
GetIncomingWebhookByToken(token string) (*model.IncomingWebhook, error)
GetIncomingWebhookByUserAndAlias(uid uint, alias string) (*model.IncomingWebhook, error)
GetIncomingWebhooksByUser(uid uint) ([]model.IncomingWebhook, error)
CountIncomingWebhooksByUser(uid uint) (uint, error)
CreateIncomingWebhookForUser(uid uint, form model.IncomingWebhookCreateForm) (*model.IncomingWebhook, error)
UpdateIncomingWebhookForUser(uid uint, form model.IncomingWebhookUpdateForm) (*model.IncomingWebhook, error)
DeleteIncomingWebhookByUser(uid uint, id uint) error
Expand Down
2 changes: 1 addition & 1 deletion pkg/db/postgres/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func mapRowToCategory(row *sql.Row) (*model.Category, error) {
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
return nil, mapError(err)
}
return cat, nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/db/postgres/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func mapRowToDevice(row *sql.Row) (*model.Device, error) {
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
return nil, mapError(err)
}
if err = device.SetSubscription(sub); err != nil {
return nil, err
Expand Down
16 changes: 15 additions & 1 deletion pkg/db/postgres/incoming-webhooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func mapRowToIncomingWebhook(row *sql.Row) (*model.IncomingWebhook, error) {
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
return nil, mapError(err)
}
return &result, nil
}
Expand Down Expand Up @@ -156,6 +156,20 @@ func (pg *DB) GetIncomingWebhooksByUser(uid uint) ([]model.IncomingWebhook, erro
return result, nil
}

// CountIncomingWebhooksByUser returns total nb of incoming webhooks of an user from the DB
func (pg *DB) CountIncomingWebhooksByUser(uid uint) (uint, error) {
counter := pg.psql.Select("count(*)").From(
"incoming_webhooks",
).Where(sq.Eq{"user_id": uid})
query, args, _ := counter.ToSql()

var count uint
if err := pg.db.QueryRow(query, args...).Scan(&count); err != nil {
return 0, err
}
return count, nil
}

// DeleteIncomingWebhookByUser removes an inboundService from the DB
func (pg *DB) DeleteIncomingWebhookByUser(uid uint, id uint) error {
query, args, _ := pg.psql.Delete("incoming_webhooks").Where(
Expand Down
2 changes: 1 addition & 1 deletion pkg/db/postgres/outgoing-webhooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func mapRowToOutgoingWebhook(row *sql.Row) (*model.OutgoingWebhook, error) {
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
return nil, mapError(err)
}
return result, nil
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/schema/plan/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ var planType = graphql.NewObject(
"total_categories": &graphql.Field{
Type: graphql.Int,
},
"total_webhooks": &graphql.Field{
Type: graphql.Int,
},
},
},
)
24 changes: 24 additions & 0 deletions pkg/service/incoming_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,30 @@ func (reg *Registry) GetIncomingWebhook(ctx context.Context, id uint) (*model.In
func (reg *Registry) CreateIncomingWebhook(ctx context.Context, form model.IncomingWebhookCreateForm) (*model.IncomingWebhook, error) {
uid := getCurrentUserIDFromContext(ctx)

// Validate user quota
plan, err := reg.GetCurrentUserPlan(ctx)
if err != nil {
return nil, err
}
if plan != nil {
totalWebhooks, err := reg.db.CountIncomingWebhooksByUser(uid)
if err != nil {
reg.logger.Info().Err(err).Uint(
"uid", uid,
).Msg("unable to create incoming webhook")
return nil, err
}
if totalWebhooks >= plan.TotalWebhooks {
err = ErrUserQuotaReached
reg.logger.Info().Err(err).Uint(
"uid", uid,
).Uint(
"total", plan.TotalCategories,
).Msg("unable to create incoming webhook")
return nil, err
}
}

result, err := reg.db.CreateIncomingWebhookForUser(uid, form)
if err != nil {
reg.logger.Info().Err(err).Uint(
Expand Down
47 changes: 47 additions & 0 deletions pkg/service/test/incoming_webhooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dbtest

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/ncarlier/readflow/pkg/model"
"github.com/ncarlier/readflow/pkg/service"
)

func TestCreateIncomingWebHook(t *testing.T) {
teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)

// Create new webhook
builder := model.NewIncomingWebhookCreateFormBuilder()
form := builder.Alias("test").Build()

webhook, err := service.Lookup().CreateIncomingWebhook(testContext, *form)
assert.Nil(t, err)
assert.Equal(t, "test", webhook.Alias)
assert.NotEmpty(t, webhook.Token)

// Create same webhook again
_, err = service.Lookup().CreateIncomingWebhook(testContext, *form)
assert.Equal(t, "already exists", err.Error())
}

func TestCreateIncomingWebhooksExceedingQuota(t *testing.T) {
teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)

// Create 3 webhooks (quota is 2)
for i := 1; i <= 3; i++ {
builder := model.NewIncomingWebhookCreateFormBuilder()
form := builder.Alias(fmt.Sprintf("TestCreateWebhooksExceedingQuota-%d", i)).Build()
_, err := service.Lookup().CreateIncomingWebhook(testContext, *form)
if i <= 2 {
assert.Nil(t, err)
} else {
assert.NotNil(t, err)
assert.Equal(t, service.ErrUserQuotaReached.Error(), err.Error())
}
}
}
1 change: 1 addition & 0 deletions pkg/service/test/test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ db = "${READFLOW_DB}"
name = "test"
total_articles = 5
total_categories = 2
total_webhooks = 2
5 changes: 5 additions & 0 deletions ui/src/settings/preferences/UserPlanSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ export const GetPlans = gql`
name
total_articles
total_categories
total_webhooks
}
}
`
interface Plan {
name: string
total_articles: number
total_categories: number
total_webhooks: number
}

export interface GetPlansResponse {
Expand Down Expand Up @@ -50,6 +52,9 @@ const UserPlanBox = ({ plans }: UserPlanBoxProps) => {
<li>
Max number of categories: <b>{plan.total_categories}</b>
</li>
<li>
Max number of incoming webhooks: <b>{plan.total_webhooks}</b>
</li>
{plan.name === 'premium' && (
<li>
RSS feeds with a dedicated&nbsp;
Expand Down

0 comments on commit 016b0c6

Please sign in to comment.