~netlandish/links

067e1c9238a37d29262494dd12bec3cd9ca838a4 — Peter Sanchez 2 months ago 58a5080
Move to BaseService format
M accounts/routes.go => accounts/routes.go +26 -32
@@ 3,7 3,6 @@ package accounts
import (
	"database/sql"
	"errors"
	"fmt"
	"links"
	"net/http"



@@ 28,27 27,26 @@ import (

// Service is the base accounts service struct
type Service struct {
	name  string
	server.BaseService
	fetch *UserFetch
	eg    *echo.Group
}

// RegisterRoutes ...
func (s *Service) RegisterRoutes() {
	s.eg.GET("/register", s.Register).Name = s.RouteName("register")
	s.eg.POST("/register", s.Register).Name = s.RouteName("register_post")
	s.eg.GET("/register/:key", s.Register).Name = s.RouteName("register_invitation")
	s.eg.POST("/register/:key", s.Register).Name = s.RouteName("register_invitation_post")
	s.eg.GET("/register/confirm", s.CompleteRegister).Name = s.RouteName("complete_register")
	s.eg.POST("/register/confirm", s.CompleteRegister).Name = s.RouteName("complete_register_post")
	s.Group.GET("/register", s.Register).Name = s.RouteName("register")
	s.Group.POST("/register", s.Register).Name = s.RouteName("register_post")
	s.Group.GET("/register/:key", s.Register).Name = s.RouteName("register_invitation")
	s.Group.POST("/register/:key", s.Register).Name = s.RouteName("register_invitation_post")
	s.Group.GET("/register/confirm", s.CompleteRegister).Name = s.RouteName("complete_register")
	s.Group.POST("/register/confirm", s.CompleteRegister).Name = s.RouteName("complete_register_post")

	// Register default routes from gobwebs
	gservice := accounts.NewService(s.eg, s.name, s.fetch)
	gservice := accounts.NewService(s.Group, s.Name, s.fetch, s.RenderFunc)
	gservice.RegisterRoutes()
	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/settings", s.Settings).Name = s.RouteName("settings")
	s.eg.GET("/profile", s.EditProfile).Name = s.RouteName("profile_edit")
	s.eg.POST("/profile", s.EditProfile).Name = s.RouteName("profile_edit_post")
	s.Group.Use(auth.AuthRequired())
	s.Group.GET("/settings", s.Settings).Name = s.RouteName("settings")
	s.Group.GET("/profile", s.EditProfile).Name = s.RouteName("profile_edit")
	s.Group.POST("/profile", s.EditProfile).Name = s.RouteName("profile_edit_post")
}

// EditProfile ..


@@ 118,7 116,7 @@ func (s *Service) EditProfile(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "profile_edit.html", gmap)
				return s.Render(c, http.StatusOK, "profile_edit.html", gmap)
			default:
				// Raise ISE if error is unrelated to input validation
				return err


@@ 166,7 164,7 @@ func (s *Service) EditProfile(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "profile_edit.html", gmap)
					return s.Render(c, http.StatusOK, "profile_edit.html", gmap)
				}
			}
			return err


@@ 193,7 191,7 @@ func (s *Service) EditProfile(c echo.Context) error {
		gmap["image"] = userOrg.Image
	}

	return links.Render(c, http.StatusOK, "profile_edit.html", gmap)
	return s.Render(c, http.StatusOK, "profile_edit.html", gmap)
}

// Settings ...


@@ 241,7 239,7 @@ func (s *Service) Settings(c echo.Context) error {
		"langTrans":      langTrans,
		"navFlag":        "settings",
	}
	return links.Render(c, http.StatusOK, "settings.html", gmap)
	return s.Render(c, http.StatusOK, "settings.html", gmap)
}

// CompleteRegister ...


@@ 282,7 280,7 @@ func (s *Service) CompleteRegister(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "complete_register.html", gmap)
				return s.Render(c, http.StatusOK, "complete_register.html", gmap)
			default:
				// Raise ISE if error is unrelated to input validation
				return err


@@ 327,7 325,7 @@ func (s *Service) CompleteRegister(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "complete_register.html", gmap)
					return s.Render(c, http.StatusOK, "complete_register.html", gmap)
				}
			}
			return err


@@ 336,7 334,7 @@ func (s *Service) CompleteRegister(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("accounts:login"))

	}
	return links.Render(c, http.StatusOK, "complete_register.html", gmap)
	return s.Render(c, http.StatusOK, "complete_register.html", gmap)
}

// Register is ...


@@ 364,7 362,7 @@ func (s *Service) Register(c echo.Context) error {
	gmap := gobwebs.Map{"pd": pd, "key": key, "form": form}
	if !links.IsRegistrationEnabled(c) && key == "" {
		gmap["no_reg"] = true
		return links.Render(c, http.StatusOK, "register.html", gmap)
		return s.Render(c, http.StatusOK, "register.html", gmap)
	}
	if key != "" {
		conf, err = gaccounts.GetConfirmation(c.Request().Context(), key)


@@ 396,7 394,7 @@ func (s *Service) Register(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "register.html", gmap)
				return s.Render(c, http.StatusOK, "register.html", gmap)
			default:
				// Raise ISE if error is unrelated to input validation
				return err


@@ 429,7 427,7 @@ func (s *Service) Register(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "register.html", gmap)
					return s.Render(c, http.StatusOK, "register.html", gmap)
				}
			}
			return err


@@ 444,17 442,13 @@ func (s *Service) Register(c echo.Context) error {
		}
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("accounts:login"))
	}
	return links.Render(c, http.StatusOK, "register.html", gmap)
}

// RouteName ...
func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
	return s.Render(c, http.StatusOK, "register.html", gmap)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "accounts", fetch: NewUserFetch(), eg: eg}
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "accounts", render)
	service := &Service{BaseService: baseService, fetch: NewUserFetch()}
	service.RegisterRoutes()
	return service
}

M admin/routes.go => admin/routes.go +51 -56
@@ 24,35 24,34 @@ import (
)

type Service struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (s *Service) RegisterRoutes() {
	s.eg.Use(auth.AuthRequired())
	s.eg.Use(superUserRequierd())
	s.eg.GET("", s.Dashboard).Name = s.RouteName("dashboard")
	s.eg.GET("/user", s.UserList).Name = s.RouteName("user_list")
	s.eg.GET("/user/invite", s.UserInvitation).Name = s.RouteName("user_invitation")
	s.eg.POST("/user/invite", s.UserInvitation).Name = s.RouteName("user_invitation")
	s.eg.GET("/user/:id", s.UserDetail).Name = s.RouteName("user_detail")
	s.eg.GET("/user/:id/edit", s.UserUpdate).Name = s.RouteName("user_edit")
	s.eg.POST("/user/:id/edit", s.UserUpdate).Name = s.RouteName("user_edit_post")
	s.eg.GET("/user/:id/lock", s.UserToggleLock).Name = s.RouteName("user_toggle_lock")
	s.eg.POST("/user/:id/lock", s.UserToggleLock).Name = s.RouteName("user_toggle_lock")
	s.eg.GET("/organization", s.OrgList).Name = s.RouteName("org_list")
	s.eg.GET("/organization/:id", s.OrgDetail).Name = s.RouteName("org_detail")
	s.eg.GET("/organization/:id/type", s.UpdateOrgType).Name = s.RouteName("update_org_type")
	s.eg.POST("/organization/:id/type", s.UpdateOrgType).Name = s.RouteName("update_org_type")
	s.eg.GET("/domain", s.DomainList).Name = s.RouteName("domain_list")
	s.eg.GET("/domain/:id", s.DomainDetail).Name = s.RouteName("domain_detail")
	s.eg.GET("/domain/add", s.DomainCreate).Name = s.RouteName("domain_create")
	s.eg.POST("/domain/add", s.DomainCreate).Name = s.RouteName("domain_create")
	s.eg.GET("/domain/:id/edit", s.DomainUpdate).Name = s.RouteName("domain_edit")
	s.eg.POST("/domain/:id/edit", s.DomainUpdate).Name = s.RouteName("domain_edit")
	s.eg.GET("/billing", s.BillingList).Name = s.RouteName("billing_list")

	s.eg.GET("/autocomplete", s.Autocomplete).Name = s.RouteName("autocomplete")
	s.Group.Use(auth.AuthRequired())
	s.Group.Use(superUserRequierd())
	s.Group.GET("", s.Dashboard).Name = s.RouteName("dashboard")
	s.Group.GET("/user", s.UserList).Name = s.RouteName("user_list")
	s.Group.GET("/user/invite", s.UserInvitation).Name = s.RouteName("user_invitation")
	s.Group.POST("/user/invite", s.UserInvitation).Name = s.RouteName("user_invitation")
	s.Group.GET("/user/:id", s.UserDetail).Name = s.RouteName("user_detail")
	s.Group.GET("/user/:id/edit", s.UserUpdate).Name = s.RouteName("user_edit")
	s.Group.POST("/user/:id/edit", s.UserUpdate).Name = s.RouteName("user_edit_post")
	s.Group.GET("/user/:id/lock", s.UserToggleLock).Name = s.RouteName("user_toggle_lock")
	s.Group.POST("/user/:id/lock", s.UserToggleLock).Name = s.RouteName("user_toggle_lock")
	s.Group.GET("/organization", s.OrgList).Name = s.RouteName("org_list")
	s.Group.GET("/organization/:id", s.OrgDetail).Name = s.RouteName("org_detail")
	s.Group.GET("/organization/:id/type", s.UpdateOrgType).Name = s.RouteName("update_org_type")
	s.Group.POST("/organization/:id/type", s.UpdateOrgType).Name = s.RouteName("update_org_type")
	s.Group.GET("/domain", s.DomainList).Name = s.RouteName("domain_list")
	s.Group.GET("/domain/:id", s.DomainDetail).Name = s.RouteName("domain_detail")
	s.Group.GET("/domain/add", s.DomainCreate).Name = s.RouteName("domain_create")
	s.Group.POST("/domain/add", s.DomainCreate).Name = s.RouteName("domain_create")
	s.Group.GET("/domain/:id/edit", s.DomainUpdate).Name = s.RouteName("domain_edit")
	s.Group.POST("/domain/:id/edit", s.DomainUpdate).Name = s.RouteName("domain_edit")
	s.Group.GET("/billing", s.BillingList).Name = s.RouteName("billing_list")

	s.Group.GET("/autocomplete", s.Autocomplete).Name = s.RouteName("autocomplete")
}

type Item struct {


@@ 234,7 233,7 @@ func (s *Service) Dashboard(c echo.Context) error {
	}
	gmap["billingStats"] = billingStatsResult.BillingStats

	return links.Render(c, http.StatusOK, "admin_dashboard.html", gmap)
	return s.Render(c, http.StatusOK, "admin_dashboard.html", gmap)
}

func (s *Service) UpdateOrgType(c echo.Context) error {


@@ 296,7 295,7 @@ func (s *Service) UpdateOrgType(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "admin_update_org_type.html", gmap)
				return s.Render(c, http.StatusOK, "admin_update_org_type.html", gmap)
			default:
				return err
			}


