File size: 25,398 Bytes
88211d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
defmodule PlausibleWeb.Router do
  use PlausibleWeb, :router
  use Plausible
  import Phoenix.LiveView.Router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_secure_browser_headers
    plug PlausibleWeb.Plugs.NoRobots
    on_ee(do: nil, else: plug(PlausibleWeb.FirstLaunchPlug, redirect_to: "/register"))
    plug PlausibleWeb.AuthPlug
    on_ee(do: plug(Plausible.Plugs.HandleExpiredSession))
    on_ee(do: plug(Plausible.Plugs.SSOTeamAccess))
    plug PlausibleWeb.Plugs.UserSessionTouch
    plug :put_root_layout, html: {PlausibleWeb.LayoutView, :app}
  end

  on_ee do
    pipeline :browser_sso_notice do
      plug :accepts, ["html"]
      plug :fetch_session
      plug :fetch_live_flash
      plug :put_secure_browser_headers
      plug PlausibleWeb.Plugs.NoRobots
      on_ee(do: nil, else: plug(PlausibleWeb.FirstLaunchPlug, redirect_to: "/register"))
      plug PlausibleWeb.AuthPlug
      on_ee(do: plug(Plausible.Plugs.HandleExpiredSession))
      plug PlausibleWeb.Plugs.UserSessionTouch
      plug :put_root_layout, html: {PlausibleWeb.LayoutView, :app}
    end
  end

  pipeline :shared_link do
    plug :accepts, ["html"]
    plug PlausibleWeb.Plugs.SecureEmbedHeaders
    plug PlausibleWeb.Plugs.NoRobots
    plug :put_root_layout, html: {PlausibleWeb.LayoutView, :app}
  end

  on_ee do
    pipeline :helpscout do
      plug :accepts, ["html"]
      plug :fetch_session
      plug PlausibleWeb.Plugs.SecureEmbedHeaders
      plug PlausibleWeb.Plugs.NoRobots
      plug PlausibleWeb.AuthPlug
      plug :put_root_layout, html: {PlausibleWeb.LayoutView, :app}
    end
  end

  pipeline :csrf do
    plug :protect_from_forgery
  end

  pipeline :app_layout do
    plug :put_root_layout, html: {PlausibleWeb.LayoutView, :app}
  end

  pipeline :external_api do
    plug :accepts, ["json"]
  end

  pipeline :api do
    plug :accepts, ["json"]
    plug :fetch_session
    plug PlausibleWeb.AuthPlug
  end

  pipeline :internal_stats_api do
    plug :accepts, ["json"]
    plug :fetch_session
    plug PlausibleWeb.AuthPlug
    plug PlausibleWeb.Plugs.AuthorizeSiteAccess
    plug PlausibleWeb.Plugs.NoRobots
    plug PlausibleWeb.Plugs.InternalStatsApiVersion
  end

  pipeline :docs_stats_api do
    plug :accepts, ["json"]
    plug :fetch_session
    plug PlausibleWeb.AuthPlug

    plug PlausibleWeb.Plugs.AuthorizeSiteAccess,
         {[:admin, :editor, :super_admin, :owner], "site_id"}

    plug PlausibleWeb.Plugs.NoRobots
  end

  pipeline :public_api do
    plug :accepts, ["json"]
  end

  on_ee do
    pipeline :flags do
      plug :accepts, ["html"]
      plug :put_secure_browser_headers
      plug PlausibleWeb.Plugs.NoRobots
      plug :fetch_session

      plug PlausibleWeb.AuthPlug
      plug PlausibleWeb.SuperAdminOnlyPlug
    end
  end

  if Mix.env() in [:dev, :ce_dev, :e2e_test] do
    forward "/sent-emails", Bamboo.SentEmailViewerPlug
    forward "/sent-emails-api", Bamboo.SentEmailApiPlug
  end

  on_ee do
    live_session :customer_support,
      on_mount: PlausibleWeb.Live.SuperAdminLiveAuth do
      scope alias: PlausibleWeb.Live,
            assigns: %{connect_live_socket: true, skip_plausible_tracking: true} do
        pipe_through [:browser, :csrf, :app_layout, :flags]

        live "/cs", CustomerSupport, :index, as: :customer_support
        live "/cs/teams/team/:id", CustomerSupport.Team, :show, as: :customer_support_team
        live "/cs/users/user/:id", CustomerSupport.User, :show, as: :customer_support_user
        live "/cs/sites/site/:id", CustomerSupport.Site, :show, as: :customer_support_site
      end
    end
  end

  on_ee do
    scope path: "/flags" do
      pipe_through :flags
      forward "/", FunWithFlags.UI.Router, namespace: "flags"
    end
  end

  on_ee do
    if Mix.env() in [:dev, :test, :e2e_test] do
      scope "/dev", PlausibleWeb do
        pipe_through :browser

        get "/billing/create-subscription-form/:plan_id", DevSubscriptionController, :create_form
        get "/billing/update-subscription-form", DevSubscriptionController, :update_form
        get "/billing/cancel-subscription-form", DevSubscriptionController, :cancel_form

        post "/billing/create-subscription/:plan_id", DevSubscriptionController, :create
        post "/billing/update-subscription", DevSubscriptionController, :update
        post "/billing/cancel-subscription", DevSubscriptionController, :cancel
      end
    end
  end

  # Routes for plug integration testing
  if Mix.env() in [:test, :ce_test, :e2e_test] do
    scope "/plug-tests", PlausibleWeb do
      scope [] do
        pipe_through :browser

        get("/basic", TestController, :browser_basic)
        get("/:domain/shared-link/:slug", TestController, :browser_basic)
        get("/:domain/with-domain", TestController, :browser_basic)
      end

      scope [] do
        pipe_through :api

        get("/api-basic", TestController, :api_basic)
        get("/:domain/api-with-domain", TestController, :api_basic)
      end
    end
  end

  # Routes for E2E testing
  on_ee do
    if Mix.env() == :e2e_test do
      scope "/e2e-tests", PlausibleWeb do
        pipe_through :api

        post "/stats", E2EController, :populate_stats
        post "/funnel", E2EController, :create_funnel
        post "/goal", E2EController, :create_goal
      end
    end
  end

  # SSO routes
  on_ee do
    pipeline :sso_saml do
      plug :accepts, ["html"]

      plug PlausibleWeb.Plugs.SecureSSO

      plug PlausibleWeb.Plugs.NoRobots

      plug :fetch_session
      plug :fetch_live_flash
    end

    pipeline :sso_saml_auth do
      plug :protect_from_forgery, with: :clear_session
    end

    scope "/sso", PlausibleWeb do
      pipe_through [:browser, :csrf]

      get "/login", SSOController, :login_form
      post "/login", SSOController, :login
    end

    scope "/sso/saml", PlausibleWeb do
      pipe_through [:sso_saml]

      scope [] do
        pipe_through :sso_saml_auth

        get "/signin/:integration_id", SSOController, :saml_signin
      end

      post "/consume/:integration_id", SSOController, :saml_consume
      post "/csp-report", SSOController, :csp_report
    end
  end

  scope path: "/api/plugins", as: :plugins_api do
    pipeline :plugins_api_auth do
      plug(PlausibleWeb.Plugs.AuthorizePluginsAPI)
    end

    pipeline :plugins_api do
      plug(:accepts, ["json"])
      plug(OpenApiSpex.Plug.PutApiSpec, module: PlausibleWeb.Plugins.API.Spec)
    end

    scope "/spec" do
      pipe_through(:plugins_api)
      get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
      get("/swagger-ui", OpenApiSpex.Plug.SwaggerUI, path: "/api/plugins/spec/openapi")
    end

    scope "/v1/capabilities", PlausibleWeb.Plugins.API.Controllers,
      assigns: %{plugins_api: true} do
      pipe_through([:plugins_api])
      get("/", Capabilities, :index)
    end

    scope "/v1", PlausibleWeb.Plugins.API.Controllers, assigns: %{plugins_api: true} do
      pipe_through([:plugins_api, :plugins_api_auth])

      get("/shared_links", SharedLinks, :index)
      get("/shared_links/:id", SharedLinks, :get)
      put("/shared_links", SharedLinks, :create)

      get("/goals", Goals, :index)
      get("/goals/:id", Goals, :get)
      put("/goals", Goals, :create)

      on_ee do
        get("/funnels/:id", Funnels, :get)
        get("/funnels", Funnels, :index)
        put("/funnels", Funnels, :create)
      end

      delete("/goals/:id", Goals, :delete)
      delete("/goals", Goals, :delete_bulk)

      put("/custom_props", CustomProps, :enable)
      delete("/custom_props", CustomProps, :disable)

      get("/tracker_script_configuration", TrackerScriptConfiguration, :get)
      put("/tracker_script_configuration", TrackerScriptConfiguration, :update)
    end
  end

  scope "/api" do
    pipe_through :internal_stats_api

    scope "/stats", PlausibleWeb.Api do
      on_ee do
        get "/:domain/funnels/:id", StatsController, :funnel

        post "/:domain/exploration/next", StatsController, :exploration_next
        post "/:domain/exploration/funnel", StatsController, :exploration_funnel

        post "/:domain/exploration/next-with-funnel",
             StatsController,
             :exploration_next_with_funnel
      end

      scope private: %{allow_consolidated_views: true} do
        post "/:domain/query", StatsController, :query
        post "/:domain/export", StatsController, :csv_export
        get "/:domain/google-search-terms", StatsController, :google_search_terms
        get "/:domain/current-visitors", StatsController, :current_visitors
        get "/:domain/suggestions/:filter_name", StatsController, :filter_suggestions

        get "/:domain/suggestions/custom-prop-values/:prop_key",
            StatsController,
            :custom_prop_value_filter_suggestions
      end
    end

    scope "/:domain/segments", PlausibleWeb.Api.Internal,
      private: %{allow_consolidated_views: true} do
      post "/", SegmentsController, :create
      patch "/:segment_id", SegmentsController, :update
      delete "/:segment_id", SegmentsController, :delete
      get "/:segment_id/shared-links", SegmentsController, :get_related_shared_links
    end

    scope "/:domain/annotations", PlausibleWeb.Api.Internal,
      private: %{allow_consolidated_views: true} do
      get "/", AnnotationsController, :index
      post "/", AnnotationsController, :create
      patch "/:annotation_id", AnnotationsController, :update
      delete "/:annotation_id", AnnotationsController, :delete
    end
  end

  scope "/api/v1/stats", PlausibleWeb.Api,
    assigns: %{api_scope: "stats:read:*", api_context: :site} do
    pipe_through [:public_api, PlausibleWeb.Plugs.AuthorizePublicAPI]

    get "/realtime/visitors", ExternalStatsController, :realtime_visitors
    get "/aggregate", ExternalStatsController, :aggregate
    get "/breakdown", ExternalStatsController, :breakdown
    get "/timeseries", ExternalStatsController, :timeseries
  end

  scope "/api/v2", PlausibleWeb.Api,
    private: %{
      allow_consolidated_views: true
    },
    assigns: %{
      api_scope: "stats:read:*",
      api_context: :site
    } do
    pipe_through [:public_api, PlausibleWeb.Plugs.AuthorizePublicAPI]

    post "/query", ExternalQueryApiController, :query
  end

  scope "/api/docs", PlausibleWeb.Api do
    get "/query/schema.json", ExternalQueryApiController, :schema

    scope [] do
      pipe_through :docs_stats_api

      post "/query", ExternalQueryApiController, :query
    end
  end

  on_ee do
    scope "/api/v1/sites", PlausibleWeb.Api do
      pipe_through :public_api

      scope assigns: %{api_scope: "sites:read:*"} do
        pipe_through PlausibleWeb.Plugs.AuthorizePublicAPI

        get "/", ExternalSitesController, :index
        get "/teams", ExternalSitesController, :teams_index

        scope assigns: %{api_context: :site} do
          get "/goals", ExternalSitesController, :goals_index
          get "/custom-props", ExternalSitesController, :custom_props_index
          get "/guests", ExternalSitesController, :guests_index
          get "/:site_id", ExternalSitesController, :get_site
        end
      end

      scope assigns: %{api_scope: "sites:provision:*"} do
        pipe_through PlausibleWeb.Plugs.AuthorizePublicAPI

        post "/", ExternalSitesController, :create_site

        scope assigns: %{api_context: :site} do
          put "/shared-links", ExternalSitesController, :find_or_create_shared_link

          put "/goals", ExternalSitesController, :find_or_create_goal
          delete "/goals/:goal_id", ExternalSitesController, :delete_goal

          put "/custom-props", ExternalSitesController, :add_custom_prop
          # Property name can contain forward slashes, hence we match on wildcard here
          delete "/custom-props/*property", ExternalSitesController, :delete_custom_prop

          put "/guests", ExternalSitesController, :find_or_create_guest
          delete "/guests/:email", ExternalSitesController, :delete_guest

          put "/:site_id", ExternalSitesController, :update_site
          delete "/:site_id", ExternalSitesController, :delete_site
        end
      end
    end
  end

  scope "/api", PlausibleWeb do
    scope [] do
      pipe_through :external_api

      post "/event", Api.ExternalController, :event
      get "/error", Api.ExternalController, :error
      # Remove this once all external checks are migration to new /system/health/* checks
      get "/health", Api.SystemController, :readiness
    end

    scope "/system" do
      get "/", Api.SystemController, :info
      get "/health/live", Api.SystemController, :liveness
      get "/health/ready", Api.SystemController, :readiness
    end

    scope [] do
      pipe_through :api
      post "/paddle/webhook", Api.PaddleController, :webhook
      get "/paddle/currency", Api.PaddleController, :currency

      put "/:domain/disable-feature", Api.InternalController, :disable_feature

      get "/sites", Api.InternalController, :sites
    end
  end

  scope "/", PlausibleWeb do
    get "/health", Api.SystemController, :liveness
    get "/api-docs", Api.SystemController, :api_docs
  end

  scope "/", PlausibleWeb do
    pipe_through [:browser, :csrf]

    scope alias: Live, assigns: %{connect_live_socket: true} do
      pipe_through [PlausibleWeb.RequireLoggedOutPlug, :app_layout]

      live_session :auth, on_mount: PlausibleWeb.Live.AuthLayoutContext do
        scope assigns: %{disable_registration_for: [:invite_only, true]} do
          pipe_through PlausibleWeb.Plugs.MaybeDisableRegistration

          live "/register", RegisterForm, :register_form, as: :auth
        end

        scope assigns: %{
                disable_registration_for: true,
                dogfood_page_path: "/register/invitation/:invitation_id"
              } do
          pipe_through PlausibleWeb.Plugs.MaybeDisableRegistration

          live "/register/invitation/:invitation_id",
               RegisterForm,
               :register_from_invitation_form, as: :auth
        end
      end
    end

    get "/activate", AuthController, :activate_form
    post "/activate/request-code", AuthController, :request_activation_code
    post "/activate", AuthController, :activate
    get "/login", AuthController, :login_form
    post "/login", AuthController, :login
    get "/password/request-reset", AuthController, :password_reset_request_form
    post "/password/request-reset", AuthController, :password_reset_request
    get "/2fa/setup/force-initiate", AuthController, :force_initiate_2fa_setup
    post "/2fa/setup/initiate", AuthController, :initiate_2fa_setup
    get "/2fa/setup/verify", AuthController, :verify_2fa_setup_form
    post "/2fa/setup/verify", AuthController, :verify_2fa_setup
    post "/2fa/disable", AuthController, :disable_2fa
    post "/2fa/recovery_codes", AuthController, :generate_2fa_recovery_codes
    get "/2fa/verify", AuthController, :verify_2fa_form
    post "/2fa/verify", AuthController, :verify_2fa
    get "/2fa/use_recovery_code", AuthController, :verify_2fa_recovery_code_form
    post "/2fa/use_recovery_code", AuthController, :verify_2fa_recovery_code
    get "/password/reset", AuthController, :password_reset_form
    post "/password/reset", AuthController, :password_reset
    get "/avatar/:hash", AvatarController, :avatar
    post "/error_report", ErrorReportController, :submit_error_report
  end

  scope "/", PlausibleWeb do
    pipe_through [:shared_link]

    get "/share/:domain/*path", StatsController, :shared_link
    post "/share/:slug/authenticate", StatsController, :authenticate_shared_link
  end

  scope "/settings", PlausibleWeb do
    pipe_through [
      :browser,
      :csrf,
      PlausibleWeb.RequireAccountPlug,
      PlausibleWeb.Plugs.CurrentPath
    ]

    get "/", SettingsController, :index
    get "/preferences", SettingsController, :preferences

    post "/preferences/name", SettingsController, :update_name
    post "/preferences/theme", SettingsController, :update_theme

    get "/security", SettingsController, :security
    delete "/security/user-sessions/:id", SettingsController, :delete_session

    post "/security/email/cancel", SettingsController, :cancel_update_email
    post "/security/email", SettingsController, :update_email
    post "/security/password", SettingsController, :update_password

    live_session :settings, on_mount: PlausibleWeb.Live.SettingsContext do
      scope alias: Live, assigns: %{connect_live_socket: true} do
        live "/billing/subscription", SubscriptionSettings, :subscription, as: :settings
      end
    end

    get "/billing/invoices", SettingsController, :redirect_invoices
    get "/api-keys", SettingsController, :api_keys

    get "/api-keys/new", SettingsController, :new_api_key
    post "/api-keys", SettingsController, :create_api_key
    delete "/api-keys/:id", SettingsController, :delete_api_key

    get "/danger-zone", SettingsController, :danger_zone

    get "/team/general", SettingsController, :team_general
    post "/team/general/name", SettingsController, :update_team_name
    post "/team/leave", SettingsController, :leave_team
    post "/team/force_2fa/enable", SettingsController, :enable_team_force_2fa
    post "/team/force_2fa/disable", SettingsController, :disable_team_force_2fa

    on_ee do
      get "/sso/info", SSOController, :cta
      get "/sso/general", SSOController, :sso_settings
      get "/sso/sessions", SSOController, :team_sessions
      delete "/sso/sessions/:session_id", SSOController, :delete_session
    end

    post "/team/invitations/:invitation_id/accept", InvitationController, :accept_invitation
    post "/team/invitations/:invitation_id/reject", InvitationController, :reject_invitation
    delete "/team/invitations/:invitation_id", InvitationController, :remove_team_invitation
    get "/team/delete", SettingsController, :team_danger_zone
    delete "/team/delete", SettingsController, :delete_team
  end

  on_ee do
    scope "/", PlausibleWeb do
      pipe_through [:browser_sso_notice, :csrf]

      get "/sso/notice", SSOController, :provision_notice
      get "/sso/issue", SSOController, :provision_issue
      get "/logout", AuthController, :logout
    end

    scope "/", PlausibleWeb do
      pipe_through [:helpscout, :csrf]

      get "/helpscout/callback", HelpScoutController, :callback
      get "/helpscout/show", HelpScoutController, :show
      get "/helpscout/search", HelpScoutController, :search
    end
  end

  scope "/", PlausibleWeb do
    pipe_through [:browser, :csrf]

    on_ce do
      get "/logout", AuthController, :logout
    end

    delete "/me", AuthController, :delete_me

    get "/auth/google/callback", AuthController, :google_auth_callback

    get "/", PageController, :index

    get "/billing/change-plan/preview/:plan_id", BillingController, :change_plan_preview
    post "/billing/change-plan/:new_plan_id", BillingController, :change_plan
    get "/billing/choose-plan", BillingController, :choose_plan
    get "/billing/upgrade-to-enterprise-plan", BillingController, :upgrade_to_enterprise_plan
    get "/billing/upgrade-success", BillingController, :upgrade_success
    get "/billing/subscription/ping", BillingController, :ping_subscription

    scope alias: Live, assigns: %{connect_live_socket: true} do
      pipe_through [:app_layout, PlausibleWeb.RequireAccountPlug]

      live "/sites", Sites, :index, as: :site
      live "/team/setup", TeamSetup, :setup, as: :team_setup
    end

    get "/sites/new", SiteController, :new
    post "/sites", SiteController, :create_site
    post "/sites/:domain/make-public", SiteController, :make_public
    post "/sites/:domain/make-private", SiteController, :make_private

    get "/sites/:domain/memberships/invite", Site.MembershipController, :invite_member_form
    post "/sites/:domain/memberships/invite", Site.MembershipController, :invite_member

    post "/sites/invitations/:invitation_id/accept", InvitationController, :accept_invitation

    post "/sites/invitations/:invitation_id/reject", InvitationController, :reject_invitation

    delete "/sites/:domain/invitations/:invitation_id", InvitationController, :remove_invitation

    put "/sites/:domain/memberships/u/:id/role/:new_role",
        Site.MembershipController,
        :update_role_by_user

    delete "/sites/:domain/memberships/u/:id", Site.MembershipController, :remove_member_by_user

    scope alias: Live, assigns: %{connect_live_socket: true} do
      pipe_through [:app_layout, PlausibleWeb.RequireAccountPlug]

      scope assigns: %{
              dogfood_page_path: "/:website/installation"
            } do
        live "/:domain/installation", Installation, :installation, as: :site
      end

      scope assigns: %{
              dogfood_page_path: "/:website/verification"
            } do
        live "/:domain/verification",
             on_ee(do: Verification, else: AwaitingPageviews),
             :verification,
             as: :site
      end

      scope assigns: %{
              dogfood_page_path: "/:website/change-domain"
            } do
        live "/:domain/change-domain", ChangeDomain, :change_domain, as: :site
        live "/:domain/change-domain/success", ChangeDomain, :success, as: :site
      end
    end

    get "/:domain/settings/people", SiteController, :settings_people
    get "/:domain/settings/visibility", SiteController, :settings_visibility

    on_ee do
      get "/:domain/settings/funnels", SiteController, :settings_funnels
    end

    get "/:domain/settings/danger-zone", SiteController, :settings_danger_zone
    get "/:domain/settings/integrations", SiteController, :settings_integrations
    get "/:domain/settings/shields/:shield", SiteController, :settings_shields
    get "/:domain/settings/imports-exports", SiteController, :settings_imports_exports

    put "/:domain/settings/google", SiteController, :update_google_auth
    delete "/:domain/settings/google-search", SiteController, :delete_google_auth
    delete "/:domain/settings/google-import", SiteController, :delete_google_auth
    delete "/:domain", SiteController, :delete_site
    delete "/:domain/stats", SiteController, :reset_stats

    get "/:domain/import/google-analytics/property",
        GoogleAnalyticsController,
        :property_form

    post "/:domain/import/google-analytics/property",
         GoogleAnalyticsController,
         :property

    get "/:domain/import/google-analytics/confirm", GoogleAnalyticsController, :confirm
    post "/:domain/settings/google-import", GoogleAnalyticsController, :import

    delete "/:domain/settings/forget-imported", SiteController, :forget_imported
    delete "/:domain/settings/forget-import/:import_id", SiteController, :forget_import

    get "/:domain/download/export", SiteController, :download_export
    get "/:domain/settings/import", SiteController, :csv_import

    get "/debug/clickhouse", DebugController, :clickhouse

    scope private: %{allow_consolidated_views: true} do
      post "/sites/:domain/weekly-report/enable", SiteController, :enable_weekly_report
      post "/sites/:domain/weekly-report/disable", SiteController, :disable_weekly_report
      post "/sites/:domain/weekly-report/recipients", SiteController, :add_weekly_report_recipient

      delete "/sites/:domain/weekly-report/recipients/:recipient",
             SiteController,
             :remove_weekly_report_recipient

      post "/sites/:domain/monthly-report/enable", SiteController, :enable_monthly_report
      post "/sites/:domain/monthly-report/disable", SiteController, :disable_monthly_report

      post "/sites/:domain/monthly-report/recipients",
           SiteController,
           :add_monthly_report_recipient

      delete "/sites/:domain/monthly-report/recipients/:recipient",
             SiteController,
             :remove_monthly_report_recipient

      post "/sites/:domain/traffic-change-notification/:type/enable",
           SiteController,
           :enable_traffic_change_notification

      post "/sites/:domain/traffic-change-notification/:type/disable",
           SiteController,
           :disable_traffic_change_notification

      put "/sites/:domain/traffic-change-notification/:type",
          SiteController,
          :update_traffic_change_notification

      post "/sites/:domain/traffic-change-notification/:type/recipients",
           SiteController,
           :add_traffic_change_notification_recipient

      delete "/sites/:domain/traffic-change-notification/:type/recipients/:recipient",
             SiteController,
             :remove_traffic_change_notification_recipient

      get "/sites/:domain/weekly-report/unsubscribe", UnsubscribeController, :weekly_report
      get "/sites/:domain/monthly-report/unsubscribe", UnsubscribeController, :monthly_report

      get "/:domain/settings", SiteController, :settings
      get "/:domain/settings/general", SiteController, :settings_general
      get "/:domain/settings/goals", SiteController, :settings_goals
      get "/:domain/settings/properties", SiteController, :settings_props
      get "/:domain/settings/email-reports", SiteController, :settings_email_reports

      put "/:domain/settings", SiteController, :update_settings

      get "/:domain", StatsController, :stats
      get "/:domain/*path", StatsController, :stats
    end
  end
end