M analytics/routes.go => analytics/routes.go +0 -32
@@ 27,7 27,6 @@ type Service struct {
func (s *Service) RegisterRoutes() {
s.eg.Use(auth.AuthRequired())
- s.eg.GET("/:type", s.List).Name = s.RouteName("list")
s.eg.GET("/:type/:id", s.Detail).Name = s.RouteName("detail")
}
@@ 236,37 235,6 @@ func (s *Service) Detail(c echo.Context) error {
return gctx.Render(http.StatusOK, "analytics_detail.html", gmap)
}
-func (s *Service) List(c echo.Context) error {
- aType := c.Param("type")
- var list interface{}
- var err error
- switch aType {
- case "links":
- list, err = models.GetOrgLinksAnalytics(c.Request().Context(), nil)
- if err != nil {
- return err
- }
- case "shorts":
- list, err = models.GetLinkShortsAnalytics(c.Request().Context(), nil)
- if err != nil {
- return err
- }
- case "lists":
- list, err = models.GetListsAnalytics(c.Request().Context(), nil)
- if err != nil {
- return err
- }
-
- default:
- return echo.NotFoundHandler(c)
- }
- gctx := c.(*server.Context)
- lt := localizer.GetSessionLocalizer(c)
- pd := localizer.NewPageData(lt.Translate(""))
- gmap := gobwebs.Map{"pd": pd, "list": list, "aType": aType}
- return gctx.Render(http.StatusOK, "analytics_list.html", gmap)
-}
-
func (s *Service) RouteName(value string) string {
return fmt.Sprintf("%s:%s", s.name, value)
}
M analytics/routes_test.go => analytics/routes_test.go +45 -12
@@ 7,6 7,7 @@ import (
"links/models"
"net/http"
"net/http/httptest"
+ "net/url"
"sort"
"testing"
"time"
@@ 30,13 31,13 @@ func TestHandler(t *testing.T) {
go srv.Run()
dbCtx := cmd.NewDBContext(srv.DB, "America/Managua")
- httpmock.Activate()
- defer httpmock.DeactivateAndReset()
- jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/analytics.json"))
- c.NoError(err)
- httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)
+ t.Run("analytics short details", func(t *testing.T) {
+ httpmock.Activate()
+ defer httpmock.DeactivateAndReset()
+ jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/analytics_shorts.json"))
+ c.NoError(err)
- t.Run("checkout success", func(t *testing.T) {
+ httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)
short := &models.LinkShort{
Title: "Testing short",
URL: "http://testing.com",
@@ 45,12 46,7 @@ func TestHandler(t *testing.T) {
UserID: int(user.ID),
DomainID: 1,
}
- err := short.Store(dbCtx)
- c.NoError(err)
- req := httptest.NewRequest(http.MethodGet, "/", nil)
-
- // Add fake analytics entries
- err = analytics.AddAnalytics(dbCtx, req, short.ID, analytics.LinkShortAnalyticsFilter, "")
+ err = short.Store(dbCtx)
c.NoError(err)
request := httptest.NewRequest(http.MethodGet, "/shorts/1", nil)
request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
@@ 67,6 63,43 @@ func TestHandler(t *testing.T) {
c.NoError(err)
c.Equal(http.StatusOK, recorder.Code)
})
+
+ t.Run("analytics list detail", func(t *testing.T) {
+ httpmock.Activate()
+ defer httpmock.DeactivateAndReset()
+ jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/analytics_lists.json"))
+ c.NoError(err)
+
+ httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)
+ list := &models.Listing{
+ Title: "Testing lists",
+ Slug: "testing-list",
+ OrgID: 1,
+ UserID: int(user.ID),
+ DomainID: 1,
+ }
+ err = list.Store(dbCtx)
+ c.NoError(err)
+
+ q := make(url.Values)
+ q.Add("date-start", "2023-12-12")
+ q.Add("date-end", "2024-02-12")
+ request := httptest.NewRequest(http.MethodGet, "/lists/1?"+q.Encode(), nil)
+ request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
+ recorder := httptest.NewRecorder()
+ ctx := &server.Context{
+ Server: srv,
+ Context: e.NewContext(request, recorder),
+ User: user,
+ }
+ ctx.SetPath("/:slug/analytics/:type/:id")
+ ctx.SetParamNames("slug", "type", "id")
+ ctx.SetParamValues("personal-org", "lists", fmt.Sprint(list.ID))
+ err = cmd.MakeRequest(srv, analyticsService.Detail, ctx)
+ c.NoError(err)
+ c.Equal(http.StatusOK, recorder.Code)
+
+ })
}
func TestAPI(t *testing.T) {
R analytics/samples/analytics.json => analytics/samples/analytics_lists.json +0 -0
A analytics/samples/analytics_shorts.json => analytics/samples/analytics_shorts.json +61 -0
@@ 0,0 1,61 @@
+{
+ "data": {
+ "analytics": {
+ "title": "New Short",
+ "clicksOverTime": [
+ {
+ "key": "2024-01-21",
+ "val": 10
+ },
+ {
+ "key": "2024-01-28",
+ "val": 10
+ },
+ {
+ "key": "2024-02-04",
+ "val": 10
+ },
+ {
+ "key": "2024-02-11",
+ "val": 10
+ },
+ {
+ "key": "2024-01-14",
+ "val": 10
+ }
+ ],
+ "shortLink": [
+ {
+ "id": 12,
+ "title": "Title",
+ "clicks": 12
+ }
+ ],
+ "clicksCountry": [
+ {
+ "key": "Country",
+ "val": 1000
+ }
+ ],
+ "clicksCity": [
+ {
+ "key": "City",
+ "val": 1000
+ }
+
+ ],
+ "clicksDevice": [
+ {
+ "key": "Device",
+ "val": 1000
+ }
+ ],
+ "clicksReferrer": [
+ {
+ "key": "Referrer",
+ "val": 1000
+ }
+ ]
+ }
+ }
+}
M core/routes_test.go => core/routes_test.go +101 -0
@@ 2,6 2,7 @@ package core_test
import (
"database/sql"
+ "fmt"
"links/cmd"
"links/core"
"links/models"
@@ 35,6 36,77 @@ func TestHandlers(t *testing.T) {
})
c.NoError(err)
+ t.Run("invalid domain", func(t *testing.T) {
+ invalidD := &models.Domain{
+ Name: "invalid",
+ LookupName: "foo.com",
+ OrgID: sql.NullInt64{Valid: true, Int64: 1},
+ Level: models.DomainLevelUser,
+ Service: models.DomainServiceLinks,
+ Status: models.DomainStatusApproved,
+ IsActive: false,
+ }
+ err := invalidD.Store(dbCtx)
+ c.NoError(err)
+
+ request := httptest.NewRequest(http.MethodGet, "/invalid/"+invalidD.LookupName, nil)
+ recorder := httptest.NewRecorder()
+ ctx := &server.Context{
+ Server: srv,
+ Context: e.NewContext(request, recorder),
+ User: loggedInUser,
+ }
+ ctx.SetPath("/invalid/:d")
+ ctx.SetParamNames("d")
+ ctx.SetParamValues(invalidD.LookupName)
+ err = cmd.MakeRequest(srv, coreService.InvalidDomain, ctx)
+ c.NoError(err)
+ htmlBody := recorder.Body.String()
+ c.True(strings.Contains(htmlBody, "Invalid Domain"))
+
+ })
+
+ t.Run("homepage", func(t *testing.T) {
+ d := &models.Domain{
+ Name: "valid user domain",
+ LookupName: "foo.com",
+ OrgID: sql.NullInt64{Valid: false, Int64: 0},
+ Level: models.DomainLevelSystem,
+ Service: models.DomainServiceLinks,
+ Status: models.DomainStatusApproved,
+ IsActive: true,
+ }
+ err := d.Store(dbCtx)
+ c.NoError(err)
+ request := httptest.NewRequest(http.MethodGet, "/", nil)
+ recorder := httptest.NewRecorder()
+ ctx := &server.Context{
+ Server: srv,
+ Context: e.NewContext(request, recorder),
+ User: loggedInUser,
+ }
+ ctx.SetPath("/")
+ err = cmd.MakeRequestWithDomain(srv, coreService.Homepage, ctx, d)
+ c.NoError(err)
+ })
+
+ t.Run("feature tour", func(t *testing.T) {
+ request := httptest.NewRequest(http.MethodGet, "/tour", nil)
+ recorder := httptest.NewRecorder()
+ ctx := &server.Context{
+ Server: srv,
+ Context: e.NewContext(request, recorder),
+ User: loggedInUser,
+ }
+ ctx.SetPath("/tour")
+ err = cmd.MakeRequestWithDomain(srv, coreService.FeatureTour, ctx, domains[0])
+ c.NoError(err)
+ c.Equal(http.StatusOK, recorder.Code)
+ htmlBody := recorder.Body.String()
+ c.True(strings.Contains(htmlBody, "Explore Features"))
+
+ })
+
t.Run("home link list", func(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
@@ 387,6 459,35 @@ func TestHandlers(t *testing.T) {
c.Equal(http.StatusMovedPermanently, recorder.Code)
})
+ t.Run("org lists", func(t *testing.T) {
+ domains, err := models.GetDomains(dbCtx, &database.FilterOptions{
+ Filter: sq.And{
+ sq.Eq{"d.lookup_name": "foo.com"},
+ },
+ Limit: 1,
+ })
+ c.NoError(err)
+ domain := domains[0]
+ httpmock.Activate()
+ defer httpmock.DeactivateAndReset()
+ jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK,
+ httpmock.File("samples/org_list.json"))
+ c.NoError(err)
+ httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)
+
+ request := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/organizations?d=%s", domain.LookupName), nil)
+ recorder := httptest.NewRecorder()
+ ctx := &server.Context{
+ Server: srv,
+ Context: e.NewContext(request, recorder),
+ User: loggedInUser,
+ }
+ ctx.SetPath("/organizations")
+ err = cmd.MakeRequest(srv, coreService.OrgList, ctx)
+ c.NoError(err)
+ c.Equal(http.StatusOK, recorder.Code)
+ })
+
t.Run("qr redirect", func(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
A core/samples/org_list.json => core/samples/org_list.json +39 -0
@@ 0,0 1,39 @@
+{
+ "data": {
+ "getOrganizations": [
+ {
+ "id": 1,
+ "name": "Org 1",
+ "slug": "org-1",
+ "isActive": true,
+ "settings": {
+ "billing": {
+ "status": 1
+ }
+ }
+ },
+ {
+ "id": 59,
+ "name": "Org 2",
+ "slug": "org-2",
+ "isActive": true,
+ "settings": {
+ "billing": {
+ "status": 2
+ }
+ }
+ },
+ {
+ "id": 60,
+ "name": "Org 3",
+ "slug": "org-3",
+ "isActive": true,
+ "settings": {
+ "billing": {
+ "status": 0
+ }
+ }
+ }
+ ]
+ }
+}