@@ 324,7 323,7 @@ func (s *Service) UpdateOrgType(c echo.Context) error {

	form.OrgType = result.Org.Settings.Billing.Status
	gmap["form"] = form
	return links.Render(c, http.StatusOK, "admin_update_org_type.html", gmap)
	return s.Render(c, http.StatusOK, "admin_update_org_type.html", gmap)
}

func (s *Service) OrgDetail(c echo.Context) error {


@@ 504,7 503,7 @@ func (s *Service) OrgDetail(c echo.Context) error {
		"domains":  domainsResult.Organizations.Result,
		"stats":    statsResult.Stats,
	}
	return links.Render(c, http.StatusOK, "admin_org_detail.html", gmap)
	return s.Render(c, http.StatusOK, "admin_org_detail.html", gmap)
}

func (s *Service) DomainDetail(c echo.Context) error {


@@ 569,7 568,7 @@ func (s *Service) DomainDetail(c echo.Context) error {
		"navFlag": "admin",
		"domain":  result.Domain,
	}
	return links.Render(c, http.StatusOK, "admin_domain_detail.html", gmap)
	return s.Render(c, http.StatusOK, "admin_domain_detail.html", gmap)
}

func (s *Service) DomainUpdate(c echo.Context) error {


@@ 653,7 652,7 @@ func (s *Service) DomainUpdate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
				return s.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
			default:
				return err
			}


@@ 699,7 698,7 @@ func (s *Service) DomainUpdate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
					return s.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
				}
			}
			return err


@@ 718,7 717,7 @@ func (s *Service) DomainUpdate(c echo.Context) error {
		gmap["current_org"] = fmt.Sprintf("%d: %s (%s)", domain.OrgID.Int64, domain.OrgName.String, domain.OrgSlug.String)
	}

	return links.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
	return s.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
}

func (s *Service) DomainCreate(c echo.Context) error {


@@ 782,7 781,7 @@ func (s *Service) DomainCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
				return s.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
			default:
				return err
			}


@@ 828,7 827,7 @@ func (s *Service) DomainCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
					return s.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
				}
			}
			return err


@@ 837,7 836,7 @@ func (s *Service) DomainCreate(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse(s.RouteName("domain_list")))

	}
	return links.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
	return s.Render(c, http.StatusOK, "admin_domain_create.html", gmap)
}

func (s *Service) BillingList(c echo.Context) error {


@@ 1030,7 1029,7 @@ func (s *Service) BillingList(c echo.Context) error {
		tmpQueryParams.Set("next", historyResult.Payments.PageInfo.Cursor)
		gmap["nextURL"] = template.URL(tmpQueryParams.Encode())
	}
	return links.Render(c, http.StatusOK, "admin_billing_list.html", gmap)
	return s.Render(c, http.StatusOK, "admin_billing_list.html", gmap)
}

func (s *Service) DomainList(c echo.Context) error {


@@ 1177,7 1176,7 @@ func (s *Service) DomainList(c echo.Context) error {
		gmap["nextURL"] = links.GetPaginationParams("next", "", query, result.Organizations.PageInfo.Cursor)
	}
	gmap["domains"] = result.Organizations.Result
	return links.Render(c, http.StatusOK, "admin_domains_list.html", gmap)
	return s.Render(c, http.StatusOK, "admin_domains_list.html", gmap)
}

func (s *Service) OrgList(c echo.Context) error {


@@ 1254,7 1253,7 @@ func (s *Service) OrgList(c echo.Context) error {
		gmap["nextURL"] = links.GetPaginationParams("next", "", query, result.Organizations.PageInfo.Cursor)
	}
	gmap["orgs"] = result.Organizations.Result
	return links.Render(c, http.StatusOK, "admin_organization_list.html", gmap)
	return s.Render(c, http.StatusOK, "admin_organization_list.html", gmap)

}



@@ 1302,7 1301,7 @@ func (s *Service) UserToggleLock(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "admin_user_lock.html", gmap)
					return s.Render(c, http.StatusOK, "admin_user_lock.html", gmap)
				default:
					return err
				}


@@ 1345,7 1344,7 @@ func (s *Service) UserToggleLock(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse(s.RouteName("user_detail"), id))
	}

	return links.Render(c, http.StatusOK, "admin_user_lock.html", gmap)
	return s.Render(c, http.StatusOK, "admin_user_lock.html", gmap)
}

func (s *Service) UserDetail(c echo.Context) error {


@@ 1439,7 1438,7 @@ func (s *Service) UserDetail(c echo.Context) error {
		"user":    user,
		"orgs":    orgResult.Organizations.Result,
	}
	return links.Render(c, http.StatusOK, "admin_user_detail.html", gmap)
	return s.Render(c, http.StatusOK, "admin_user_detail.html", gmap)
}

func (s *Service) UserUpdate(c echo.Context) error {


@@ 1485,7 1484,7 @@ func (s *Service) UserUpdate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "admin_user_edit.html", gmap)
				return s.Render(c, http.StatusOK, "admin_user_edit.html", gmap)
			default:
				return err
			}


@@ 1513,7 1512,7 @@ func (s *Service) UserUpdate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "admin_user_edit.html", gmap)
					return s.Render(c, http.StatusOK, "admin_user_edit.html", gmap)
				}
			}
			return err


@@ 1524,7 1523,7 @@ func (s *Service) UserUpdate(c echo.Context) error {
	form.Name = user.Name
	form.Email = user.Email
	form.IsVerified = user.IsVerified()
	return links.Render(c, http.StatusOK, "admin_user_edit.html", gmap)
	return s.Render(c, http.StatusOK, "admin_user_edit.html", gmap)
}

func (s *Service) UserInvitation(c echo.Context) error {


@@ 1546,7 1545,7 @@ func (s *Service) UserInvitation(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "admin_invite_user.html", gmap)
				return s.Render(c, http.StatusOK, "admin_invite_user.html", gmap)
			default:
				return err
			}


@@ 1572,7 1571,7 @@ func (s *Service) UserInvitation(c echo.Context) error {
		messages.Success(c, lt.Translate("A register invitation was sent to %s", result.RegisterInvitation.Email))
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse(s.RouteName("user_list")))
	}
	return links.Render(c, http.StatusOK, "admin_invite_user.html", gmap)
	return s.Render(c, http.StatusOK, "admin_invite_user.html", gmap)
}

func (s *Service) UserList(c echo.Context) error {


@@ 1643,17 1642,13 @@ func (s *Service) UserList(c echo.Context) error {
		gmap["nextURL"] = links.GetPaginationParams("next", "", query, result.GetUsers.PageInfo.Cursor)
	}
	gmap["users"] = result.GetUsers.Result
	return links.Render(c, http.StatusOK, "admin_user_list.html", gmap)
}

// RouteName ...
func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
	return s.Render(c, http.StatusOK, "admin_user_list.html", gmap)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "admin", eg: eg}
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "admin", render)
	service := &Service{BaseService: baseService}
	service.RegisterRoutes()
	return service
}

M analytics/routes.go => analytics/routes.go +9 -12
@@ 1,7 1,6 @@
package analytics

import (
	"fmt"
	"links"
	"links/internal/localizer"
	"links/models"


@@ 16,17 15,18 @@ import (
	"netlandish.com/x/gobwebs"
	"netlandish.com/x/gobwebs/auth"
	"netlandish.com/x/gobwebs/database"
	"netlandish.com/x/gobwebs/server"
	"netlandish.com/x/gobwebs/timezone"
	"netlandish.com/x/gobwebs/validate"
)

type Service struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (s *Service) RegisterRoutes() {
	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/:type/:id", s.Detail).Name = s.RouteName("detail")
	s.Group.Use(auth.AuthRequired())
	s.Group.GET("/:type/:id", s.Detail).Name = s.RouteName("detail")
}

func (s *Service) Detail(c echo.Context) error {


@@ 230,16 230,13 @@ func (s *Service) Detail(c echo.Context) error {
		"org":                  org,
		"back":                 back,
	}
	return links.Render(c, http.StatusOK, "analytics_detail.html", gmap)
}

func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
	return s.Render(c, http.StatusOK, "analytics_detail.html", gmap)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "analytics", eg: eg}
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "analytics", render)
	service := &Service{BaseService: baseService}
	service.RegisterRoutes()
	return service
}

M analytics/routes_test.go => analytics/routes_test.go +2 -1
@@ 2,6 2,7 @@ package analytics_test

import (
	"fmt"
	"links"
	"links/analytics"
	"links/cmd"
	"links/cmd/test"


@@ 26,7 27,7 @@ func TestHandler(t *testing.T) {
	c := require.New(t)
	srv, e := test.NewWebTestServer(t)
	cmd.RunMigrations(t, srv.DB)
	analyticsService := analytics.NewService(e.Group("/analytics"))
	analyticsService := analytics.NewService(e.Group("/analytics"), links.Render)
	user := test.NewTestUser(1, false, false, true, true)
	defer srv.Shutdown()
	go srv.Run()

M billing/routes.go => billing/routes.go +28 -32
@@ 23,33 23,21 @@ import (
)

type Service struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (s *Service) RegisterRoutes() {
	s.eg.POST("/stripe/webhook", s.StripeWebhook).Name = s.RouteName("stripe_webhook")
	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/:slug/subscription/select-plan", s.CreateSubscription).Name = s.RouteName("create_subscription")
	s.eg.POST("/:slug/subscription/select-plan", s.CreateSubscription).Name = s.RouteName("create_subscription_post")
	s.eg.GET("/:slug/subscription/cancel", s.CancelSubscription).Name = s.RouteName("cancel_subscription")
	s.eg.POST("/:slug/subscription/cancel", s.CancelSubscription).Name = s.RouteName("cancel_subscription")
	s.eg.GET("/:slug/subscription/portal", s.SubscriptionPortal).Name = s.RouteName("subscription_portal")
	s.eg.GET("/:slug/subscription", s.ManageSubscription).Name = s.RouteName("manage_subscription")
	s.eg.GET("/:slug/subscription/history", s.SubscriptionHistory).Name = s.RouteName("subscription_history")
	s.eg.GET("/success", s.CheckoutSuccess).Name = s.RouteName("success")
	s.eg.GET("/cancel", s.CheckoutCancel).Name = s.RouteName("cancel")
}

func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "billing", eg: eg}
	service.RegisterRoutes()
	return service
	s.Group.POST("/stripe/webhook", s.StripeWebhook).Name = s.RouteName("stripe_webhook")
	s.Group.Use(auth.AuthRequired())
	s.Group.GET("/:slug/subscription/select-plan", s.CreateSubscription).Name = s.RouteName("create_subscription")
	s.Group.POST("/:slug/subscription/select-plan", s.CreateSubscription).Name = s.RouteName("create_subscription_post")
	s.Group.GET("/:slug/subscription/cancel", s.CancelSubscription).Name = s.RouteName("cancel_subscription")
	s.Group.POST("/:slug/subscription/cancel", s.CancelSubscription).Name = s.RouteName("cancel_subscription")
	s.Group.GET("/:slug/subscription/portal", s.SubscriptionPortal).Name = s.RouteName("subscription_portal")
	s.Group.GET("/:slug/subscription", s.ManageSubscription).Name = s.RouteName("manage_subscription")
	s.Group.GET("/:slug/subscription/history", s.SubscriptionHistory).Name = s.RouteName("subscription_history")
	s.Group.GET("/success", s.CheckoutSuccess).Name = s.RouteName("success")
	s.Group.GET("/cancel", s.CheckoutCancel).Name = s.RouteName("cancel")
}

func (s *Service) SubscriptionPortal(c echo.Context) error {


@@ 184,7 172,7 @@ func (s *Service) CheckoutSuccess(c echo.Context) error {
		"invoiceNumber": invoiceNumber,
		"invoiceURL":    invoiceURL,
	}
	return links.Render(c, http.StatusOK, "billing_checkout.html", gmap)
	return s.Render(c, http.StatusOK, "billing_checkout.html", gmap)
}

func (s *Service) CheckoutCancel(c echo.Context) error {


@@ 195,7 183,7 @@ func (s *Service) CheckoutCancel(c echo.Context) error {
	gmap := gobwebs.Map{
		"pd": pd,
	}
	return links.Render(c, http.StatusOK, "billing_checkout.html", gmap)
	return s.Render(c, http.StatusOK, "billing_checkout.html", gmap)

}



@@ 299,7 287,7 @@ func (s *Service) SubscriptionHistory(c echo.Context) error {
	if result.Payments.PageInfo.HasNextPage {
		gmap["nextURL"] = links.GetPaginationParams("next", "", "", result.Payments.PageInfo.Cursor)
	}
	return links.Render(c, http.StatusOK, "billing_history.html", gmap)
	return s.Render(c, http.StatusOK, "billing_history.html", gmap)

}



@@ 380,7 368,7 @@ func (s *Service) ManageSubscription(c echo.Context) error {
		"invoices": invoices,
		"org":      org,
	}
	return links.Render(c, http.StatusOK, "billing_manage_subscription.html", gmap)
	return s.Render(c, http.StatusOK, "billing_manage_subscription.html", gmap)
}

func (s *Service) CreateSubscription(c echo.Context) error {


@@ 488,7 476,7 @@ func (s *Service) CreateSubscription(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "billing_create_subscription.html", gmap)
				return s.Render(c, http.StatusOK, "billing_create_subscription.html", gmap)
			default:
				return err
			}


@@ 508,7 496,7 @@ func (s *Service) CreateSubscription(c echo.Context) error {
				[]error{fmt.Errorf(lt.Translate("Invalid subscription plan ID given"))},
			)
			gmap["errors"] = err
			return links.Render(c, http.StatusOK, "billing_create_subscription.html", gmap)
			return s.Render(c, http.StatusOK, "billing_create_subscription.html", gmap)
		}

		successURL := links.GetLinksDomainURL(c)


@@ 540,7 528,7 @@ func (s *Service) CreateSubscription(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, stripeSession.URL)

	}
	return links.Render(c, http.StatusOK, "billing_create_subscription.html", gmap)
	return s.Render(c, http.StatusOK, "billing_create_subscription.html", gmap)
}

func (s *Service) CancelSubscription(c echo.Context) error {


@@ 615,5 603,13 @@ func (s *Service) CancelSubscription(c echo.Context) error {
		"elementsToBeDisabled": elementsToBeDisabled,
		"back":                 back,
	}
	return links.Render(c, http.StatusOK, "billing_cancel_subscription.html", gmap)
	return s.Render(c, http.StatusOK, "billing_cancel_subscription.html", gmap)
}

// NewService return service
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "billing", render)
	service := &Service{BaseService: baseService}
	service.RegisterRoutes()
	return service
}

M billing/routes_test.go => billing/routes_test.go +2 -1
@@ 1,6 1,7 @@
package billing_test

import (
	"links"
	"links/billing"
	"links/cmd"
	"links/cmd/test"


@@ 25,7 26,7 @@ func TestHandlers(t *testing.T) {
	c := require.New(t)
	srv, e := test.NewWebTestServer(t)
	cmd.RunMigrations(t, srv.DB)
	billingService := billing.NewService(e.Group("/billing"))
	billingService := billing.NewService(e.Group("/billing"), links.Render)
	user := test.NewTestUser(1, false, false, true, true)
	defer srv.Shutdown()
	go srv.Run()

M cmd/links/main.go => cmd/links/main.go +9 -9
@@ 296,11 296,11 @@ func run() error {
	e.GET("/static/*", echo.WrapHandler(staticHandler))
	e.Static(config.MediaURL, "media")
	accountGroup := e.Group("/accounts")
	accounts.NewService(accountGroup)
	accounts.NewService(accountGroup, links.Render)
	feedback.NewService(e.Group("/email-notify"), &mailChecker, true, "")

	coreService := e.Group("")
	core.NewService(coreService)
	core.NewService(coreService, links.Render)

	// graphql playground
	gqlConfig := &gobwebsgql.ServiceConfig{


@@ 315,19 315,19 @@ func run() error {
	gobwebsgql.NewService(gqlGroup, gqlConfig)

	slackService := e.Group("/slack")
	slack.NewService(slackService)
	slack.NewService(slackService, links.Render)
	mattermostService := e.Group("/mattermost")
	mattermost.NewService(mattermostService)
	mattermost.NewService(mattermostService, links.Render)
	shortService := e.Group("/:slug/short")
	short.NewService(shortService)
	short.NewService(shortService, links.Render)
	listService := e.Group("/:slug/list")
	list.NewService(listService)
	list.NewService(listService, links.Render)
	adminService := e.Group("/admin")
	admin.NewService(adminService)
	admin.NewService(adminService, links.Render)
	analyticsService := e.Group("/:slug/analytics")
	analytics.NewService(analyticsService)
	analytics.NewService(analyticsService, links.Render)
	billingService := e.Group("/billing")
	billing.NewService(billingService)
	billing.NewService(billingService, links.Render)

	oauthConfig := oauth2.ServiceConfig{
		Helper:           &core.OAuth2Helper{},

M cmd/list/main.go => cmd/list/main.go +1 -1
@@ 136,7 136,7 @@ func run() error {
	e.Static(config.MediaURL, "media")

	detailService := e.Group("")
	list.NewDetailService(detailService)
	list.NewDetailService(detailService, links.Render)
	srv.Run()

	return nil

M cmd/short/main.go => cmd/short/main.go +1 -1
@@ 98,7 98,7 @@ func run() error {
	}

	redirectService := e.Group("")
	short.NewRedirectService(redirectService)
	short.NewRedirectService(redirectService, links.Render)
	srv.AddFuncs(template.FuncMap{
		"staticURL": func(path string) string {
			url, _ := url.JoinPath(config.StaticURL, path)

M core/routes.go => core/routes.go +114 -119
@@ 32,75 32,74 @@ import (

// Service is the base accounts service struct
type Service struct {
	name string
	eg   *echo.Group
	server.BaseService
}

// RegisterRoutes ...
func (s *Service) RegisterRoutes() {
	s.eg.GET("/", s.Homepage).Name = s.RouteName("index")
	s.eg.GET("/invalid/:d", s.InvalidDomain).Name = s.RouteName("invalid_domain")
	s.eg.GET("/:slug", s.OrgLinksList).Name = s.RouteName("org_link_list")
	s.eg.GET("/:slug/rss", s.OrgLinksList).Name = s.RouteName("org_link_list_rss")
	s.eg.GET("/:slug/rss/:hash", s.OrgLinksList).Name = s.RouteName("org_link_private_rss")
	s.eg.GET("/recent", s.OrgLinksList).Name = s.RouteName("recent_link_list")
	s.eg.GET("/recent/rss", s.OrgLinksList).Name = s.RouteName("recent_link_list_rss")
	s.eg.GET("/popular", s.PopularLinkList).Name = s.RouteName("popular_link_list")
	s.eg.GET("/popular/rss", s.PopularLinkList).Name = s.RouteName("popular_link_list_rss")
	s.eg.GET("/lang", SwiftLang).Name = s.RouteName("lang_switch")
	s.eg.GET("/member/confirm", s.OrgMemberConfirmation).Name = s.RouteName("org_member_confirmation")
	s.eg.GET("/q/:hash", s.QRRedirect).Name = s.RouteName("qr_redirect")
	s.eg.GET("/tour", s.FeatureTour).Name = s.RouteName("feature_tour")
	s.eg.GET("/pricing", s.PricingList).Name = s.RouteName("pricing_list")
	s.eg.GET("/note/:hash", s.NoteDetail).Name = s.RouteName("note_detail")
	s.eg.GET("/link/:hash", s.OrgLinkDetail).Name = s.RouteName("link_detail")
	s.eg.GET("/click/:hash", s.OrgLinkRedirect).Name = s.RouteName("link_redirect")
	s.eg.GET("/tag-autocomplete", s.TagAutocomplete).Name = s.RouteName("tag_autocomplete")

	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/:slug/follow-toggle/:action", s.FollowToggle).Name = s.RouteName("follow-toggle")
	s.eg.GET("/feed", s.UserFeed).Name = s.RouteName("user_feed")
	s.eg.GET("/feed/rss", s.UserFeed).Name = s.RouteName("user_feed_rss")
	s.eg.GET("/feed/following", s.UserFeedFollowing).Name = s.RouteName("user_feed_following")
	s.eg.GET("/:slug/domains/add", s.DomainCreate).Name = s.RouteName("domain_add")
	s.eg.POST("/:slug/domains/add", s.DomainCreate).Name = s.RouteName("domain_add_post")
	s.eg.GET("/:slug/domains", s.DomainList).Name = s.RouteName("domain_list")
	s.eg.GET("/:slug/domains/:id/delete", s.DomainDelete).Name = s.RouteName("domain_delete")
	s.eg.POST("/:slug/domains/:id/delete", s.DomainDelete).Name = s.RouteName("domain_delete_post")

	s.eg.GET("/organizations", s.OrgList).Name = s.RouteName("org_list")
	s.eg.GET("/organization/add", s.OrgCreate).Name = s.RouteName("org_create")
	s.eg.POST("/organization/add", s.OrgCreate).Name = s.RouteName("org_create_post")
	s.eg.GET("/organization/switch/:slug", s.OrgSwitch).Name = s.RouteName("org_switch")
	s.eg.GET("/:slug/edit", s.OrgUpdate).Name = s.RouteName("org_edit")
	s.eg.POST("/:slug/edit", s.OrgUpdate).Name = s.RouteName("org_edit_post")
	s.eg.GET("/:slug/members", s.OrgMembersList).Name = s.RouteName("org_member_list")
	s.eg.GET("/:slug/members/add", s.OrgMembersAdd).Name = s.RouteName("org_member_add")
	s.eg.POST("/:slug/members/add", s.OrgMembersAdd).Name = s.RouteName("org_member_add_post")
	s.eg.GET("/:slug/members/delete/:id", s.OrgMembersDelete).Name = s.RouteName("org_member_delete")
	s.eg.POST("/:slug/members/delete/:id", s.OrgMembersDelete).Name = s.RouteName("org_member_delete_post")
	s.eg.GET("/:slug/export", s.ExportData).Name = s.RouteName("export_data")
	s.eg.POST("/:slug/export", s.ExportData).Name = s.RouteName("export_data_post")
	s.eg.GET("/:slug/import", s.ImportData).Name = s.RouteName("import_data")
	s.eg.POST("/:slug/import", s.ImportData).Name = s.RouteName("import_data")
	s.eg.GET("/:slug/integrations", s.Integrations).Name = s.RouteName("integrations")

	s.eg.GET("/home", s.OrgLinksList).Name = s.RouteName("home_link_list")
	s.eg.GET("/add", s.OrgLinksCreate).Name = s.RouteName("link_create")
	s.eg.POST("/add", s.OrgLinksCreate).Name = s.RouteName("link_create_post")
	s.eg.GET("/read/:id", s.OrgLinkAsReadToggle).Name = s.RouteName("link_mark_as_read")
	s.eg.GET("/star/:id", s.OrgLinkStarToggle).Name = s.RouteName("link_star_toggle")
	s.eg.GET("/link/:id/delete", s.OrgLinkDelete).Name = s.RouteName("link_delete")
	s.eg.POST("/link/:id/delete", s.OrgLinkDelete).Name = s.RouteName("link_delete")
	s.eg.GET("/link/:id/edit", s.OrgLinkUpdate).Name = s.RouteName("link_edit")
	s.eg.POST("/link/:id/edit", s.OrgLinkUpdate).Name = s.RouteName("link_edit_post")
	s.eg.GET("/qr/:hash/detail", s.QRManageDetail).Name = s.RouteName("qr_manage_detail")
	s.eg.GET("/qr/:id/delete", s.QRManageDelete).Name = s.RouteName("qr_manage_delete")
	s.eg.POST("/qr/:id/delete", s.QRManageDelete).Name = s.RouteName("qr_manage_delete_post")
	s.eg.GET("/note/add", s.NoteCreate).Name = s.RouteName("note_create")
	s.eg.POST("/note/add", s.NoteCreate).Name = s.RouteName("note_create_post")
	s.eg.GET("/note/:id/edit", s.NoteUpdate).Name = s.RouteName("note_edit")
	s.eg.POST("/note/:id/edit", s.NoteUpdate).Name = s.RouteName("note_edit_post")
	s.Group.GET("/", s.Homepage).Name = s.RouteName("index")
	s.Group.GET("/invalid/:d", s.InvalidDomain).Name = s.RouteName("invalid_domain")
	s.Group.GET("/:slug", s.OrgLinksList).Name = s.RouteName("org_link_list")
	s.Group.GET("/:slug/rss", s.OrgLinksList).Name = s.RouteName("org_link_list_rss")
	s.Group.GET("/:slug/rss/:hash", s.OrgLinksList).Name = s.RouteName("org_link_private_rss")
	s.Group.GET("/recent", s.OrgLinksList).Name = s.RouteName("recent_link_list")
	s.Group.GET("/recent/rss", s.OrgLinksList).Name = s.RouteName("recent_link_list_rss")
	s.Group.GET("/popular", s.PopularLinkList).Name = s.RouteName("popular_link_list")
	s.Group.GET("/popular/rss", s.PopularLinkList).Name = s.RouteName("popular_link_list_rss")
	s.Group.GET("/lang", SwiftLang).Name = s.RouteName("lang_switch")
	s.Group.GET("/member/confirm", s.OrgMemberConfirmation).Name = s.RouteName("org_member_confirmation")
	s.Group.GET("/q/:hash", s.QRRedirect).Name = s.RouteName("qr_redirect")
	s.Group.GET("/tour", s.FeatureTour).Name = s.RouteName("feature_tour")
	s.Group.GET("/pricing", s.PricingList).Name = s.RouteName("pricing_list")
	s.Group.GET("/note/:hash", s.NoteDetail).Name = s.RouteName("note_detail")
	s.Group.GET("/link/:hash", s.OrgLinkDetail).Name = s.RouteName("link_detail")
	s.Group.GET("/click/:hash", s.OrgLinkRedirect).Name = s.RouteName("link_redirect")
	s.Group.GET("/tag-autocomplete", s.TagAutocomplete).Name = s.RouteName("tag_autocomplete")

	s.Group.Use(auth.AuthRequired())
	s.Group.GET("/:slug/follow-toggle/:action", s.FollowToggle).Name = s.RouteName("follow-toggle")
	s.Group.GET("/feed", s.UserFeed).Name = s.RouteName("user_feed")
	s.Group.GET("/feed/rss", s.UserFeed).Name = s.RouteName("user_feed_rss")
	s.Group.GET("/feed/following", s.UserFeedFollowing).Name = s.RouteName("user_feed_following")
	s.Group.GET("/:slug/domains/add", s.DomainCreate).Name = s.RouteName("domain_add")
	s.Group.POST("/:slug/domains/add", s.DomainCreate).Name = s.RouteName("domain_add_post")
	s.Group.GET("/:slug/domains", s.DomainList).Name = s.RouteName("domain_list")
	s.Group.GET("/:slug/domains/:id/delete", s.DomainDelete).Name = s.RouteName("domain_delete")
	s.Group.POST("/:slug/domains/:id/delete", s.DomainDelete).Name = s.RouteName("domain_delete_post")

	s.Group.GET("/organizations", s.OrgList).Name = s.RouteName("org_list")
	s.Group.GET("/organization/add", s.OrgCreate).Name = s.RouteName("org_create")
	s.Group.POST("/organization/add", s.OrgCreate).Name = s.RouteName("org_create_post")
	s.Group.GET("/organization/switch/:slug", s.OrgSwitch).Name = s.RouteName("org_switch")
	s.Group.GET("/:slug/edit", s.OrgUpdate).Name = s.RouteName("org_edit")
	s.Group.POST("/:slug/edit", s.OrgUpdate).Name = s.RouteName("org_edit_post")
	s.Group.GET("/:slug/members", s.OrgMembersList).Name = s.RouteName("org_member_list")
	s.Group.GET("/:slug/members/add", s.OrgMembersAdd).Name = s.RouteName("org_member_add")
	s.Group.POST("/:slug/members/add", s.OrgMembersAdd).Name = s.RouteName("org_member_add_post")
	s.Group.GET("/:slug/members/delete/:id", s.OrgMembersDelete).Name = s.RouteName("org_member_delete")
	s.Group.POST("/:slug/members/delete/:id", s.OrgMembersDelete).Name = s.RouteName("org_member_delete_post")
	s.Group.GET("/:slug/export", s.ExportData).Name = s.RouteName("export_data")
	s.Group.POST("/:slug/export", s.ExportData).Name = s.RouteName("export_data_post")
	s.Group.GET("/:slug/import", s.ImportData).Name = s.RouteName("import_data")
	s.Group.POST("/:slug/import", s.ImportData).Name = s.RouteName("import_data")
	s.Group.GET("/:slug/integrations", s.Integrations).Name = s.RouteName("integrations")

	s.Group.GET("/home", s.OrgLinksList).Name = s.RouteName("home_link_list")
	s.Group.GET("/add", s.OrgLinksCreate).Name = s.RouteName("link_create")
	s.Group.POST("/add", s.OrgLinksCreate).Name = s.RouteName("link_create_post")
	s.Group.GET("/read/:id", s.OrgLinkAsReadToggle).Name = s.RouteName("link_mark_as_read")
	s.Group.GET("/star/:id", s.OrgLinkStarToggle).Name = s.RouteName("link_star_toggle")
	s.Group.GET("/link/:id/delete", s.OrgLinkDelete).Name = s.RouteName("link_delete")
	s.Group.POST("/link/:id/delete", s.OrgLinkDelete).Name = s.RouteName("link_delete")
	s.Group.GET("/link/:id/edit", s.OrgLinkUpdate).Name = s.RouteName("link_edit")
	s.Group.POST("/link/:id/edit", s.OrgLinkUpdate).Name = s.RouteName("link_edit_post")
	s.Group.GET("/qr/:hash/detail", s.QRManageDetail).Name = s.RouteName("qr_manage_detail")
	s.Group.GET("/qr/:id/delete", s.QRManageDelete).Name = s.RouteName("qr_manage_delete")
	s.Group.POST("/qr/:id/delete", s.QRManageDelete).Name = s.RouteName("qr_manage_delete_post")
	s.Group.GET("/note/add", s.NoteCreate).Name = s.RouteName("note_create")
	s.Group.POST("/note/add", s.NoteCreate).Name = s.RouteName("note_create_post")
	s.Group.GET("/note/:id/edit", s.NoteUpdate).Name = s.RouteName("note_edit")
	s.Group.POST("/note/:id/edit", s.NoteUpdate).Name = s.RouteName("note_edit_post")
}

func (s *Service) InvalidDomain(c echo.Context) error {


@@ 130,7 129,7 @@ func (s *Service) InvalidDomain(c echo.Context) error {
		return echo.NotFoundHandler(c)
	}
	gmap := gobwebs.Map{"pd": pd, "domain": d}
	return links.Render(c, http.StatusOK, "inactive_domain.html", gmap)
	return s.Render(c, http.StatusOK, "inactive_domain.html", gmap)
}

func (s *Service) Homepage(c echo.Context) error {


@@ 238,7 237,7 @@ func (s *Service) PricingList(c echo.Context) error {
		"pd":      pd,
		"seoData": seoData,
	}
	return links.Render(c, http.StatusOK, "pricing_list.html", gmap)
	return s.Render(c, http.StatusOK, "pricing_list.html", gmap)
}

func (s *Service) FeatureTour(c echo.Context) error {


@@ 366,7 365,7 @@ func (s *Service) FeatureTour(c echo.Context) error {
		"seoData": seoData,
		"repo":    repo,
	}
	return links.Render(c, http.StatusOK, "feature_tour.html", gmap)
	return s.Render(c, http.StatusOK, "feature_tour.html", gmap)
}

func (s *Service) DomainList(c echo.Context) error {


@@ 429,7 428,7 @@ func (s *Service) DomainList(c echo.Context) error {
		"navFlag":        "settings",
		"navSubFlag":     "domainList",
	}
	return links.Render(c, http.StatusOK, "domain_list.html", gmap)
	return s.Render(c, http.StatusOK, "domain_list.html", gmap)
}

func (s *Service) DomainCreate(c echo.Context) error {


@@ 489,7 488,7 @@ func (s *Service) DomainCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "domain_create.html", gmap)
				return s.Render(c, http.StatusOK, "domain_create.html", gmap)
			default:
				return err
			}


@@ 521,7 520,7 @@ func (s *Service) DomainCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "domain_create.html", gmap)
					return s.Render(c, http.StatusOK, "domain_create.html", gmap)
				}
			}
			return err


@@ 534,7 533,7 @@ func (s *Service) DomainCreate(c echo.Context) error {
	if links.BillingEnabled(gctx.Server.Config) && curOrg.IsRestricted([]int{models.BillingStatusFree}) {
		return links.RenderRestrictedTemplate(c)
	}
	return links.Render(c, http.StatusOK, "domain_create.html", gmap)
	return s.Render(c, http.StatusOK, "domain_create.html", gmap)
}

func (s *Service) DomainDelete(c echo.Context) error {


@@ 627,7 626,7 @@ func (s *Service) DomainDelete(c echo.Context) error {
		"url":  c.Echo().Reverse(s.RouteName("domain_delete_post"), slug, id),
		"back": c.Echo().Reverse(s.RouteName("domain_list"), slug),
	}
	return links.Render(c, http.StatusOK, "element_delete.html", gmap)
	return s.Render(c, http.StatusOK, "element_delete.html", gmap)
}

func (s *Service) OrgList(c echo.Context) error {


@@ 718,7 717,7 @@ func (s *Service) OrgList(c echo.Context) error {
	if query != "" {
		gmap["search"] = query
	}
	return links.Render(c, http.StatusOK, "org_list.html", gmap)
	return s.Render(c, http.StatusOK, "org_list.html", gmap)
}

// OrgCreate ...


@@ 754,7 753,7 @@ func (s *Service) OrgCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "org_create.html", gmap)
				return s.Render(c, http.StatusOK, "org_create.html", gmap)
			default:
				return err
			}


@@ 797,7 796,7 @@ func (s *Service) OrgCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "org_create.html", gmap)
					return s.Render(c, http.StatusOK, "org_create.html", gmap)
				}
			}
			return err


@@ 822,7 821,7 @@ func (s *Service) OrgCreate(c echo.Context) error {
		return err
	}
	gmap["restricted"] = len(orgs) > 1
	return links.Render(c, http.StatusOK, "org_create.html", gmap)
	return s.Render(c, http.StatusOK, "org_create.html", gmap)
}

// OrgSwitch ...


@@ 909,7 908,7 @@ func (s *Service) OrgUpdate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "org_edit.html", gmap)
				return s.Render(c, http.StatusOK, "org_edit.html", gmap)
			default:
				return err
			}


@@ 965,7 964,7 @@ func (s *Service) OrgUpdate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "org_edit.html", gmap)
					return s.Render(c, http.StatusOK, "org_edit.html", gmap)
				}
			}
			return err


@@ 980,7 979,7 @@ func (s *Service) OrgUpdate(c echo.Context) error {
	form.DefaultPerm = org.Settings.DefaultPerm

	gmap["form"] = form
	return links.Render(c, http.StatusOK, "org_edit.html", gmap)
	return s.Render(c, http.StatusOK, "org_edit.html", gmap)
}

// OrgMembersAdd ...


@@ 1039,7 1038,7 @@ func (s *Service) OrgMembersAdd(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "member_add.html", gmap)
				return s.Render(c, http.StatusOK, "member_add.html", gmap)
			default:
				return err
			}


@@ 1071,7 1070,7 @@ func (s *Service) OrgMembersAdd(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "member_add.html", gmap)
					return s.Render(c, http.StatusOK, "member_add.html", gmap)
				}
			}
			return err


@@ 1094,7 1093,7 @@ func (s *Service) OrgMembersAdd(c echo.Context) error {

	gmap["form"] = form
	gmap["isRestricted"] = isRestricted
	return links.Render(c, http.StatusOK, "member_add.html", gmap)
	return s.Render(c, http.StatusOK, "member_add.html", gmap)
}

// OrgMembersDelete ...


@@ 1195,7 1194,7 @@ func (s *Service) OrgMembersDelete(c echo.Context) error {
		"url":  c.Echo().Reverse(s.RouteName("org_member_delete"), slug, id),
		"back": c.Echo().Reverse(s.RouteName("org_member_list"), slug),
	}
	return links.Render(c, http.StatusOK, "element_delete.html", gmap)
	return s.Render(c, http.StatusOK, "element_delete.html", gmap)
}

// OrgMemberConfirmation ...


@@ 1330,7 1329,7 @@ func (s *Service) OrgMembersList(c echo.Context) error {
		gmap["members"] = result.OrgMembers
	}

	return links.Render(c, http.StatusOK, "member_list.html", gmap)
	return s.Render(c, http.StatusOK, "member_list.html", gmap)
}

// OrgLinksCreate ...


@@ 1399,7 1398,7 @@ func (s *Service) OrgLinksCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "link_create.html", gmap)
				return s.Render(c, http.StatusOK, "link_create.html", gmap)
			default:
				return err
			}


@@ 1443,7 1442,7 @@ func (s *Service) OrgLinksCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "link_create.html", gmap)
					return s.Render(c, http.StatusOK, "link_create.html", gmap)
				}
			}
			return err


@@ 1549,7 1548,7 @@ func (s *Service) OrgLinksCreate(c echo.Context) error {
		}
	}
	gmap["form"] = form
	return links.Render(c, http.StatusOK, "link_create.html", gmap)
	return s.Render(c, http.StatusOK, "link_create.html", gmap)
}

// PopularLinkList ...


@@ 1662,7 1661,7 @@ func (s *Service) PopularLinkList(c echo.Context) error {
		gmap["search"] = search
	}
	gmap["title"] = lt.Translate("Popular Links")
	return links.Render(c, http.StatusOK, "link_list.html", gmap)
	return s.Render(c, http.StatusOK, "link_list.html", gmap)
}

func (s *Service) UserFeedFollowing(c echo.Context) error {


@@ 1698,7 1697,7 @@ func (s *Service) UserFeedFollowing(c echo.Context) error {
		"user":    user,
		"navFlag": "feed",
	}
	return links.Render(c, http.StatusOK, "feed_following.html", gmap)
	return s.Render(c, http.StatusOK, "feed_following.html", gmap)
}

func (s *Service) UserFeed(c echo.Context) error {


@@ 1856,7 1855,7 @@ func (s *Service) UserFeed(c echo.Context) error {
	if search != "" {
		gmap["search"] = search
	}
	return links.Render(c, http.StatusOK, "feed.html", gmap)
	return s.Render(c, http.StatusOK, "feed.html", gmap)
}

// OrgLinksList ...


@@ 2223,7 2222,7 @@ func (s *Service) OrgLinksList(c echo.Context) error {
	} else {
		gmap["title"] = lt.Translate("%s Links", org.Name)
	}
	return links.Render(c, http.StatusOK, "link_list.html", gmap)
	return s.Render(c, http.StatusOK, "link_list.html", gmap)
}

func (s *Service) OrgLinkRedirect(c echo.Context) error {


@@ 2316,7 2315,7 @@ func (s *Service) OrgLinkDetail(c echo.Context) error {
		"navFlag":   "bookmarks",
		"seoData":   seoData,
	}
	return links.Render(c, http.StatusOK, "link_detail.html", gmap)
	return s.Render(c, http.StatusOK, "link_detail.html", gmap)
}

// OrgLinkDelete ...


@@ 2404,7 2403,7 @@ func (s *Service) OrgLinkDelete(c echo.Context) error {
		"url":  c.Echo().Reverse(s.RouteName("link_delete"), link.ID),
		"back": c.Echo().Reverse(s.RouteName("home_link_list")),
	}
	return links.Render(c, http.StatusOK, "element_delete.html", gmap)
	return s.Render(c, http.StatusOK, "element_delete.html", gmap)
}

// OrgLinkUpdate ...


@@ 2476,7 2475,7 @@ func (s *Service) OrgLinkUpdate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "link_create.html", gmap)
				return s.Render(c, http.StatusOK, "link_create.html", gmap)
			default:
				return err
			}


@@ 2519,7 2518,7 @@ func (s *Service) OrgLinkUpdate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "link_create.html", gmap)
					return s.Render(c, http.StatusOK, "link_create.html", gmap)
				}
			}
			return err


@@ 2549,7 2548,7 @@ func (s *Service) OrgLinkUpdate(c echo.Context) error {
		Tags:        orgLink.TagsToString(),
	}
	gmap["form"] = form
	return links.Render(c, http.StatusOK, "link_create.html", gmap)
	return s.Render(c, http.StatusOK, "link_create.html", gmap)
}

func (s *Service) QRRedirect(c echo.Context) error {


@@ 2608,7 2607,7 @@ func (s *Service) QRRedirect(c echo.Context) error {
			"url":     QRCode.URL,
			"hideNav": true,
		}
		return links.Render(c, http.StatusOK, "restriction_redirect.html", gmap)
		return s.Render(c, http.StatusOK, "restriction_redirect.html", gmap)
	}

	srv := gctx.Server


@@ 2700,7 2699,7 @@ func (s *Service) QRManageDetail(c echo.Context) error {
		"pd":      pd,
		"backURL": backURL,
	}
	return links.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
	return s.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
}

func (s *Service) QRManageDelete(c echo.Context) error {


@@ 2798,7 2797,7 @@ func (s *Service) QRManageDelete(c echo.Context) error {
		"pd":  pd,
		"url": url,
	}
	return links.Render(c, http.StatusOK, "element_delete.html", gmap)
	return s.Render(c, http.StatusOK, "element_delete.html", gmap)
}

func (s *Service) ExportData(c echo.Context) error {


@@ 2847,7 2846,7 @@ func (s *Service) ExportData(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "export_data.html", gmap)
				return s.Render(c, http.StatusOK, "export_data.html", gmap)
			default:
				return err
			}


@@ 2855,7 2854,7 @@ func (s *Service) ExportData(c echo.Context) error {
		return Export(c, org, form.Format)
	}

	return links.Render(c, http.StatusOK, "export_data.html", gmap)
	return s.Render(c, http.StatusOK, "export_data.html", gmap)

}



@@ 2941,7 2940,7 @@ func (s *Service) Integrations(c echo.Context) error {
		"slConn":       slConn,
		"mmConn":       mmConn,
	}
	return links.Render(c, http.StatusOK, "org_integration.html", gmap)
	return s.Render(c, http.StatusOK, "org_integration.html", gmap)
}

func (s *Service) ImportData(c echo.Context) error {


@@ 3006,7 3005,7 @@ func (s *Service) ImportData(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "import_data.html", gmap)
				return s.Render(c, http.StatusOK, "import_data.html", gmap)
			default:
				return err
			}


@@ 3030,7 3029,7 @@ func (s *Service) ImportData(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("core:org_link_list", org.Slug))

	}
	return links.Render(c, http.StatusOK, "import_data.html", gmap)
	return s.Render(c, http.StatusOK, "import_data.html", gmap)
}

func (s *Service) OrgLinkStarToggle(c echo.Context) error {


@@ 3296,7 3295,7 @@ func (s *Service) NoteUpdate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "note_create.html", gmap)
				return s.Render(c, http.StatusOK, "note_create.html", gmap)
			default:
				return err
			}


@@ 3335,7 3334,7 @@ func (s *Service) NoteUpdate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "note_create.html", gmap)
					return s.Render(c, http.StatusOK, "note_create.html", gmap)
				}
			}
			return err


@@ 3362,7 3361,7 @@ func (s *Service) NoteUpdate(c echo.Context) error {
		Tags:        orgLink.TagsToString(),
	}
	gmap["form"] = form
	return links.Render(c, http.StatusOK, "note_create.html", gmap)
	return s.Render(c, http.StatusOK, "note_create.html", gmap)
}

func (s *Service) NoteDetail(c echo.Context) error {


@@ 3429,7 3428,7 @@ func (s *Service) NoteDetail(c echo.Context) error {
		"navFlag":   "notes",
		"seoData":   seoData,
	}
	return links.Render(c, http.StatusOK, "link_detail.html", gmap)
	return s.Render(c, http.StatusOK, "link_detail.html", gmap)
}

func (s *Service) NoteCreate(c echo.Context) error {


@@ 3492,7 3491,7 @@ func (s *Service) NoteCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "note_create.html", gmap)
				return s.Render(c, http.StatusOK, "note_create.html", gmap)
			default:
				return err
			}


@@ 3530,7 3529,7 @@ func (s *Service) NoteCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "note_create.html", gmap)
					return s.Render(c, http.StatusOK, "note_create.html", gmap)
				}
			}
			return err


@@ 3585,7 3584,7 @@ func (s *Service) NoteCreate(c echo.Context) error {
	}

	gmap["form"] = form
	return links.Render(c, http.StatusOK, "note_create.html", gmap)
	return s.Render(c, http.StatusOK, "note_create.html", gmap)
}

// SwiftLang ...


@@ 3605,14 3604,10 @@ func SwiftLang(c echo.Context) error {
	return c.Redirect(http.StatusMovedPermanently, refer)
}

// RouteName ...
func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "core", eg: eg}
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "core", render)
	service := &Service{BaseService: baseService}
	service.RegisterRoutes()
	return service
}

M core/routes_test.go => core/routes_test.go +2 -1
@@ 3,6 3,7 @@ package core_test
import (
	"database/sql"
	"fmt"
	"links"
	"links/cmd"
	"links/cmd/test"
	"links/core"


@@ 26,7 27,7 @@ func TestHandlers(t *testing.T) {
	c := require.New(t)
	srv, e := test.NewWebTestServer(t)
	cmd.RunMigrations(t, srv.DB)
	coreService := core.NewService(e.Group(""))
	coreService := core.NewService(e.Group(""), links.Render)
	loggedInUser := test.NewTestUser(1, false, false, true, true)
	defer srv.Shutdown()
	go srv.Run()

M list/routes.go => list/routes.go +50 -60
@@ 27,29 27,28 @@ import (
)

type Service struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (s *Service) RegisterRoutes() {
	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/add", s.ListingCreate).Name = s.RouteName("listing_create")
	s.eg.POST("/add", s.ListingCreate).Name = s.RouteName("listing_create_post")
	s.eg.GET("/:id/edit", s.ListingUpdate).Name = s.RouteName("listing_update")
	s.eg.POST("/:id/edit", s.ListingUpdate).Name = s.RouteName("listing_update_post")
	s.eg.GET("/:id/delete", s.ListingDelete).Name = s.RouteName("listing_delete")
	s.eg.POST("/:id/delete", s.ListingDelete).Name = s.RouteName("listing_delete")
	s.eg.GET("/:id/links", s.ListingLinksManage).Name = s.RouteName("listing_links")
	s.eg.GET("/:id/links/add", s.ListingLinksCreate).Name = s.RouteName("listing_link_create")
	s.eg.POST("/:id/links/add", s.ListingLinksCreate).Name = s.RouteName("listing_link_create_post")
	s.eg.GET("/:id/links/:lid/edit", s.ListingLinksUpdate).Name = s.RouteName("listing_link_update")
	s.eg.POST("/:id/links/:lid/edit", s.ListingLinksUpdate).Name = s.RouteName("listing_link_update_post")
	s.eg.GET("/:id/links/:lid/delete", s.ListingLinksDelete).Name = s.RouteName("listing_link_delete")
	s.eg.POST("/:id/links/:lid/delete", s.ListingLinksDelete).Name = s.RouteName("listing_link_delete_post")
	s.eg.GET("/:id/qr", s.ListingQRCodeList).Name = s.RouteName("listing_qrcode_list")
	s.eg.GET("/:id/qr/add", s.ListingQRCodeCreate).Name = s.RouteName("listing_qrcode_create")
	s.eg.POST("/:id/qr/add", s.ListingQRCodeCreate).Name = s.RouteName("listing_qrcode_create_post")
	s.eg.GET("", s.ListingList).Name = s.RouteName("listing_list")
	s.Group.Use(auth.AuthRequired())
	s.Group.GET("/add", s.ListingCreate).Name = s.RouteName("listing_create")
	s.Group.POST("/add", s.ListingCreate).Name = s.RouteName("listing_create_post")
	s.Group.GET("/:id/edit", s.ListingUpdate).Name = s.RouteName("listing_update")
	s.Group.POST("/:id/edit", s.ListingUpdate).Name = s.RouteName("listing_update_post")
	s.Group.GET("/:id/delete", s.ListingDelete).Name = s.RouteName("listing_delete")
	s.Group.POST("/:id/delete", s.ListingDelete).Name = s.RouteName("listing_delete")
	s.Group.GET("/:id/links", s.ListingLinksManage).Name = s.RouteName("listing_links")
	s.Group.GET("/:id/links/add", s.ListingLinksCreate).Name = s.RouteName("listing_link_create")
	s.Group.POST("/:id/links/add", s.ListingLinksCreate).Name = s.RouteName("listing_link_create_post")
	s.Group.GET("/:id/links/:lid/edit", s.ListingLinksUpdate).Name = s.RouteName("listing_link_update")
	s.Group.POST("/:id/links/:lid/edit", s.ListingLinksUpdate).Name = s.RouteName("listing_link_update_post")
	s.Group.GET("/:id/links/:lid/delete", s.ListingLinksDelete).Name = s.RouteName("listing_link_delete")
	s.Group.POST("/:id/links/:lid/delete", s.ListingLinksDelete).Name = s.RouteName("listing_link_delete_post")
	s.Group.GET("/:id/qr", s.ListingQRCodeList).Name = s.RouteName("listing_qrcode_list")
	s.Group.GET("/:id/qr/add", s.ListingQRCodeCreate).Name = s.RouteName("listing_qrcode_create")
	s.Group.POST("/:id/qr/add", s.ListingQRCodeCreate).Name = s.RouteName("listing_qrcode_create_post")
	s.Group.GET("", s.ListingList).Name = s.RouteName("listing_list")
}

func (s *Service) ListingLinksUpdate(c echo.Context) error {


@@ 152,7 151,7 @@ func (s *Service) ListingLinksUpdate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "listing_link_create.html", gmap)
				return s.Render(c, http.StatusOK, "listing_link_create.html", gmap)
			default:
				return err
			}


@@ 183,7 182,7 @@ func (s *Service) ListingLinksUpdate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "listing_link_create.html", gmap)
					return s.Render(c, http.StatusOK, "listing_link_create.html", gmap)
				}
			}
			return err


@@ 194,7 193,7 @@ func (s *Service) ListingLinksUpdate(c echo.Context) error {
			c.Echo().Reverse(s.RouteName("listing_links"), org.Slug, listing.ID))

	}
	return links.Render(c, http.StatusOK, "listing_link_create.html", gmap)
	return s.Render(c, http.StatusOK, "listing_link_create.html", gmap)
}

func (s *Service) ListingLinksDelete(c echo.Context) error {


@@ 311,7 310,7 @@ func (s *Service) ListingLinksDelete(c echo.Context) error {
		"url":  c.Echo().Reverse(s.RouteName("listing_link_delete"), org.Slug, listing.ID, listingLink.ID),
		"back": c.Echo().Reverse(s.RouteName("listing_links"), org.Slug, listing.ID),
	}
	return links.Render(c, http.StatusOK, "element_delete.html", gmap)
	return s.Render(c, http.StatusOK, "element_delete.html", gmap)

}



@@ 387,7 386,7 @@ func (s *Service) ListingLinksCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "listing_link_create.html", gmap)
				return s.Render(c, http.StatusOK, "listing_link_create.html", gmap)
			default:
				return err
			}


@@ 418,7 417,7 @@ func (s *Service) ListingLinksCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "listing_link_create.html", gmap)
					return s.Render(c, http.StatusOK, "listing_link_create.html", gmap)
				}
			}
			return err


@@ 429,7 428,7 @@ func (s *Service) ListingLinksCreate(c echo.Context) error {
			c.Echo().Reverse(s.RouteName("listing_links"), org.Slug, listing.ID))

	}
	return links.Render(c, http.StatusOK, "listing_link_create.html", gmap)
	return s.Render(c, http.StatusOK, "listing_link_create.html", gmap)
}

func (s *Service) ListingLinksManage(c echo.Context) error {


@@ 569,7 568,7 @@ func (s *Service) ListingLinksManage(c echo.Context) error {
	if result.Listing.PageInfo.HasNextPage {
		gmap["nextURL"] = links.GetPaginationParams("next", "", "", result.Listing.PageInfo.Cursor)
	}
	return links.Render(c, http.StatusOK, "listing_link_list.html", gmap)
	return s.Render(c, http.StatusOK, "listing_link_list.html", gmap)
}

func (s *Service) ListingUpdate(c echo.Context) error {


@@ 679,7 678,7 @@ func (s *Service) ListingUpdate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "listing_create.html", gmap)
				return s.Render(c, http.StatusOK, "listing_create.html", gmap)
			default:
				return err
			}


@@ 746,7 745,7 @@ func (s *Service) ListingUpdate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "listing_create.html", gmap)
					return s.Render(c, http.StatusOK, "listing_create.html", gmap)
				}
			}
			return err


@@ 776,7 775,7 @@ func (s *Service) ListingUpdate(c echo.Context) error {
		form.Tags = strings.Join(tagNameList, ", ")
	}
	gmap["form"] = form
	return links.Render(c, http.StatusOK, "listing_create.html", gmap)
	return s.Render(c, http.StatusOK, "listing_create.html", gmap)

}



@@ 867,7 866,7 @@ func (s *Service) ListingCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "listing_create.html", gmap)
				return s.Render(c, http.StatusOK, "listing_create.html", gmap)
			default:
				return err
			}


@@ 922,7 921,7 @@ func (s *Service) ListingCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "listing_create.html", gmap)
					return s.Render(c, http.StatusOK, "listing_create.html", gmap)
				}
			}
			return err


@@ 947,7 946,7 @@ func (s *Service) ListingCreate(c echo.Context) error {
			return links.RenderRestrictedTemplate(c)
		}
	}
	return links.Render(c, http.StatusOK, "listing_create.html", gmap)
	return s.Render(c, http.StatusOK, "listing_create.html", gmap)
}

func (s *Service) ListingList(c echo.Context) error {


@@ 1085,7 1084,7 @@ func (s *Service) ListingList(c echo.Context) error {
	if result.Listings.PageInfo.HasNextPage {
		gmap["nextURL"] = links.GetPaginationParams("next", tag, "", result.Listings.PageInfo.Cursor)
	}
	return links.Render(c, http.StatusOK, "listing_list.html", gmap)
	return s.Render(c, http.StatusOK, "listing_list.html", gmap)
}

// ListingDelete ...


@@ 1186,7 1185,7 @@ func (s *Service) ListingDelete(c echo.Context) error {
		"url":  c.Echo().Reverse(s.RouteName("listing_delete"), org.Slug, listing.ID),
		"back": c.Echo().Reverse(s.RouteName("listing_list"), org.Slug),
	}
	return links.Render(c, http.StatusOK, "element_delete.html", gmap)
	return s.Render(c, http.StatusOK, "element_delete.html", gmap)
}

func (s *Service) ListingQRCodeCreate(c echo.Context) error {


@@ 1257,7 1256,7 @@ func (s *Service) ListingQRCodeCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
				return s.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
			default:
				return err
			}


@@ 1302,7 1301,7 @@ func (s *Service) ListingQRCodeCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
					return s.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
				}
			}
			return err


@@ 1311,7 1310,7 @@ func (s *Service) ListingQRCodeCreate(c echo.Context) error {
		gmap["qrCode"] = result.QRCode
		gmap["backURL"] = c.Echo().Reverse(s.RouteName("listing_qrcode_list"), org.Slug, listing.ID)
	}
	return links.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
	return s.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
}

func (s *Service) ListingQRCodeList(c echo.Context) error {


@@ 1412,30 1411,25 @@ func (s *Service) ListingQRCodeList(c echo.Context) error {
		"back":          c.Echo().Reverse(s.RouteName("listing_list"), org.Slug),
		"navFlag":       "listing",
	}
	return links.Render(c, http.StatusOK, "qrcodes_list.html", gmap)
}

// RouteName ...
func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
	return s.Render(c, http.StatusOK, "qrcodes_list.html", gmap)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "list", eg: eg}
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "list", render)
	service := &Service{BaseService: baseService}
	service.RegisterRoutes()
	return service
}

type DetailService struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (r *DetailService) RegisterRoutes() {
	r.eg.GET("/:slug", r.ListDetail).Name = r.RouteName("list_slug")
	r.eg.GET("/:slug/:id", r.ListLink).Name = r.RouteName("redirect")
	r.eg.GET("/", r.ListDetail).Name = r.RouteName("list_default")
	r.Group.GET("/:slug", r.ListDetail).Name = r.RouteName("list_slug")
	r.Group.GET("/:slug/:id", r.ListLink).Name = r.RouteName("redirect")
	r.Group.GET("/", r.ListDetail).Name = r.RouteName("list_default")
}

func (r *DetailService) ListLink(c echo.Context) error {


@@ 1599,17 1593,13 @@ func (r *DetailService) ListDetail(c echo.Context) error {
		result.Listing.Result.ID,
		analytics.ListAnalyticsFilter,
	))
	return links.Render(c, http.StatusOK, "listing_detail.html", gmap)
}

// RouteName ...
func (r *DetailService) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", r.name, value)
	return r.Render(c, http.StatusOK, "listing_detail.html", gmap)
}

// NewService return service
func NewDetailService(eg *echo.Group) *DetailService {
	dService := &DetailService{name: "list_detail", eg: eg}
func NewDetailService(eg *echo.Group, render validate.TemplateRenderFunc) *DetailService {
	baseService := server.NewService(eg, "list_detail", render)
	dService := &DetailService{BaseService: baseService}
	dService.RegisterRoutes()
	return dService
}

M list/routes_test.go => list/routes_test.go +3 -2
@@ 2,6 2,7 @@ package list_test

import (
	"bytes"
	"links"
	"links/cmd"
	"links/cmd/test"
	"links/list"


@@ 25,7 26,7 @@ func TestHandlers(t *testing.T) {
	c := require.New(t)
	srv, e := test.NewWebTestServer(t)
	cmd.RunMigrations(t, srv.DB)
	listService := list.NewService(e.Group("/list"))
	listService := list.NewService(e.Group("/list"), links.Render)
	user := test.NewTestUser(1, false, false, true, true)
	defer srv.Shutdown()
	go srv.Run()


@@ 299,7 300,7 @@ func TestDetailHandler(t *testing.T) {
	c := require.New(t)
	srv, e := test.NewWebTestServer(t)
	cmd.RunMigrations(t, srv.DB)
	detailService := list.NewDetailService(e.Group("/"))
	detailService := list.NewDetailService(e.Group("/"), links.Render)
	dbCtx := test.NewDBContext(srv.DB, "America/Managua")
	user := test.NewTestUser(1, false, false, true, true)
	defer srv.Shutdown()

M mattermost/routes.go => mattermost/routes.go +25 -29
@@ 20,29 20,29 @@ import (
	"netlandish.com/x/gobwebs/database"
	"netlandish.com/x/gobwebs/messages"
	"netlandish.com/x/gobwebs/server"
	"netlandish.com/x/gobwebs/validate"
)

type Service struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (s *Service) RegisterRoutes() {
	s.eg.GET("/:slug/manifest.json", s.ServeManifest).Name = s.RouteName("manifest")
	s.eg.POST("/:slug/bindings", s.ServeBinding).Name = s.RouteName("bindings")
	s.eg.POST("/:slug/command/connect", s.ConnectCommand).Name = s.RouteName("command_connect")
	s.eg.POST("/command/add", s.AddCommand).Name = s.RouteName("command_add")
	s.eg.POST("/command/search", s.SearchCommand).Name = s.RouteName("command_search")
	s.eg.POST("/command/short", s.ShortCommand).Name = s.RouteName("command_short")
	s.eg.POST("/ping", s.Ping).Name = s.RouteName("ping")

	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/connect/:slug/:id", s.Connect).Name = s.RouteName("connect_org")
	s.eg.POST("/connect/:slug/:id", s.Connect).Name = s.RouteName("connect_org_post")
	s.eg.GET("/connect/:id/user/:uid", s.ConnectUser).Name = s.RouteName("connect_user")
	s.eg.POST("/connect/:id/user/:uid", s.ConnectUser).Name = s.RouteName("connect_user_post")
	s.eg.GET("/disconnect/:id", s.Disconnect).Name = s.RouteName("disconnect")
	s.eg.POST("/disconnect/:id", s.Disconnect).Name = s.RouteName("disconnect_post")
	s.Group.GET("/:slug/manifest.json", s.ServeManifest).Name = s.RouteName("manifest")
	s.Group.POST("/:slug/bindings", s.ServeBinding).Name = s.RouteName("bindings")
	s.Group.POST("/:slug/command/connect", s.ConnectCommand).Name = s.RouteName("command_connect")
	s.Group.POST("/command/add", s.AddCommand).Name = s.RouteName("command_add")
	s.Group.POST("/command/search", s.SearchCommand).Name = s.RouteName("command_search")
	s.Group.POST("/command/short", s.ShortCommand).Name = s.RouteName("command_short")
	s.Group.POST("/ping", s.Ping).Name = s.RouteName("ping")

	s.Group.Use(auth.AuthRequired())
	s.Group.GET("/connect/:slug/:id", s.Connect).Name = s.RouteName("connect_org")
	s.Group.POST("/connect/:slug/:id", s.Connect).Name = s.RouteName("connect_org_post")
	s.Group.GET("/connect/:id/user/:uid", s.ConnectUser).Name = s.RouteName("connect_user")
	s.Group.POST("/connect/:id/user/:uid", s.ConnectUser).Name = s.RouteName("connect_user_post")
	s.Group.GET("/disconnect/:id", s.Disconnect).Name = s.RouteName("disconnect")
	s.Group.POST("/disconnect/:id", s.Disconnect).Name = s.RouteName("disconnect_post")
}

func (s *Service) Disconnect(c echo.Context) error {


@@ 81,7 81,7 @@ func (s *Service) Disconnect(c echo.Context) error {
		"pd":   pd,
		"conn": mmConn,
	}
	return links.Render(c, http.StatusOK, "disconnect_mattermost.html", gmap)
	return s.Render(c, http.StatusOK, "disconnect_mattermost.html", gmap)

}



@@ 132,7 132,7 @@ func (s *Service) Connect(c echo.Context) error {
			"pd":        pd,
			"connected": true,
		}
		return links.Render(c, http.StatusOK, "connect_mattermost.html", gmap)
		return s.Render(c, http.StatusOK, "connect_mattermost.html", gmap)
	}

	opts = &database.FilterOptions{


@@ 148,7 148,7 @@ func (s *Service) Connect(c echo.Context) error {
	}
	if links.BillingEnabled(gctx.Server.Config) && org.IsRestricted([]int{models.BillingStatusFree}) {
		gmap["isRestricted"] = true
		return links.Render(c, http.StatusOK, "connect_mattermost.html", gmap)
		return s.Render(c, http.StatusOK, "connect_mattermost.html", gmap)
	}

	req := c.Request()


@@ 167,7 167,7 @@ func (s *Service) Connect(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("core:org_list"))
	}

	return links.Render(c, http.StatusOK, "connect_mattermost.html", gmap)
	return s.Render(c, http.StatusOK, "connect_mattermost.html", gmap)
}

func (s *Service) ConnectUser(c echo.Context) error {


@@ 227,7 227,7 @@ func (s *Service) ConnectUser(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("accounts:settings"))

	}
	return links.Render(c, http.StatusOK, "connect_mm_user.html", gmap)
	return s.Render(c, http.StatusOK, "connect_mm_user.html", gmap)
}

func (s *Service) SearchCommand(c echo.Context) error {


@@ 667,14 667,10 @@ func (s *Service) ServeManifest(c echo.Context) error {
	return c.JSON(http.StatusOK, appManifest)
}

// RouteName ...
func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "mattermost", eg: eg}
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "mattermost", render)
	service := &Service{BaseService: baseService}
	service.RegisterRoutes()
	return service
}

M short/routes.go => short/routes.go +34 -44
@@ 27,22 27,21 @@ import (
)

type Service struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (s *Service) RegisterRoutes() {
	s.eg.Use(auth.AuthRequired())
	s.eg.GET("", s.LinkShortList).Name = s.RouteName("link_short_list")
	s.eg.GET("/add", s.LinkShortCreate).Name = s.RouteName("link_short_create")
	s.eg.POST("/add", s.LinkShortCreate).Name = s.RouteName("link_short_create_post")
	s.eg.GET("/:id/edit", s.LinkShortUpdate).Name = s.RouteName("link_short_edit")
	s.eg.POST("/:id/edit", s.LinkShortUpdate).Name = s.RouteName("link_short_edit_post")
	s.eg.GET("/:id/delete", s.LinkShortDelete).Name = s.RouteName("link_short_delete")
	s.eg.POST("/:id/delete", s.LinkShortDelete).Name = s.RouteName("link_short_delete")
	s.eg.GET("/:id/qr", s.LinkShortQRCodeList).Name = s.RouteName("link_short_qrcode_list")
	s.eg.GET("/:id/qr/add", s.LinkShortQRCodeCreate).Name = s.RouteName("link_short_qrcode_create")
	s.eg.POST("/:id/qr/add", s.LinkShortQRCodeCreate).Name = s.RouteName("link_short_qrcode_create_post")
	s.Group.Use(auth.AuthRequired())
	s.Group.GET("", s.LinkShortList).Name = s.RouteName("link_short_list")
	s.Group.GET("/add", s.LinkShortCreate).Name = s.RouteName("link_short_create")
	s.Group.POST("/add", s.LinkShortCreate).Name = s.RouteName("link_short_create_post")
	s.Group.GET("/:id/edit", s.LinkShortUpdate).Name = s.RouteName("link_short_edit")
	s.Group.POST("/:id/edit", s.LinkShortUpdate).Name = s.RouteName("link_short_edit_post")
	s.Group.GET("/:id/delete", s.LinkShortDelete).Name = s.RouteName("link_short_delete")
	s.Group.POST("/:id/delete", s.LinkShortDelete).Name = s.RouteName("link_short_delete")
	s.Group.GET("/:id/qr", s.LinkShortQRCodeList).Name = s.RouteName("link_short_qrcode_list")
	s.Group.GET("/:id/qr/add", s.LinkShortQRCodeCreate).Name = s.RouteName("link_short_qrcode_create")
	s.Group.POST("/:id/qr/add", s.LinkShortQRCodeCreate).Name = s.RouteName("link_short_qrcode_create_post")
}

// LinkShortList ...


@@ 187,7 186,7 @@ func (s *Service) LinkShortList(c echo.Context) error {
	if result.LinkShorts.PageInfo.HasNextPage {
		gmap["nextURL"] = links.GetPaginationParams("next", tag, "", result.LinkShorts.PageInfo.Cursor)
	}
	return links.Render(c, http.StatusOK, "link_short_list.html", gmap)
	return s.Render(c, http.StatusOK, "link_short_list.html", gmap)
}

func (s *Service) LinkShortCreate(c echo.Context) error {


@@ 270,7 269,7 @@ func (s *Service) LinkShortCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "link_short_create.html", gmap)
				return s.Render(c, http.StatusOK, "link_short_create.html", gmap)
			default:
				return err
			}


@@ 308,7 307,7 @@ func (s *Service) LinkShortCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "link_short_create.html", gmap)
					return s.Render(c, http.StatusOK, "link_short_create.html", gmap)
				}
			}
			return err


@@ 319,7 318,7 @@ func (s *Service) LinkShortCreate(c echo.Context) error {
			c.Echo().Reverse(s.RouteName("link_short_list"), org.Slug))
	}

	return links.Render(c, http.StatusOK, "link_short_create.html", gmap)
	return s.Render(c, http.StatusOK, "link_short_create.html", gmap)
}

// LinkShortUpdate ...


@@ 441,7 440,7 @@ func (s *Service) LinkShortUpdate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "link_short_create.html", gmap)
				return s.Render(c, http.StatusOK, "link_short_create.html", gmap)
			default:
				return err
			}


@@ 480,7 479,7 @@ func (s *Service) LinkShortUpdate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "link_create.html", gmap)
					return s.Render(c, http.StatusOK, "link_create.html", gmap)
				}
			}
			return err


@@ 490,7 489,7 @@ func (s *Service) LinkShortUpdate(c echo.Context) error {
			c.Echo().Reverse(s.RouteName("link_short_list"), org.Slug))

	}
	return links.Render(c, http.StatusOK, "link_short_create.html", gmap)
	return s.Render(c, http.StatusOK, "link_short_create.html", gmap)
}

// LinkShortDelete ...


@@ 570,17 569,13 @@ func (s *Service) LinkShortDelete(c echo.Context) error {
		"url":  c.Echo().Reverse(s.RouteName("link_short_delete"), orgSlug, linkShort.ID),
		"back": c.Echo().Reverse(s.RouteName("link_short_list"), orgSlug),
	}
	return links.Render(c, http.StatusOK, "element_delete.html", gmap)
}

// RouteName ...
func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
	return s.Render(c, http.StatusOK, "element_delete.html", gmap)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "short", eg: eg}
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "short", render)
	service := &Service{BaseService: baseService}
	service.RegisterRoutes()
	return service
}


@@ 683,7 678,7 @@ func (s *Service) LinkShortQRCodeList(c echo.Context) error {
		"navFlag":       "short",
		"back":          c.Echo().Reverse(s.RouteName("link_short_list"), orgSlug),
	}
	return links.Render(c, http.StatusOK, "qrcodes_list.html", gmap)
	return s.Render(c, http.StatusOK, "qrcodes_list.html", gmap)
}

func (s *Service) LinkShortQRCodeCreate(c echo.Context) error {


@@ 754,7 749,7 @@ func (s *Service) LinkShortQRCodeCreate(c echo.Context) error {
			case validate.InputErrors:
				gmap["errors"] = err
				gmap["form"] = form
				return links.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
				return s.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
			default:
				return err
			}


@@ 798,7 793,7 @@ func (s *Service) LinkShortQRCodeCreate(c echo.Context) error {
				case validate.InputErrors:
					gmap["errors"] = err
					gmap["form"] = form
					return links.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
					return s.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
				}
			}
			return err


@@ 806,17 801,16 @@ func (s *Service) LinkShortQRCodeCreate(c echo.Context) error {
		messages.Success(c, lt.Translate("QR Code succesfully created"))
		gmap["qrCode"] = result.QRCode
	}
	return links.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
	return s.Render(c, http.StatusOK, "qrcode_detail.html", gmap)
}

type RedirectService struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (r *RedirectService) RegisterRoutes() {
	r.eg.GET("/:code", r.LinkShort).Name = r.RouteName("link_short")
	r.eg.GET("/", r.Index).Name = r.RouteName("link_short_index")
	r.Group.GET("/:code", r.LinkShort).Name = r.RouteName("link_short")
	r.Group.GET("/", r.Index).Name = r.RouteName("link_short_index")
}

func (r *RedirectService) Index(c echo.Context) error {


@@ 898,20 892,16 @@ func (r *RedirectService) LinkShort(c echo.Context) error {
			"url":     recURL,
			"hideNav": true,
		}
		return links.Render(c, http.StatusOK, "restriction_redirect.html", gmap)
		return r.Render(c, http.StatusOK, "restriction_redirect.html", gmap)
	}

	return c.Redirect(http.StatusMovedPermanently, recURL)
}

// RouteName ...
func (r *RedirectService) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", r.name, value)
}

// NewService return service
func NewRedirectService(eg *echo.Group) *RedirectService {
	rService := &RedirectService{name: "redirect_short", eg: eg}
func NewRedirectService(eg *echo.Group, render validate.TemplateRenderFunc) *RedirectService {
	baseService := server.NewService(eg, "redirect_short", render)
	rService := &RedirectService{BaseService: baseService}
	rService.RegisterRoutes()
	return rService
}

M short/routes_test.go => short/routes_test.go +3 -2
@@ 2,6 2,7 @@ package short_test

import (
	"bytes"
	"links"
	"links/cmd"
	"links/cmd/test"
	"links/models"


@@ 25,7 26,7 @@ func TestHandlers(t *testing.T) {
	c := require.New(t)
	srv, e := test.NewWebTestServer(t)
	cmd.RunMigrations(t, srv.DB)
	shortService := short.NewService(e.Group("/short"))
	shortService := short.NewService(e.Group("/short"), links.Render)
	loggedInUser := test.NewTestUser(1, false, false, true, true)
	defer srv.Shutdown()
	go srv.Run()


@@ 191,7 192,7 @@ func TestRedirectHandler(t *testing.T) {
	c := require.New(t)
	srv, e := test.NewWebTestServer(t)
	cmd.RunMigrations(t, srv.DB)
	redirectService := short.NewRedirectService(e.Group("/"))
	redirectService := short.NewRedirectService(e.Group("/"), links.Render)
	dbCtx := test.NewDBContext(srv.DB, "America/Managua")
	defer srv.Shutdown()
	go srv.Run()

M slack/routes.go => slack/routes.go +17 -22
@@ 38,19 38,18 @@ type SlackAuthResponse struct {

// Service is the base accounts service struct
type Service struct {
	name string
	eg   *echo.Group
	server.BaseService
}

func (s *Service) RegisterRoutes() {
	s.eg.POST("/command", s.SlashCommand).Name = s.RouteName("slack_slash_command")
	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/connect", s.ConnectSlack).Name = s.RouteName("slack_connect")
	s.eg.POST("/connect", s.ConnectSlack).Name = s.RouteName("slack_connect_post")
	s.eg.GET("/connect/:id/user/:uid", s.ConnectUser).Name = s.RouteName("slack_user_connect")
	s.eg.POST("/connect/:id/user/:uid", s.ConnectUser).Name = s.RouteName("slack_user_connect_post")
	s.eg.GET("/disconnect/:id", s.DisconnectSlack).Name = s.RouteName("slack_disconnect")
	s.eg.POST("/disconnect/:id", s.DisconnectSlack).Name = s.RouteName("slack_disconnect_post")
	s.Group.POST("/command", s.SlashCommand).Name = s.RouteName("slack_slash_command")
	s.Group.Use(auth.AuthRequired())
	s.Group.GET("/connect", s.ConnectSlack).Name = s.RouteName("slack_connect")
	s.Group.POST("/connect", s.ConnectSlack).Name = s.RouteName("slack_connect_post")
	s.Group.GET("/connect/:id/user/:uid", s.ConnectUser).Name = s.RouteName("slack_user_connect")
	s.Group.POST("/connect/:id/user/:uid", s.ConnectUser).Name = s.RouteName("slack_user_connect_post")
	s.Group.GET("/disconnect/:id", s.DisconnectSlack).Name = s.RouteName("slack_disconnect")
	s.Group.POST("/disconnect/:id", s.DisconnectSlack).Name = s.RouteName("slack_disconnect_post")
}

func (s *Service) DisconnectSlack(c echo.Context) error {


@@ 91,7 90,7 @@ func (s *Service) DisconnectSlack(c echo.Context) error {
		"pd":   pd,
		"conn": slackConn,
	}
	return links.Render(c, http.StatusOK, "disconnect_slack.html", gmap)
	return s.Render(c, http.StatusOK, "disconnect_slack.html", gmap)
}

func (s *Service) ConnectUser(c echo.Context) error {


@@ 146,7 145,7 @@ func (s *Service) ConnectUser(c echo.Context) error {
		if !org.SlackConnID.Valid {
			// Should not be reached, but just in case
			messages.Error(c, lt.Translate("Something went wrong. The user could not be linked."))
			return links.Render(c, http.StatusOK, "connect_user.html", gmap)
			return s.Render(c, http.StatusOK, "connect_user.html", gmap)

		}
		slackUser := &models.SlackUser{


@@ 163,7 162,7 @@ func (s *Service) ConnectUser(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, c.Echo().Reverse("accounts:settings"))
	}

	return links.Render(c, http.StatusOK, "connect_user.html", gmap)
	return s.Render(c, http.StatusOK, "connect_user.html", gmap)
}

func (s *Service) SlashCommand(c echo.Context) error {


@@ 371,7 370,7 @@ func (s *Service) ConnectSlack(c echo.Context) error {
		}
		gmap["form"] = form
		gmap["workspace"] = slackResp.Team.Name
		return links.Render(c, http.StatusOK, "connect_slack.html", gmap)
		return s.Render(c, http.StatusOK, "connect_slack.html", gmap)
	}

	req := c.Request()


@@ 383,7 382,7 @@ func (s *Service) ConnectSlack(c echo.Context) error {
				gmap["errors"] = err
				gmap["form"] = form
				gmap["workspace"] = form.TeamName
				return links.Render(c, http.StatusOK, "connect_slack.html", gmap)
				return s.Render(c, http.StatusOK, "connect_slack.html", gmap)
			default:
				return err
			}


@@ 406,14 405,10 @@ func (s *Service) ConnectSlack(c echo.Context) error {
	return echo.NotFoundHandler(c)
}

// RouteName ...
func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
}

// NewService return service
func NewService(eg *echo.Group) *Service {
	service := &Service{name: "slack", eg: eg}
func NewService(eg *echo.Group, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, "slack", render)
	service := &Service{BaseService: baseService}
	service.RegisterRoutes()
	return service
}