@amplitude/wizard 1.0.0-beta.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/README.md +171 -74
  2. package/dist/bin.js +338 -222
  3. package/dist/src/lib/agent-interface.js +64 -9
  4. package/dist/src/lib/agent-runner.js +1 -10
  5. package/dist/src/lib/api.d.ts +22 -4
  6. package/dist/src/lib/api.js +114 -12
  7. package/dist/src/lib/commandments.js +14 -1
  8. package/dist/src/lib/constants.d.ts +6 -5
  9. package/dist/src/lib/constants.js +13 -13
  10. package/dist/src/lib/credential-resolution.d.ts +45 -0
  11. package/dist/src/lib/credential-resolution.js +311 -0
  12. package/dist/src/lib/exit-codes.d.ts +10 -0
  13. package/dist/src/lib/exit-codes.js +12 -0
  14. package/dist/src/lib/health-checks/statuspage.d.ts +1 -0
  15. package/dist/src/lib/health-checks/statuspage.js +5 -1
  16. package/dist/src/lib/mode-config.d.ts +14 -0
  17. package/dist/src/lib/mode-config.js +14 -0
  18. package/dist/src/lib/session-checkpoint.d.ts +27 -0
  19. package/dist/src/lib/session-checkpoint.js +134 -0
  20. package/dist/src/lib/wizard-session.d.ts +44 -1
  21. package/dist/src/lib/wizard-session.js +70 -14
  22. package/dist/src/lib/wizard-tools.js +19 -4
  23. package/dist/src/steps/add-mcp-server-to-clients/clients/claude.d.ts +3 -0
  24. package/dist/src/steps/add-mcp-server-to-clients/clients/claude.js +6 -0
  25. package/dist/src/steps/add-mcp-server-to-clients/clients/cursor.js +3 -1
  26. package/dist/src/ui/agent-ui.d.ts +91 -0
  27. package/dist/src/ui/agent-ui.js +277 -0
  28. package/dist/src/ui/logging-ui.js +1 -1
  29. package/dist/src/ui/tui/App.d.ts +12 -0
  30. package/dist/src/ui/tui/App.js +29 -18
  31. package/dist/src/ui/tui/components/AmplitudeLogo.js +16 -17
  32. package/dist/src/ui/tui/components/AmplitudeTextLogo.d.ts +0 -2
  33. package/dist/src/ui/tui/components/AmplitudeTextLogo.js +53 -18
  34. package/dist/src/ui/tui/components/BrailleSpinner.d.ts +8 -0
  35. package/dist/src/ui/tui/components/BrailleSpinner.js +15 -0
  36. package/dist/src/ui/tui/components/ConsoleView.d.ts +8 -11
  37. package/dist/src/ui/tui/components/ConsoleView.js +51 -34
  38. package/dist/src/ui/tui/components/HeaderBar.d.ts +12 -0
  39. package/dist/src/ui/tui/components/HeaderBar.js +17 -0
  40. package/dist/src/ui/tui/components/JourneyStepper.d.ts +16 -0
  41. package/dist/src/ui/tui/components/JourneyStepper.js +83 -0
  42. package/dist/src/ui/tui/components/KeyHintBar.d.ts +19 -0
  43. package/dist/src/ui/tui/components/KeyHintBar.js +20 -0
  44. package/dist/src/ui/tui/console-commands.d.ts +1 -2
  45. package/dist/src/ui/tui/console-commands.js +48 -7
  46. package/dist/src/ui/tui/flows.d.ts +1 -1
  47. package/dist/src/ui/tui/flows.js +1 -1
  48. package/dist/src/ui/tui/hooks/useAsyncEffect.d.ts +15 -0
  49. package/dist/src/ui/tui/hooks/useAsyncEffect.js +35 -0
  50. package/dist/src/ui/tui/hooks/useWizardStore.d.ts +9 -0
  51. package/dist/src/ui/tui/hooks/useWizardStore.js +11 -0
  52. package/dist/src/ui/tui/ink-ui.js +1 -1
  53. package/dist/src/ui/tui/primitives/DissolveTransition.js +4 -5
  54. package/dist/src/ui/tui/primitives/EventPlanViewer.d.ts +3 -1
  55. package/dist/src/ui/tui/primitives/EventPlanViewer.js +8 -3
  56. package/dist/src/ui/tui/primitives/ProgressList.js +1 -1
  57. package/dist/src/ui/tui/primitives/SlashCommandInput.js +19 -4
  58. package/dist/src/ui/tui/primitives/SplitView.d.ts +2 -1
  59. package/dist/src/ui/tui/primitives/SplitView.js +10 -2
  60. package/dist/src/ui/tui/primitives/TabContainer.js +10 -2
  61. package/dist/src/ui/tui/primitives/index.d.ts +0 -1
  62. package/dist/src/ui/tui/primitives/index.js +0 -1
  63. package/dist/src/ui/tui/router.js +1 -1
  64. package/dist/src/ui/tui/screen-registry.d.ts +0 -7
  65. package/dist/src/ui/tui/screen-registry.js +13 -4
  66. package/dist/src/ui/tui/screens/ActivationOptionsScreen.d.ts +2 -2
  67. package/dist/src/ui/tui/screens/ActivationOptionsScreen.js +8 -8
  68. package/dist/src/ui/tui/screens/AuthScreen.js +57 -27
  69. package/dist/src/ui/tui/screens/ChecklistScreen.d.ts +2 -12
  70. package/dist/src/ui/tui/screens/ChecklistScreen.js +22 -33
  71. package/dist/src/ui/tui/screens/DataIngestionCheckScreen.d.ts +3 -12
  72. package/dist/src/ui/tui/screens/DataIngestionCheckScreen.js +109 -39
  73. package/dist/src/ui/tui/screens/DataSetupScreen.d.ts +3 -3
  74. package/dist/src/ui/tui/screens/DataSetupScreen.js +17 -10
  75. package/dist/src/ui/tui/screens/IntroScreen.d.ts +5 -3
  76. package/dist/src/ui/tui/screens/IntroScreen.js +132 -41
  77. package/dist/src/ui/tui/screens/LoginScreen.d.ts +1 -1
  78. package/dist/src/ui/tui/screens/LoginScreen.js +4 -4
  79. package/dist/src/ui/tui/screens/LogoutScreen.d.ts +4 -2
  80. package/dist/src/ui/tui/screens/LogoutScreen.js +17 -5
  81. package/dist/src/ui/tui/screens/McpScreen.d.ts +4 -4
  82. package/dist/src/ui/tui/screens/McpScreen.js +25 -17
  83. package/dist/src/ui/tui/screens/OutageScreen.d.ts +1 -1
  84. package/dist/src/ui/tui/screens/OutageScreen.js +5 -5
  85. package/dist/src/ui/tui/screens/OutroScreen.d.ts +5 -0
  86. package/dist/src/ui/tui/screens/OutroScreen.js +21 -14
  87. package/dist/src/ui/tui/screens/RegionSelectScreen.js +15 -13
  88. package/dist/src/ui/tui/screens/RunScreen.d.ts +7 -5
  89. package/dist/src/ui/tui/screens/RunScreen.js +102 -157
  90. package/dist/src/ui/tui/screens/SettingsOverrideScreen.d.ts +1 -1
  91. package/dist/src/ui/tui/screens/SettingsOverrideScreen.js +6 -5
  92. package/dist/src/ui/tui/screens/SetupScreen.d.ts +1 -1
  93. package/dist/src/ui/tui/screens/SetupScreen.js +7 -7
  94. package/dist/src/ui/tui/screens/SlackScreen.d.ts +2 -2
  95. package/dist/src/ui/tui/screens/SlackScreen.js +60 -35
  96. package/dist/src/ui/tui/session-constants.d.ts +41 -0
  97. package/dist/src/ui/tui/session-constants.js +38 -0
  98. package/dist/src/ui/tui/start-tui.d.ts +3 -1
  99. package/dist/src/ui/tui/start-tui.js +14 -10
  100. package/dist/src/ui/tui/store.d.ts +2 -1
  101. package/dist/src/ui/tui/store.js +33 -7
  102. package/dist/src/ui/tui/styles.d.ts +75 -19
  103. package/dist/src/ui/tui/styles.js +101 -19
  104. package/dist/src/ui/tui/utils/classify-error.d.ts +14 -0
  105. package/dist/src/ui/tui/utils/classify-error.js +90 -0
  106. package/dist/src/ui/tui/utils/diagnostics.d.ts +21 -0
  107. package/dist/src/ui/tui/utils/diagnostics.js +72 -0
  108. package/dist/src/ui/tui/utils/with-retry.d.ts +12 -0
  109. package/dist/src/ui/tui/utils/with-retry.js +32 -0
  110. package/dist/src/ui/tui/utils/with-timeout.d.ts +10 -0
  111. package/dist/src/ui/tui/utils/with-timeout.js +24 -0
  112. package/dist/src/utils/ampli-settings.d.ts +1 -1
  113. package/dist/src/utils/ampli-settings.js +15 -5
  114. package/dist/src/utils/api-key-store.js +5 -5
  115. package/dist/src/utils/atomic-write.d.ts +15 -0
  116. package/dist/src/utils/atomic-write.js +34 -0
  117. package/dist/src/utils/setup-utils.js +2 -2
  118. package/dist/src/utils/token-refresh.d.ts +22 -0
  119. package/dist/src/utils/token-refresh.js +79 -0
  120. package/dist/src/utils/wizard-abort.js +6 -1
  121. package/package.json +6 -6
  122. package/skills/instrumentation/add-analytics-instrumentation/SKILL.md +142 -0
  123. package/skills/instrumentation/diff-intake/SKILL.md +128 -0
  124. package/skills/instrumentation/discover-analytics-patterns/SKILL.md +185 -0
  125. package/skills/instrumentation/discover-event-surfaces/SKILL.md +322 -0
  126. package/skills/instrumentation/discover-event-surfaces/references/best-practices.md +563 -0
  127. package/skills/instrumentation/instrument-events/SKILL.md +169 -0
  128. package/skills/instrumentation/instrument-events/references/best-practices.md +563 -0
  129. package/skills/integration/integration-android/SKILL.md +49 -0
  130. package/skills/integration/integration-android/references/EXAMPLE.md +1977 -0
  131. package/skills/integration/integration-android/references/amplitude-quickstart.md +1845 -0
  132. package/skills/integration/integration-android/references/analytics.md +1778 -0
  133. package/skills/integration/integration-android/references/basic-integration-1.0-begin.md +43 -0
  134. package/skills/integration/integration-android/references/basic-integration-1.1-edit.md +35 -0
  135. package/skills/integration/integration-android/references/basic-integration-1.2-revise.md +23 -0
  136. package/skills/integration/integration-android/references/basic-integration-1.3-conclude.md +57 -0
  137. package/skills/integration/integration-angular/SKILL.md +49 -0
  138. package/skills/integration/integration-angular/references/EXAMPLE.md +899 -0
  139. package/skills/integration/integration-angular/references/amplitude-quickstart.md +1845 -0
  140. package/skills/integration/integration-angular/references/basic-integration-1.0-begin.md +43 -0
  141. package/skills/integration/integration-angular/references/basic-integration-1.1-edit.md +35 -0
  142. package/skills/integration/integration-angular/references/basic-integration-1.2-revise.md +23 -0
  143. package/skills/integration/integration-angular/references/basic-integration-1.3-conclude.md +57 -0
  144. package/skills/integration/integration-angular/references/browser-sdk-2.md +4680 -0
  145. package/skills/integration/integration-astro-hybrid/SKILL.md +56 -0
  146. package/skills/integration/integration-astro-hybrid/references/EXAMPLE.md +1095 -0
  147. package/skills/integration/integration-astro-hybrid/references/amplitude-quickstart.md +1845 -0
  148. package/skills/integration/integration-astro-hybrid/references/basic-integration-1.0-begin.md +43 -0
  149. package/skills/integration/integration-astro-hybrid/references/basic-integration-1.1-edit.md +35 -0
  150. package/skills/integration/integration-astro-hybrid/references/basic-integration-1.2-revise.md +23 -0
  151. package/skills/integration/integration-astro-hybrid/references/basic-integration-1.3-conclude.md +57 -0
  152. package/skills/integration/integration-astro-hybrid/references/browser-sdk-2.md +4680 -0
  153. package/skills/integration/integration-astro-ssr/SKILL.md +52 -0
  154. package/skills/integration/integration-astro-ssr/references/EXAMPLE.md +1106 -0
  155. package/skills/integration/integration-astro-ssr/references/amplitude-quickstart.md +1845 -0
  156. package/skills/integration/integration-astro-ssr/references/basic-integration-1.0-begin.md +43 -0
  157. package/skills/integration/integration-astro-ssr/references/basic-integration-1.1-edit.md +35 -0
  158. package/skills/integration/integration-astro-ssr/references/basic-integration-1.2-revise.md +23 -0
  159. package/skills/integration/integration-astro-ssr/references/basic-integration-1.3-conclude.md +57 -0
  160. package/skills/integration/integration-astro-ssr/references/browser-sdk-2.md +4680 -0
  161. package/skills/integration/integration-astro-static/SKILL.md +49 -0
  162. package/skills/integration/integration-astro-static/references/EXAMPLE.md +910 -0
  163. package/skills/integration/integration-astro-static/references/amplitude-quickstart.md +1845 -0
  164. package/skills/integration/integration-astro-static/references/basic-integration-1.0-begin.md +43 -0
  165. package/skills/integration/integration-astro-static/references/basic-integration-1.1-edit.md +35 -0
  166. package/skills/integration/integration-astro-static/references/basic-integration-1.2-revise.md +23 -0
  167. package/skills/integration/integration-astro-static/references/basic-integration-1.3-conclude.md +57 -0
  168. package/skills/integration/integration-astro-static/references/browser-sdk-2.md +4680 -0
  169. package/skills/integration/integration-astro-view-transitions/SKILL.md +51 -0
  170. package/skills/integration/integration-astro-view-transitions/references/EXAMPLE.md +979 -0
  171. package/skills/integration/integration-astro-view-transitions/references/amplitude-quickstart.md +1845 -0
  172. package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.0-begin.md +43 -0
  173. package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.1-edit.md +35 -0
  174. package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.2-revise.md +23 -0
  175. package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.3-conclude.md +57 -0
  176. package/skills/integration/integration-astro-view-transitions/references/browser-sdk-2.md +4680 -0
  177. package/skills/integration/integration-django/SKILL.md +57 -0
  178. package/skills/integration/integration-django/references/EXAMPLE.md +1005 -0
  179. package/skills/integration/integration-django/references/amplitude-quickstart.md +1845 -0
  180. package/skills/integration/integration-django/references/basic-integration-1.0-begin.md +43 -0
  181. package/skills/integration/integration-django/references/basic-integration-1.1-edit.md +35 -0
  182. package/skills/integration/integration-django/references/basic-integration-1.2-revise.md +23 -0
  183. package/skills/integration/integration-django/references/basic-integration-1.3-conclude.md +57 -0
  184. package/skills/integration/integration-django/references/python.md +1424 -0
  185. package/skills/integration/integration-expo/SKILL.md +53 -0
  186. package/skills/integration/integration-expo/references/EXAMPLE.md +1291 -0
  187. package/skills/integration/integration-expo/references/amplitude-quickstart.md +1845 -0
  188. package/skills/integration/integration-expo/references/basic-integration-1.0-begin.md +43 -0
  189. package/skills/integration/integration-expo/references/basic-integration-1.1-edit.md +35 -0
  190. package/skills/integration/integration-expo/references/basic-integration-1.2-revise.md +23 -0
  191. package/skills/integration/integration-expo/references/basic-integration-1.3-conclude.md +57 -0
  192. package/skills/integration/integration-expo/references/react-native-sdk.md +2819 -0
  193. package/skills/integration/integration-fastapi/SKILL.md +57 -0
  194. package/skills/integration/integration-fastapi/references/EXAMPLE.md +1389 -0
  195. package/skills/integration/integration-fastapi/references/amplitude-quickstart.md +1845 -0
  196. package/skills/integration/integration-fastapi/references/basic-integration-1.0-begin.md +43 -0
  197. package/skills/integration/integration-fastapi/references/basic-integration-1.1-edit.md +35 -0
  198. package/skills/integration/integration-fastapi/references/basic-integration-1.2-revise.md +23 -0
  199. package/skills/integration/integration-fastapi/references/basic-integration-1.3-conclude.md +57 -0
  200. package/skills/integration/integration-fastapi/references/python.md +1424 -0
  201. package/skills/integration/integration-flask/SKILL.md +56 -0
  202. package/skills/integration/integration-flask/references/EXAMPLE.md +1130 -0
  203. package/skills/integration/integration-flask/references/amplitude-quickstart.md +1845 -0
  204. package/skills/integration/integration-flask/references/basic-integration-1.0-begin.md +43 -0
  205. package/skills/integration/integration-flask/references/basic-integration-1.1-edit.md +35 -0
  206. package/skills/integration/integration-flask/references/basic-integration-1.2-revise.md +23 -0
  207. package/skills/integration/integration-flask/references/basic-integration-1.3-conclude.md +57 -0
  208. package/skills/integration/integration-flask/references/python.md +1424 -0
  209. package/skills/integration/integration-javascript_node/SKILL.md +54 -0
  210. package/skills/integration/integration-javascript_node/references/EXAMPLE.md +365 -0
  211. package/skills/integration/integration-javascript_node/references/amplitude-quickstart.md +1845 -0
  212. package/skills/integration/integration-javascript_node/references/analytics.md +1778 -0
  213. package/skills/integration/integration-javascript_node/references/basic-integration-1.0-begin.md +43 -0
  214. package/skills/integration/integration-javascript_node/references/basic-integration-1.1-edit.md +35 -0
  215. package/skills/integration/integration-javascript_node/references/basic-integration-1.2-revise.md +23 -0
  216. package/skills/integration/integration-javascript_node/references/basic-integration-1.3-conclude.md +57 -0
  217. package/skills/integration/integration-javascript_web/SKILL.md +58 -0
  218. package/skills/integration/integration-javascript_web/references/EXAMPLE.md +451 -0
  219. package/skills/integration/integration-javascript_web/references/amplitude-quickstart.md +1845 -0
  220. package/skills/integration/integration-javascript_web/references/basic-integration-1.0-begin.md +43 -0
  221. package/skills/integration/integration-javascript_web/references/basic-integration-1.1-edit.md +35 -0
  222. package/skills/integration/integration-javascript_web/references/basic-integration-1.2-revise.md +23 -0
  223. package/skills/integration/integration-javascript_web/references/basic-integration-1.3-conclude.md +57 -0
  224. package/skills/integration/integration-javascript_web/references/browser-sdk-2.md +4680 -0
  225. package/skills/integration/integration-laravel/SKILL.md +52 -0
  226. package/skills/integration/integration-laravel/references/EXAMPLE.md +2039 -0
  227. package/skills/integration/integration-laravel/references/amplitude-quickstart.md +1845 -0
  228. package/skills/integration/integration-laravel/references/analytics.md +1778 -0
  229. package/skills/integration/integration-laravel/references/basic-integration-1.0-begin.md +43 -0
  230. package/skills/integration/integration-laravel/references/basic-integration-1.1-edit.md +35 -0
  231. package/skills/integration/integration-laravel/references/basic-integration-1.2-revise.md +23 -0
  232. package/skills/integration/integration-laravel/references/basic-integration-1.3-conclude.md +57 -0
  233. package/skills/integration/integration-nextjs-app-router/SKILL.md +54 -0
  234. package/skills/integration/integration-nextjs-app-router/references/EXAMPLE.md +673 -0
  235. package/skills/integration/integration-nextjs-app-router/references/amplitude-quickstart.md +1845 -0
  236. package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.0-begin.md +43 -0
  237. package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.1-edit.md +35 -0
  238. package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.2-revise.md +23 -0
  239. package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.3-conclude.md +57 -0
  240. package/skills/integration/integration-nextjs-app-router/references/browser-sdk-2.md +4680 -0
  241. package/skills/integration/integration-nextjs-pages-router/SKILL.md +54 -0
  242. package/skills/integration/integration-nextjs-pages-router/references/EXAMPLE.md +735 -0
  243. package/skills/integration/integration-nextjs-pages-router/references/amplitude-quickstart.md +1845 -0
  244. package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.0-begin.md +43 -0
  245. package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.1-edit.md +35 -0
  246. package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.2-revise.md +23 -0
  247. package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.3-conclude.md +57 -0
  248. package/skills/integration/integration-nextjs-pages-router/references/browser-sdk-2.md +4680 -0
  249. package/skills/integration/integration-nuxt-3.6/SKILL.md +46 -0
  250. package/skills/integration/integration-nuxt-3.6/references/EXAMPLE.md +8422 -0
  251. package/skills/integration/integration-nuxt-3.6/references/amplitude-quickstart.md +1845 -0
  252. package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.0-begin.md +43 -0
  253. package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.1-edit.md +35 -0
  254. package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.2-revise.md +23 -0
  255. package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.3-conclude.md +57 -0
  256. package/skills/integration/integration-nuxt-3.6/references/browser-sdk-2.md +4680 -0
  257. package/skills/integration/integration-nuxt-4/SKILL.md +46 -0
  258. package/skills/integration/integration-nuxt-4/references/EXAMPLE.md +8670 -0
  259. package/skills/integration/integration-nuxt-4/references/amplitude-quickstart.md +1845 -0
  260. package/skills/integration/integration-nuxt-4/references/basic-integration-1.0-begin.md +43 -0
  261. package/skills/integration/integration-nuxt-4/references/basic-integration-1.1-edit.md +35 -0
  262. package/skills/integration/integration-nuxt-4/references/basic-integration-1.2-revise.md +23 -0
  263. package/skills/integration/integration-nuxt-4/references/basic-integration-1.3-conclude.md +57 -0
  264. package/skills/integration/integration-nuxt-4/references/browser-sdk-2.md +4680 -0
  265. package/skills/integration/integration-python/SKILL.md +53 -0
  266. package/skills/integration/integration-python/references/EXAMPLE.md +445 -0
  267. package/skills/integration/integration-python/references/amplitude-quickstart.md +1845 -0
  268. package/skills/integration/integration-python/references/basic-integration-1.0-begin.md +43 -0
  269. package/skills/integration/integration-python/references/basic-integration-1.1-edit.md +35 -0
  270. package/skills/integration/integration-python/references/basic-integration-1.2-revise.md +23 -0
  271. package/skills/integration/integration-python/references/basic-integration-1.3-conclude.md +57 -0
  272. package/skills/integration/integration-python/references/python.md +1424 -0
  273. package/skills/integration/integration-react-native/SKILL.md +49 -0
  274. package/skills/integration/integration-react-native/references/EXAMPLE.md +2253 -0
  275. package/skills/integration/integration-react-native/references/amplitude-quickstart.md +1845 -0
  276. package/skills/integration/integration-react-native/references/basic-integration-1.0-begin.md +43 -0
  277. package/skills/integration/integration-react-native/references/basic-integration-1.1-edit.md +35 -0
  278. package/skills/integration/integration-react-native/references/basic-integration-1.2-revise.md +23 -0
  279. package/skills/integration/integration-react-native/references/basic-integration-1.3-conclude.md +57 -0
  280. package/skills/integration/integration-react-native/references/react-native-sdk.md +2819 -0
  281. package/skills/integration/integration-react-react-router-6/SKILL.md +53 -0
  282. package/skills/integration/integration-react-react-router-6/references/EXAMPLE.md +570 -0
  283. package/skills/integration/integration-react-react-router-6/references/amplitude-quickstart.md +1845 -0
  284. package/skills/integration/integration-react-react-router-6/references/basic-integration-1.0-begin.md +43 -0
  285. package/skills/integration/integration-react-react-router-6/references/basic-integration-1.1-edit.md +35 -0
  286. package/skills/integration/integration-react-react-router-6/references/basic-integration-1.2-revise.md +23 -0
  287. package/skills/integration/integration-react-react-router-6/references/basic-integration-1.3-conclude.md +57 -0
  288. package/skills/integration/integration-react-react-router-6/references/browser-sdk-2.md +4680 -0
  289. package/skills/integration/integration-react-react-router-7-data/SKILL.md +53 -0
  290. package/skills/integration/integration-react-react-router-7-data/references/EXAMPLE.md +830 -0
  291. package/skills/integration/integration-react-react-router-7-data/references/amplitude-quickstart.md +1845 -0
  292. package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.0-begin.md +43 -0
  293. package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.1-edit.md +35 -0
  294. package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.2-revise.md +23 -0
  295. package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.3-conclude.md +57 -0
  296. package/skills/integration/integration-react-react-router-7-data/references/browser-sdk-2.md +4680 -0
  297. package/skills/integration/integration-react-react-router-7-declarative/SKILL.md +53 -0
  298. package/skills/integration/integration-react-react-router-7-declarative/references/EXAMPLE.md +609 -0
  299. package/skills/integration/integration-react-react-router-7-declarative/references/amplitude-quickstart.md +1845 -0
  300. package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.0-begin.md +43 -0
  301. package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.1-edit.md +35 -0
  302. package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.2-revise.md +23 -0
  303. package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.3-conclude.md +57 -0
  304. package/skills/integration/integration-react-react-router-7-declarative/references/browser-sdk-2.md +4680 -0
  305. package/skills/integration/integration-react-react-router-7-framework/SKILL.md +53 -0
  306. package/skills/integration/integration-react-react-router-7-framework/references/EXAMPLE.md +1081 -0
  307. package/skills/integration/integration-react-react-router-7-framework/references/amplitude-quickstart.md +1845 -0
  308. package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.0-begin.md +43 -0
  309. package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.1-edit.md +35 -0
  310. package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.2-revise.md +23 -0
  311. package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.3-conclude.md +57 -0
  312. package/skills/integration/integration-react-react-router-7-framework/references/browser-sdk-2.md +4680 -0
  313. package/skills/integration/integration-react-tanstack-router-code-based/SKILL.md +57 -0
  314. package/skills/integration/integration-react-tanstack-router-code-based/references/EXAMPLE.md +659 -0
  315. package/skills/integration/integration-react-tanstack-router-code-based/references/amplitude-quickstart.md +1845 -0
  316. package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.0-begin.md +43 -0
  317. package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.1-edit.md +35 -0
  318. package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.2-revise.md +23 -0
  319. package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.3-conclude.md +57 -0
  320. package/skills/integration/integration-react-tanstack-router-code-based/references/browser-sdk-2.md +4680 -0
  321. package/skills/integration/integration-react-tanstack-router-file-based/SKILL.md +57 -0
  322. package/skills/integration/integration-react-tanstack-router-file-based/references/EXAMPLE.md +777 -0
  323. package/skills/integration/integration-react-tanstack-router-file-based/references/amplitude-quickstart.md +1845 -0
  324. package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.0-begin.md +43 -0
  325. package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.1-edit.md +35 -0
  326. package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.2-revise.md +23 -0
  327. package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.3-conclude.md +57 -0
  328. package/skills/integration/integration-react-tanstack-router-file-based/references/browser-sdk-2.md +4680 -0
  329. package/skills/integration/integration-react-vite/SKILL.md +53 -0
  330. package/skills/integration/integration-react-vite/references/EXAMPLE.md +542 -0
  331. package/skills/integration/integration-react-vite/references/amplitude-quickstart.md +1845 -0
  332. package/skills/integration/integration-react-vite/references/basic-integration-1.0-begin.md +43 -0
  333. package/skills/integration/integration-react-vite/references/basic-integration-1.1-edit.md +35 -0
  334. package/skills/integration/integration-react-vite/references/basic-integration-1.2-revise.md +23 -0
  335. package/skills/integration/integration-react-vite/references/basic-integration-1.3-conclude.md +57 -0
  336. package/skills/integration/integration-react-vite/references/browser-sdk-2.md +4680 -0
  337. package/skills/integration/integration-ruby/SKILL.md +50 -0
  338. package/skills/integration/integration-ruby/references/EXAMPLE.md +420 -0
  339. package/skills/integration/integration-ruby/references/amplitude-quickstart.md +1845 -0
  340. package/skills/integration/integration-ruby/references/analytics.md +1778 -0
  341. package/skills/integration/integration-ruby/references/basic-integration-1.0-begin.md +43 -0
  342. package/skills/integration/integration-ruby/references/basic-integration-1.1-edit.md +35 -0
  343. package/skills/integration/integration-ruby/references/basic-integration-1.2-revise.md +23 -0
  344. package/skills/integration/integration-ruby/references/basic-integration-1.3-conclude.md +57 -0
  345. package/skills/integration/integration-ruby-on-rails/SKILL.md +55 -0
  346. package/skills/integration/integration-ruby-on-rails/references/EXAMPLE.md +1013 -0
  347. package/skills/integration/integration-ruby-on-rails/references/amplitude-quickstart.md +1845 -0
  348. package/skills/integration/integration-ruby-on-rails/references/analytics.md +1778 -0
  349. package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.0-begin.md +43 -0
  350. package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.1-edit.md +35 -0
  351. package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.2-revise.md +23 -0
  352. package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.3-conclude.md +57 -0
  353. package/skills/integration/integration-sveltekit/SKILL.md +47 -0
  354. package/skills/integration/integration-sveltekit/references/EXAMPLE.md +14121 -0
  355. package/skills/integration/integration-sveltekit/references/amplitude-quickstart.md +1845 -0
  356. package/skills/integration/integration-sveltekit/references/basic-integration-1.0-begin.md +43 -0
  357. package/skills/integration/integration-sveltekit/references/basic-integration-1.1-edit.md +35 -0
  358. package/skills/integration/integration-sveltekit/references/basic-integration-1.2-revise.md +23 -0
  359. package/skills/integration/integration-sveltekit/references/basic-integration-1.3-conclude.md +57 -0
  360. package/skills/integration/integration-sveltekit/references/browser-sdk-2.md +4680 -0
  361. package/skills/integration/integration-swift/SKILL.md +49 -0
  362. package/skills/integration/integration-swift/references/EXAMPLE.md +660 -0
  363. package/skills/integration/integration-swift/references/amplitude-quickstart.md +1845 -0
  364. package/skills/integration/integration-swift/references/analytics.md +1778 -0
  365. package/skills/integration/integration-swift/references/basic-integration-1.0-begin.md +43 -0
  366. package/skills/integration/integration-swift/references/basic-integration-1.1-edit.md +35 -0
  367. package/skills/integration/integration-swift/references/basic-integration-1.2-revise.md +23 -0
  368. package/skills/integration/integration-swift/references/basic-integration-1.3-conclude.md +57 -0
  369. package/skills/integration/integration-tanstack-start/SKILL.md +58 -0
  370. package/skills/integration/integration-tanstack-start/references/EXAMPLE.md +998 -0
  371. package/skills/integration/integration-tanstack-start/references/amplitude-quickstart.md +1845 -0
  372. package/skills/integration/integration-tanstack-start/references/basic-integration-1.0-begin.md +43 -0
  373. package/skills/integration/integration-tanstack-start/references/basic-integration-1.1-edit.md +35 -0
  374. package/skills/integration/integration-tanstack-start/references/basic-integration-1.2-revise.md +23 -0
  375. package/skills/integration/integration-tanstack-start/references/basic-integration-1.3-conclude.md +57 -0
  376. package/skills/integration/integration-tanstack-start/references/browser-sdk-2.md +4680 -0
  377. package/skills/integration/integration-vue-3/SKILL.md +46 -0
  378. package/skills/integration/integration-vue-3/references/EXAMPLE.md +846 -0
  379. package/skills/integration/integration-vue-3/references/amplitude-quickstart.md +1845 -0
  380. package/skills/integration/integration-vue-3/references/basic-integration-1.0-begin.md +43 -0
  381. package/skills/integration/integration-vue-3/references/basic-integration-1.1-edit.md +35 -0
  382. package/skills/integration/integration-vue-3/references/basic-integration-1.2-revise.md +23 -0
  383. package/skills/integration/integration-vue-3/references/basic-integration-1.3-conclude.md +57 -0
  384. package/skills/integration/integration-vue-3/references/browser-sdk-2.md +4680 -0
  385. package/skills/taxonomy/amplitude-quickstart-taxonomy-agent/SKILL.md +228 -0
  386. package/dist/src/ui/tui/components/TitleBar.d.ts +0 -8
  387. package/dist/src/ui/tui/components/TitleBar.js +0 -27
  388. package/dist/src/ui/tui/primitives/KagiSmallWebViewer.d.ts +0 -7
  389. package/dist/src/ui/tui/primitives/KagiSmallWebViewer.js +0 -101
  390. package/dist/src/utils/anthropic-status.d.ts +0 -17
  391. package/dist/src/utils/anthropic-status.js +0 -51
@@ -0,0 +1,1389 @@
1
+ # Amplitude FastAPI Example Project
2
+
3
+ Repository: https://github.com/amplitude/context-hub
4
+ Path: basics/fastapi
5
+
6
+ ---
7
+
8
+ ## README.md
9
+
10
+ # Amplitude FastAPI Example
11
+
12
+ A FastAPI application demonstrating Amplitude integration for analytics and event tracking.
13
+
14
+ ## Features
15
+
16
+ - User registration and authentication with cookie-based sessions
17
+ - SQLite database persistence with SQLAlchemy
18
+ - User identification and property tracking
19
+ - Custom event tracking
20
+
21
+ ## Quick Start
22
+
23
+ 1. Create and activate a virtual environment:
24
+ ```bash
25
+ python -m venv venv
26
+ source venv/bin/activate # On Windows: venv\Scripts\activate
27
+ ```
28
+
29
+ 2. Install dependencies:
30
+ ```bash
31
+ pip install -r requirements.txt
32
+ ```
33
+
34
+ 3. Copy the environment file and configure:
35
+ ```bash
36
+ cp .env.example .env
37
+ # Edit .env with your Amplitude API key
38
+ ```
39
+
40
+ 4. Run the application:
41
+ ```bash
42
+ python run.py
43
+ ```
44
+
45
+ 5. Open http://localhost:5002 and either:
46
+ - Login with default credentials: `admin@example.com` / `admin`
47
+ - Or click "Sign up here" to create a new account
48
+
49
+ ## Amplitude Integration Points
50
+
51
+ ### User Registration
52
+ New users are identified and tracked on signup:
53
+ ```python
54
+ client = get_amplitude_client()
55
+ if client:
56
+ identify_obj = Identify()
57
+ identify_obj.set('email', user.email)
58
+ identify_obj.set('is_staff', user.is_staff)
59
+ identify_obj.set('date_joined', user.date_joined.isoformat())
60
+ client.identify(identify_obj, {'user_id': user.email})
61
+
62
+ client.track(BaseEvent(
63
+ event_type='User Signed Up',
64
+ user_id=user.email,
65
+ event_properties={'signup_method': 'form'},
66
+ ))
67
+ ```
68
+
69
+ ### User Identification
70
+ Users are identified on login with their properties:
71
+ ```python
72
+ client = get_amplitude_client()
73
+ if client:
74
+ identify_obj = Identify()
75
+ identify_obj.set('email', user.email)
76
+ identify_obj.set('is_staff', user.is_staff)
77
+ client.identify(identify_obj, {'user_id': user.email})
78
+
79
+ client.track(BaseEvent(
80
+ event_type='User Logged In',
81
+ user_id=user.email,
82
+ event_properties={'login_method': 'password'},
83
+ ))
84
+ ```
85
+
86
+ ### Event Tracking
87
+ Custom events are tracked throughout the app:
88
+ ```python
89
+ client = get_amplitude_client()
90
+ if client:
91
+ client.track(BaseEvent(
92
+ event_type='Burrito Considered',
93
+ user_id=current_user.email,
94
+ event_properties={'total_considerations': new_count},
95
+ ))
96
+ ```
97
+
98
+ ## Project Structure
99
+
100
+ ```
101
+ basics/fastapi/
102
+ ├── app/
103
+ │ ├── __init__.py # Package marker
104
+ │ ├── config.py # Pydantic Settings configuration
105
+ │ ├── database.py # SQLAlchemy setup
106
+ │ ├── dependencies.py # FastAPI dependency injection
107
+ │ ├── main.py # Application factory and lifespan
108
+ │ ├── middleware.py # Amplitude client helper
109
+ │ ├── models.py # User model (SQLAlchemy)
110
+ │ ├── routers/
111
+ │ │ ├── __init__.py # Routers package
112
+ │ │ ├── main.py # Page routes (HTML)
113
+ │ │ └── api.py # API endpoints (JSON)
114
+ │ └── templates/ # Jinja2 templates
115
+ ├── .env.example
116
+ ├── .gitignore
117
+ ├── requirements.txt
118
+ ├── README.md
119
+ └── run.py # Entry point (uvicorn)
120
+ ```
121
+
122
+ ---
123
+
124
+ ## .env.example
125
+
126
+ ```example
127
+ AMPLITUDE_API_KEY=your_amplitude_api_key_here
128
+ SECRET_KEY=your-secret-key-here
129
+ DEBUG=True
130
+ AMPLITUDE_DISABLED=False
131
+
132
+ ```
133
+
134
+ ---
135
+
136
+ ## app/__init__.py
137
+
138
+ ```py
139
+ """FastAPI Amplitude example application."""
140
+
141
+ ```
142
+
143
+ ---
144
+
145
+ ## app/config.py
146
+
147
+ ```py
148
+ """FastAPI application configuration using Pydantic Settings."""
149
+
150
+ from functools import lru_cache
151
+
152
+ from pydantic_settings import BaseSettings, SettingsConfigDict
153
+
154
+
155
+ class Settings(BaseSettings):
156
+ """Application settings loaded from environment variables."""
157
+
158
+ model_config = SettingsConfigDict(
159
+ env_file=".env",
160
+ env_file_encoding="utf-8",
161
+ extra="ignore",
162
+ )
163
+
164
+ # Application
165
+ secret_key: str = "dev-secret-key-change-in-production"
166
+ debug: bool = True
167
+
168
+ # Database (SQLite like Flask example)
169
+ database_url: str = "sqlite:///./db.sqlite3"
170
+
171
+ # Amplitude
172
+ amplitude_api_key: str = ""
173
+ amplitude_disabled: bool = False
174
+
175
+
176
+ @lru_cache
177
+ def get_settings() -> Settings:
178
+ """Get cached settings instance."""
179
+ return Settings()
180
+
181
+ ```
182
+
183
+ ---
184
+
185
+ ## app/database.py
186
+
187
+ ```py
188
+ """Database configuration with SQLAlchemy."""
189
+
190
+ from sqlalchemy import create_engine
191
+ from sqlalchemy.orm import DeclarativeBase, sessionmaker
192
+
193
+ from app.config import get_settings
194
+
195
+ settings = get_settings()
196
+
197
+ engine = create_engine(
198
+ settings.database_url,
199
+ connect_args={"check_same_thread": False}, # Required for SQLite
200
+ )
201
+
202
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
203
+
204
+
205
+ class Base(DeclarativeBase):
206
+ """Base class for SQLAlchemy models."""
207
+
208
+ pass
209
+
210
+
211
+ def get_db():
212
+ """Dependency that provides a database session."""
213
+ db = SessionLocal()
214
+ try:
215
+ yield db
216
+ finally:
217
+ db.close()
218
+
219
+
220
+ def init_db():
221
+ """Create all database tables."""
222
+ Base.metadata.create_all(bind=engine)
223
+
224
+ ```
225
+
226
+ ---
227
+
228
+ ## app/dependencies.py
229
+
230
+ ```py
231
+ """Authentication dependencies for FastAPI."""
232
+
233
+ from typing import Annotated, Optional
234
+
235
+ from fastapi import Cookie, Depends, HTTPException, status
236
+ from itsdangerous import BadSignature, URLSafeSerializer
237
+ from sqlalchemy.orm import Session
238
+
239
+ from app.config import get_settings
240
+ from app.database import get_db
241
+ from app.models import User
242
+
243
+ settings = get_settings()
244
+ serializer = URLSafeSerializer(settings.secret_key)
245
+
246
+
247
+ def get_session_user_id(session_token: Annotated[Optional[str], Cookie()] = None) -> Optional[int]:
248
+ """Extract user ID from session cookie."""
249
+ if not session_token:
250
+ return None
251
+ try:
252
+ data = serializer.loads(session_token)
253
+ return data.get("user_id")
254
+ except BadSignature:
255
+ return None
256
+
257
+
258
+ def get_current_user(
259
+ db: Annotated[Session, Depends(get_db)],
260
+ user_id: Annotated[Optional[int], Depends(get_session_user_id)],
261
+ ) -> Optional[User]:
262
+ """Get the current authenticated user, or None if not authenticated."""
263
+ if user_id is None:
264
+ return None
265
+ return User.get_by_id(db, user_id)
266
+
267
+
268
+ def require_auth(
269
+ current_user: Annotated[Optional[User], Depends(get_current_user)],
270
+ ) -> User:
271
+ """Require authentication - raises 401 if not authenticated."""
272
+ if current_user is None:
273
+ raise HTTPException(
274
+ status_code=status.HTTP_401_UNAUTHORIZED,
275
+ detail="Authentication required",
276
+ )
277
+ return current_user
278
+
279
+
280
+ def create_session_token(user_id: int) -> str:
281
+ """Create a signed session token for the user."""
282
+ return serializer.dumps({"user_id": user_id})
283
+
284
+
285
+ # Type aliases for cleaner dependency injection
286
+ CurrentUser = Annotated[Optional[User], Depends(get_current_user)]
287
+ RequiredUser = Annotated[User, Depends(require_auth)]
288
+ DbSession = Annotated[Session, Depends(get_db)]
289
+
290
+ ```
291
+
292
+ ---
293
+
294
+ ## app/main.py
295
+
296
+ ```py
297
+ """FastAPI application with Amplitude integration."""
298
+
299
+ from contextlib import asynccontextmanager
300
+ from pathlib import Path
301
+
302
+ from fastapi import FastAPI, Request
303
+ from fastapi.responses import HTMLResponse, JSONResponse
304
+ from fastapi.templating import Jinja2Templates
305
+
306
+ from app.config import get_settings
307
+ from app.database import SessionLocal, init_db
308
+ from app.models import User
309
+ from app.routers import api, main
310
+
311
+ settings = get_settings()
312
+
313
+ # Setup templates
314
+ templates_dir = Path(__file__).parent / "templates"
315
+ templates = Jinja2Templates(directory=str(templates_dir))
316
+
317
+
318
+ @asynccontextmanager
319
+ async def lifespan(app: FastAPI):
320
+ """Application lifespan events for startup/shutdown."""
321
+ # Initialize database and seed default user
322
+ init_db()
323
+ db = SessionLocal()
324
+ try:
325
+ if not User.get_by_email(db, "admin@example.com"):
326
+ User.create_user(
327
+ db,
328
+ email="admin@example.com",
329
+ password="admin",
330
+ is_staff=True,
331
+ )
332
+ finally:
333
+ db.close()
334
+
335
+ yield
336
+
337
+
338
+ app = FastAPI(
339
+ title="Amplitude FastAPI Example",
340
+ description="Example application demonstrating Amplitude integration with FastAPI",
341
+ lifespan=lifespan,
342
+ )
343
+
344
+ # Include routers
345
+ app.include_router(main.router)
346
+ app.include_router(api.router, prefix="/api")
347
+
348
+
349
+ # Error handlers
350
+ @app.exception_handler(404)
351
+ async def not_found_handler(request: Request, exc):
352
+ """Handle 404 errors."""
353
+ if request.url.path.startswith("/api/"):
354
+ return JSONResponse({"error": "Not found"}, status_code=404)
355
+ return templates.TemplateResponse(
356
+ request, "errors/404.html", status_code=404
357
+ )
358
+
359
+
360
+ @app.exception_handler(500)
361
+ async def internal_error_handler(request: Request, exc):
362
+ """Handle 500 errors."""
363
+ if request.url.path.startswith("/api/"):
364
+ return JSONResponse({"error": "Internal server error"}, status_code=500)
365
+ return templates.TemplateResponse(
366
+ request, "errors/500.html", status_code=500
367
+ )
368
+
369
+ ```
370
+
371
+ ---
372
+
373
+ ## app/middleware.py
374
+
375
+ ```py
376
+ """Amplitude helper for FastAPI request tracking."""
377
+
378
+ from amplitude import Amplitude
379
+
380
+ from app.config import get_settings
381
+
382
+
383
+ def get_amplitude_client():
384
+ """Get the Amplitude client instance, or None if disabled."""
385
+ settings = get_settings()
386
+ if not settings.amplitude_api_key or settings.amplitude_disabled:
387
+ return None
388
+ return Amplitude(settings.amplitude_api_key)
389
+
390
+ ```
391
+
392
+ ---
393
+
394
+ ## app/models.py
395
+
396
+ ```py
397
+ """User model with SQLite persistence (similar to Flask example)."""
398
+
399
+ from datetime import datetime, timezone
400
+ from typing import Optional
401
+
402
+ from sqlalchemy import Boolean, DateTime, Integer, String
403
+ from sqlalchemy.orm import Mapped, Session, mapped_column
404
+ from werkzeug.security import check_password_hash, generate_password_hash
405
+
406
+ from app.database import Base
407
+
408
+
409
+ class User(Base):
410
+ """User model with SQLite persistence."""
411
+
412
+ __tablename__ = "users"
413
+
414
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
415
+ email: Mapped[str] = mapped_column(String(254), unique=True, nullable=False)
416
+ password_hash: Mapped[str] = mapped_column(String(256), nullable=False)
417
+ name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
418
+ is_staff: Mapped[bool] = mapped_column(Boolean, default=False)
419
+ is_active: Mapped[bool] = mapped_column(Boolean, default=True)
420
+ login_count: Mapped[int] = mapped_column(Integer, default=0)
421
+ date_joined: Mapped[datetime] = mapped_column(
422
+ DateTime, default=lambda: datetime.now(timezone.utc)
423
+ )
424
+
425
+ def set_password(self, password: str) -> None:
426
+ """Hash and set the user's password."""
427
+ self.password_hash = generate_password_hash(password, method="pbkdf2:sha256")
428
+
429
+ def check_password(self, password: str) -> bool:
430
+ """Verify the password against the hash."""
431
+ return check_password_hash(self.password_hash, password)
432
+
433
+ @classmethod
434
+ def create_user(
435
+ cls, db: Session, email: str, password: str, is_staff: bool = False
436
+ ) -> "User":
437
+ """Create and save a new user."""
438
+ user = cls(email=email, is_staff=is_staff)
439
+ # nosemgrep: python.django.security.audit.unvalidated-password.unvalidated-password
440
+ user.set_password(password)
441
+ db.add(user)
442
+ db.commit()
443
+ db.refresh(user)
444
+ return user
445
+
446
+ @classmethod
447
+ def get_by_id(cls, db: Session, user_id: int) -> Optional["User"]:
448
+ """Get user by ID."""
449
+ return db.query(cls).filter(cls.id == user_id).first()
450
+
451
+ @classmethod
452
+ def get_by_email(cls, db: Session, email: str) -> Optional["User"]:
453
+ """Get user by email."""
454
+ return db.query(cls).filter(cls.email == email).first()
455
+
456
+ @classmethod
457
+ def authenticate(cls, db: Session, email: str, password: str) -> Optional["User"]:
458
+ """Authenticate user with email and password."""
459
+ user = cls.get_by_email(db, email)
460
+ if user and user.check_password(password):
461
+ return user
462
+ return None
463
+
464
+ def record_login(self, db: Session) -> bool:
465
+ """Record a login and return whether this is the user's first login."""
466
+ is_first_login = self.login_count == 0
467
+ self.login_count += 1
468
+ db.commit()
469
+ return is_first_login
470
+
471
+ def update_profile(self, db: Session, name: Optional[str] = None) -> list:
472
+ """Update user profile and return list of changed fields."""
473
+ changed_fields = []
474
+ if name is not None and name != self.name:
475
+ self.name = name
476
+ changed_fields.append("name")
477
+ if changed_fields:
478
+ db.commit()
479
+ return changed_fields
480
+
481
+ def __repr__(self) -> str:
482
+ return f"<User {self.email}>"
483
+
484
+ ```
485
+
486
+ ---
487
+
488
+ ## app/routers/__init__.py
489
+
490
+ ```py
491
+ """FastAPI routers package."""
492
+
493
+ ```
494
+
495
+ ---
496
+
497
+ ## app/routers/api.py
498
+
499
+ ```py
500
+ """API endpoints demonstrating Amplitude integration patterns."""
501
+
502
+ from typing import Annotated
503
+
504
+ from amplitude import BaseEvent
505
+ from fastapi import APIRouter, Cookie, Form, Query
506
+ from fastapi.responses import JSONResponse
507
+
508
+ from app.dependencies import RequiredUser
509
+ from app.middleware import get_amplitude_client
510
+
511
+ router = APIRouter()
512
+
513
+ MAX_BURRITO_COUNT = 10000
514
+
515
+
516
+ @router.post("/burrito/consider")
517
+ async def consider_burrito(
518
+ current_user: RequiredUser,
519
+ burrito_count: Annotated[int, Cookie()] = 0,
520
+ ):
521
+ """Track burrito consideration event."""
522
+ safe_count = max(0, min(burrito_count, MAX_BURRITO_COUNT))
523
+ new_count = safe_count + 1
524
+
525
+ # Amplitude: Capture custom event
526
+ client = get_amplitude_client()
527
+ if client:
528
+ client.track(BaseEvent(
529
+ event_type="Burrito Considered",
530
+ user_id=current_user.email,
531
+ event_properties={"total_considerations": new_count},
532
+ ))
533
+
534
+ response = JSONResponse({"success": True, "count": new_count})
535
+ response.set_cookie(
536
+ key="burrito_count",
537
+ value=str(new_count),
538
+ httponly=True,
539
+ samesite="lax",
540
+ )
541
+ return response
542
+
543
+
544
+ @router.post("/test-error")
545
+ async def test_error(
546
+ current_user: RequiredUser,
547
+ capture_param: Annotated[str, Query(alias="capture")] = "true",
548
+ ):
549
+ """Test endpoint demonstrating manual error event capture in Amplitude."""
550
+ should_capture = capture_param.lower() == "true"
551
+
552
+ try:
553
+ raise Exception("Test exception from critical operation")
554
+ except Exception as e:
555
+ if should_capture:
556
+ client = get_amplitude_client()
557
+ if client:
558
+ client.track(BaseEvent(
559
+ event_type="Error Occurred",
560
+ user_id=current_user.email,
561
+ event_properties={
562
+ "error_message": str(e),
563
+ "error_type": type(e).__name__,
564
+ },
565
+ ))
566
+ return JSONResponse(
567
+ {
568
+ "error": "Operation failed",
569
+ "message": f"Error captured in Amplitude: {str(e)}",
570
+ },
571
+ status_code=500,
572
+ )
573
+ else:
574
+ return JSONResponse({"error": "Operation failed"}, status_code=500)
575
+
576
+
577
+ @router.post("/reports/activity")
578
+ async def generate_activity_report(
579
+ current_user: RequiredUser,
580
+ report_type: Annotated[str, Form()] = "summary",
581
+ ):
582
+ """Generate user activity report."""
583
+ valid_report_types = {"summary", "detailed", "export"}
584
+ safe_report_type = report_type if report_type in valid_report_types else "summary"
585
+
586
+ report_data = {
587
+ "user": current_user.email,
588
+ "date_joined": current_user.date_joined.isoformat(),
589
+ "login_count": current_user.login_count,
590
+ "is_staff": current_user.is_staff,
591
+ }
592
+
593
+ if safe_report_type == "detailed":
594
+ report_data["account_age_days"] = (
595
+ __import__("datetime").datetime.now(__import__("datetime").timezone.utc)
596
+ - current_user.date_joined
597
+ ).days
598
+
599
+ row_count = len(report_data)
600
+
601
+ # Amplitude: Track report generation
602
+ client = get_amplitude_client()
603
+ if client:
604
+ client.track(BaseEvent(
605
+ event_type="Report Generated",
606
+ user_id=current_user.email,
607
+ event_properties={
608
+ "report_type": safe_report_type,
609
+ "row_count": row_count,
610
+ },
611
+ ))
612
+
613
+ return JSONResponse(
614
+ {
615
+ "success": True,
616
+ "report_type": safe_report_type,
617
+ "row_count": row_count,
618
+ "data": report_data,
619
+ }
620
+ )
621
+
622
+ ```
623
+
624
+ ---
625
+
626
+ ## app/routers/main.py
627
+
628
+ ```py
629
+ """Main routes demonstrating Amplitude integration patterns."""
630
+
631
+ from pathlib import Path
632
+ from typing import Annotated
633
+
634
+ from amplitude import BaseEvent, Identify
635
+ from fastapi import APIRouter, Cookie, Depends, Form, Request
636
+ from fastapi.responses import HTMLResponse, RedirectResponse
637
+ from fastapi.templating import Jinja2Templates
638
+
639
+ from app.dependencies import (
640
+ CurrentUser,
641
+ DbSession,
642
+ RequiredUser,
643
+ create_session_token,
644
+ )
645
+ from app.middleware import get_amplitude_client
646
+ from app.models import User
647
+
648
+ router = APIRouter()
649
+
650
+ # Setup templates
651
+ templates_dir = Path(__file__).parent.parent / "templates"
652
+ templates = Jinja2Templates(directory=str(templates_dir))
653
+
654
+
655
+ @router.get("/", response_class=HTMLResponse)
656
+ async def home(request: Request, current_user: CurrentUser, db: DbSession):
657
+ """Home/login page."""
658
+ if current_user:
659
+ return RedirectResponse(url="/dashboard", status_code=302)
660
+
661
+ return templates.TemplateResponse(
662
+ request, "home.html", {"current_user": current_user}
663
+ )
664
+
665
+
666
+ @router.post("/", response_class=HTMLResponse)
667
+ async def login(
668
+ request: Request,
669
+ db: DbSession,
670
+ email: Annotated[str, Form()],
671
+ password: Annotated[str, Form()],
672
+ ):
673
+ """Handle login form submission."""
674
+ user = User.authenticate(db, email, password)
675
+
676
+ if user:
677
+ user.record_login(db)
678
+
679
+ # Amplitude: Identify user and capture login event
680
+ client = get_amplitude_client()
681
+ if client:
682
+ identify_obj = Identify()
683
+ identify_obj.set("email", user.email)
684
+ identify_obj.set("is_staff", user.is_staff)
685
+ client.identify(identify_obj, {"user_id": user.email})
686
+
687
+ client.track(BaseEvent(
688
+ event_type="User Logged In",
689
+ user_id=user.email,
690
+ event_properties={"login_method": "password"},
691
+ ))
692
+
693
+ # Create session and redirect
694
+ response = RedirectResponse(url="/dashboard", status_code=302)
695
+ response.set_cookie(
696
+ key="session_token",
697
+ value=create_session_token(user.id),
698
+ httponly=True,
699
+ samesite="lax",
700
+ )
701
+ return response
702
+
703
+ # Login failed
704
+ return templates.TemplateResponse(
705
+ request,
706
+ "home.html",
707
+ {"current_user": None, "error": "Invalid email or password"},
708
+ )
709
+
710
+
711
+ @router.get("/signup", response_class=HTMLResponse)
712
+ async def signup_page(request: Request, current_user: CurrentUser):
713
+ """User registration page."""
714
+ if current_user:
715
+ return RedirectResponse(url="/dashboard", status_code=302)
716
+
717
+ return templates.TemplateResponse(
718
+ request, "signup.html", {"current_user": current_user}
719
+ )
720
+
721
+
722
+ @router.post("/signup", response_class=HTMLResponse)
723
+ async def signup(
724
+ request: Request,
725
+ db: DbSession,
726
+ email: Annotated[str, Form()],
727
+ password: Annotated[str, Form()],
728
+ password_confirm: Annotated[str, Form()],
729
+ ):
730
+ """Handle signup form submission."""
731
+ error = None
732
+
733
+ if not email or not password:
734
+ error = "Email and password are required"
735
+ elif password != password_confirm:
736
+ error = "Passwords do not match"
737
+ elif User.get_by_email(db, email):
738
+ error = "Email already registered"
739
+
740
+ if error:
741
+ return templates.TemplateResponse(
742
+ request, "signup.html", {"current_user": None, "error": error}
743
+ )
744
+
745
+ # Create new user
746
+ user = User.create_user(db, email=email, password=password, is_staff=False)
747
+
748
+ # Amplitude: Identify new user and capture signup event
749
+ client = get_amplitude_client()
750
+ if client:
751
+ identify_obj = Identify()
752
+ identify_obj.set("email", user.email)
753
+ identify_obj.set("is_staff", user.is_staff)
754
+ identify_obj.set("date_joined", user.date_joined.isoformat())
755
+ client.identify(identify_obj, {"user_id": user.email})
756
+
757
+ client.track(BaseEvent(
758
+ event_type="User Signed Up",
759
+ user_id=user.email,
760
+ event_properties={"signup_method": "form"},
761
+ ))
762
+
763
+ # Create session and redirect
764
+ response = RedirectResponse(url="/dashboard", status_code=302)
765
+ response.set_cookie(
766
+ key="session_token",
767
+ value=create_session_token(user.id),
768
+ httponly=True,
769
+ samesite="lax",
770
+ )
771
+ return response
772
+
773
+
774
+ @router.get("/logout")
775
+ async def logout(current_user: RequiredUser):
776
+ """Logout and capture event."""
777
+ # Amplitude: Capture logout event
778
+ client = get_amplitude_client()
779
+ if client:
780
+ client.track(BaseEvent(
781
+ event_type="User Logged Out",
782
+ user_id=current_user.email,
783
+ ))
784
+
785
+ response = RedirectResponse(url="/", status_code=302)
786
+ response.delete_cookie(key="session_token")
787
+ return response
788
+
789
+
790
+ @router.get("/dashboard", response_class=HTMLResponse)
791
+ async def dashboard(
792
+ request: Request,
793
+ current_user: RequiredUser,
794
+ ):
795
+ """Dashboard page."""
796
+ # Amplitude: Capture dashboard view
797
+ client = get_amplitude_client()
798
+ if client:
799
+ client.track(BaseEvent(
800
+ event_type="Dashboard Viewed",
801
+ user_id=current_user.email,
802
+ event_properties={"is_staff": current_user.is_staff},
803
+ ))
804
+
805
+ # TODO: Use Amplitude Experiment for feature flags
806
+
807
+ return templates.TemplateResponse(
808
+ request,
809
+ "dashboard.html",
810
+ {"current_user": current_user},
811
+ )
812
+
813
+
814
+ @router.get("/burrito", response_class=HTMLResponse)
815
+ async def burrito(
816
+ request: Request,
817
+ current_user: RequiredUser,
818
+ burrito_count: Annotated[int, Cookie()] = 0,
819
+ ):
820
+ """Burrito consideration tracker page."""
821
+ return templates.TemplateResponse(
822
+ request,
823
+ "burrito.html",
824
+ {"current_user": current_user, "burrito_count": burrito_count},
825
+ )
826
+
827
+
828
+ @router.get("/profile", response_class=HTMLResponse)
829
+ async def profile(request: Request, current_user: RequiredUser):
830
+ """User profile page."""
831
+ # Amplitude: Capture profile view
832
+ client = get_amplitude_client()
833
+ if client:
834
+ client.track(BaseEvent(
835
+ event_type="Profile Viewed",
836
+ user_id=current_user.email,
837
+ ))
838
+
839
+ return templates.TemplateResponse(
840
+ request, "profile.html", {"current_user": current_user}
841
+ )
842
+
843
+ ```
844
+
845
+ ---
846
+
847
+ ## app/templates/base.html
848
+
849
+ ```html
850
+ <!DOCTYPE html>
851
+ <html lang="en">
852
+ <head>
853
+ <meta charset="UTF-8">
854
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
855
+ <title>{% block title %}Amplitude FastAPI Example{% endblock %}</title>
856
+ <style>
857
+ * {
858
+ box-sizing: border-box;
859
+ margin: 0;
860
+ padding: 0;
861
+ }
862
+ body {
863
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
864
+ line-height: 1.6;
865
+ background-color: #f5f5f5;
866
+ color: #333;
867
+ }
868
+ .container {
869
+ max-width: 800px;
870
+ margin: 0 auto;
871
+ padding: 20px;
872
+ }
873
+ nav {
874
+ background: #1d4ed8;
875
+ padding: 15px 20px;
876
+ margin-bottom: 30px;
877
+ }
878
+ nav a {
879
+ color: white;
880
+ text-decoration: none;
881
+ margin-right: 20px;
882
+ }
883
+ nav a:hover {
884
+ text-decoration: underline;
885
+ }
886
+ .card {
887
+ background: white;
888
+ border-radius: 8px;
889
+ padding: 20px;
890
+ margin-bottom: 20px;
891
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
892
+ }
893
+ h1, h2, h3 {
894
+ margin-bottom: 15px;
895
+ color: #1d4ed8;
896
+ }
897
+ button, .btn {
898
+ background: #1d4ed8;
899
+ color: white;
900
+ border: none;
901
+ padding: 10px 20px;
902
+ border-radius: 5px;
903
+ cursor: pointer;
904
+ font-size: 14px;
905
+ display: inline-block;
906
+ text-decoration: none;
907
+ }
908
+ button:hover, .btn:hover {
909
+ background: #1e40af;
910
+ }
911
+ button.danger {
912
+ background: #dc2626;
913
+ }
914
+ button.danger:hover {
915
+ background: #b91c1c;
916
+ }
917
+ input {
918
+ width: 100%;
919
+ padding: 10px;
920
+ margin-bottom: 15px;
921
+ border: 1px solid #ddd;
922
+ border-radius: 5px;
923
+ font-size: 14px;
924
+ }
925
+ .messages {
926
+ margin-bottom: 20px;
927
+ }
928
+ .message {
929
+ padding: 10px 15px;
930
+ border-radius: 5px;
931
+ margin-bottom: 10px;
932
+ }
933
+ .message.error {
934
+ background: #fee2e2;
935
+ color: #dc2626;
936
+ }
937
+ .message.success {
938
+ background: #d1fae5;
939
+ color: #059669;
940
+ }
941
+ .feature-flag {
942
+ background: #fef3c7;
943
+ border: 2px dashed #f59e0b;
944
+ padding: 15px;
945
+ border-radius: 8px;
946
+ margin: 20px 0;
947
+ }
948
+ code {
949
+ background: #f3f4f6;
950
+ padding: 2px 6px;
951
+ border-radius: 3px;
952
+ font-family: monospace;
953
+ }
954
+ pre {
955
+ background: #1e293b;
956
+ color: #e2e8f0;
957
+ padding: 16px;
958
+ border-radius: 8px;
959
+ overflow-x: auto;
960
+ font-size: 13px;
961
+ }
962
+ .count {
963
+ font-size: 48px;
964
+ font-weight: bold;
965
+ color: #1d4ed8;
966
+ text-align: center;
967
+ padding: 20px;
968
+ }
969
+ table {
970
+ width: 100%;
971
+ border-collapse: collapse;
972
+ margin: 16px 0;
973
+ }
974
+ th, td {
975
+ padding: 12px;
976
+ text-align: left;
977
+ border-bottom: 1px solid #eee;
978
+ }
979
+ th {
980
+ background: #f8fafc;
981
+ font-weight: 600;
982
+ }
983
+ </style>
984
+ </head>
985
+ <body>
986
+ {% if current_user %}
987
+ <nav>
988
+ <a href="/dashboard">Dashboard</a>
989
+ <a href="/burrito">Burrito</a>
990
+ <a href="/profile">Profile</a>
991
+ <a href="/logout" style="float: right;">Logout ({{ current_user.email }})</a>
992
+ </nav>
993
+ {% endif %}
994
+
995
+ <div class="container">
996
+ {% if error %}
997
+ <div class="messages">
998
+ <div class="message error">{{ error }}</div>
999
+ </div>
1000
+ {% endif %}
1001
+ {% if success %}
1002
+ <div class="messages">
1003
+ <div class="message success">{{ success }}</div>
1004
+ </div>
1005
+ {% endif %}
1006
+
1007
+ {% block content %}{% endblock %}
1008
+ </div>
1009
+
1010
+ {% block scripts %}{% endblock %}
1011
+ </body>
1012
+ </html>
1013
+
1014
+ ```
1015
+
1016
+ ---
1017
+
1018
+ ## app/templates/burrito.html
1019
+
1020
+ ```html
1021
+ {% extends "base.html" %}
1022
+
1023
+ {% block title %}Burrito - Amplitude FastAPI Example{% endblock %}
1024
+
1025
+ {% block content %}
1026
+ <div class="card">
1027
+ <h1>Burrito Consideration Tracker</h1>
1028
+ <p>This page demonstrates custom event tracking with Amplitude.</p>
1029
+
1030
+ <div class="count" id="burrito-count">{{ burrito_count }}</div>
1031
+ <p style="text-align: center; color: #666;">Times you've considered a burrito</p>
1032
+
1033
+ <div style="text-align: center; margin-top: 20px;">
1034
+ <button onclick="considerBurrito()">Consider a Burrito</button>
1035
+ </div>
1036
+ </div>
1037
+
1038
+ <div class="card">
1039
+ <h3>Code Example</h3>
1040
+ <pre>
1041
+ # API endpoint captures the event
1042
+ client = get_amplitude_client()
1043
+ if client:
1044
+ client.track(BaseEvent(
1045
+ event_type='Burrito Considered',
1046
+ user_id=current_user.email,
1047
+ event_properties={'total_considerations': new_count},
1048
+ ))</pre>
1049
+ </div>
1050
+ {% endblock %}
1051
+
1052
+ {% block scripts %}
1053
+ <script>
1054
+ async function considerBurrito() {
1055
+ try {
1056
+ const response = await fetch('/api/burrito/consider', {
1057
+ method: 'POST',
1058
+ headers: {
1059
+ 'Content-Type': 'application/json'
1060
+ }
1061
+ });
1062
+ const data = await response.json();
1063
+ if (data.success) {
1064
+ document.getElementById('burrito-count').textContent = data.count;
1065
+ }
1066
+ } catch (error) {
1067
+ console.error('Error:', error);
1068
+ }
1069
+ }
1070
+ </script>
1071
+ {% endblock %}
1072
+
1073
+ ```
1074
+
1075
+ ---
1076
+
1077
+ ## app/templates/dashboard.html
1078
+
1079
+ ```html
1080
+ {% extends "base.html" %}
1081
+
1082
+ {% block title %}Dashboard - Amplitude FastAPI Example{% endblock %}
1083
+
1084
+ {% block content %}
1085
+ <div class="card">
1086
+ <h1>Dashboard</h1>
1087
+ <p>Welcome back, {{ current_user.email }}!</p>
1088
+ </div>
1089
+
1090
+ <div class="card">
1091
+ <h2>Amplitude Event Tracking</h2>
1092
+ <p>This page is tracked with Amplitude on every visit.</p>
1093
+
1094
+ <h3 style="margin-top: 20px;">Code Example</h3>
1095
+ <pre>
1096
+ # Track dashboard view
1097
+ client = get_amplitude_client()
1098
+ if client:
1099
+ client.track(BaseEvent(
1100
+ event_type='Dashboard Viewed',
1101
+ user_id=current_user.email,
1102
+ event_properties={'is_staff': current_user.is_staff},
1103
+ ))
1104
+
1105
+ # TODO: Use Amplitude Experiment for feature flags</pre>
1106
+ </div>
1107
+ {% endblock %}
1108
+
1109
+ ```
1110
+
1111
+ ---
1112
+
1113
+ ## app/templates/errors/404.html
1114
+
1115
+ ```html
1116
+ {% extends "base.html" %}
1117
+
1118
+ {% block title %}Page Not Found - Amplitude FastAPI Example{% endblock %}
1119
+
1120
+ {% block content %}
1121
+ <div class="card">
1122
+ <h1>404 - Page Not Found</h1>
1123
+ <p>The page you're looking for doesn't exist.</p>
1124
+ <a href="/" class="btn">Go Home</a>
1125
+ </div>
1126
+ {% endblock %}
1127
+
1128
+ ```
1129
+
1130
+ ---
1131
+
1132
+ ## app/templates/errors/500.html
1133
+
1134
+ ```html
1135
+ {% extends "base.html" %}
1136
+
1137
+ {% block title %}Server Error - Amplitude FastAPI Example{% endblock %}
1138
+
1139
+ {% block content %}
1140
+ <div class="card">
1141
+ <h1>500 - Internal Server Error</h1>
1142
+ <p>Something went wrong on our end. Please try again later.</p>
1143
+ <a href="/" class="btn">Go Home</a>
1144
+ </div>
1145
+ {% endblock %}
1146
+
1147
+ ```
1148
+
1149
+ ---
1150
+
1151
+ ## app/templates/home.html
1152
+
1153
+ ```html
1154
+ {% extends "base.html" %}
1155
+
1156
+ {% block title %}Login - Amplitude FastAPI Example{% endblock %}
1157
+
1158
+ {% block content %}
1159
+ <div class="card">
1160
+ <h1>Welcome to Amplitude FastAPI Example</h1>
1161
+ <p>This example demonstrates how to integrate Amplitude with a FastAPI application.</p>
1162
+
1163
+ <form method="POST">
1164
+ <label for="email">Email</label>
1165
+ <input type="email" id="email" name="email" required>
1166
+
1167
+ <label for="password">Password</label>
1168
+ <input type="password" id="password" name="password" required>
1169
+
1170
+ <button type="submit">Login</button>
1171
+ </form>
1172
+
1173
+ <p style="margin-top: 16px; font-size: 14px; color: #666;">
1174
+ Don't have an account? <a href="/signup">Sign up here</a>
1175
+ </p>
1176
+ <p style="font-size: 14px; color: #666;">
1177
+ <strong>Tip:</strong> Default credentials are admin@example.com/admin
1178
+ </p>
1179
+ </div>
1180
+
1181
+ <div class="card">
1182
+ <h2>Features Demonstrated</h2>
1183
+ <ul style="margin-left: 20px; color: #666;">
1184
+ <li>User registration and identification</li>
1185
+ <li>Event tracking</li>
1186
+ <li>User properties</li>
1187
+ </ul>
1188
+ </div>
1189
+ {% endblock %}
1190
+
1191
+ ```
1192
+
1193
+ ---
1194
+
1195
+ ## app/templates/profile.html
1196
+
1197
+ ```html
1198
+ {% extends "base.html" %}
1199
+
1200
+ {% block title %}Profile - Amplitude FastAPI Example{% endblock %}
1201
+
1202
+ {% block content %}
1203
+ <div class="card">
1204
+ <h1>Your Profile</h1>
1205
+ <p>This page demonstrates profile updates and report generation with Amplitude.</p>
1206
+
1207
+ {% if success %}
1208
+ <div class="message success">{{ success }}</div>
1209
+ {% endif %}
1210
+
1211
+ <form method="POST" action="/profile">
1212
+ <table>
1213
+ <tr>
1214
+ <th>Email</th>
1215
+ <td>{{ current_user.email }}</td>
1216
+ </tr>
1217
+ <tr>
1218
+ <th>Name</th>
1219
+ <td>
1220
+ <input type="text" name="name" value="{{ current_user.name or '' }}" placeholder="Enter your name">
1221
+ </td>
1222
+ </tr>
1223
+ <tr>
1224
+ <th>Date Joined</th>
1225
+ <td>{{ current_user.date_joined.strftime('%Y-%m-%d %H:%M') }}</td>
1226
+ </tr>
1227
+ <tr>
1228
+ <th>Login Count</th>
1229
+ <td>{{ current_user.login_count }}</td>
1230
+ </tr>
1231
+ <tr>
1232
+ <th>Staff Status</th>
1233
+ <td>{{ 'Yes' if current_user.is_staff else 'No' }}</td>
1234
+ </tr>
1235
+ </table>
1236
+ <button type="submit">Update Profile</button>
1237
+ </form>
1238
+ </div>
1239
+
1240
+ <div class="card">
1241
+ <h2>Activity Reports</h2>
1242
+ <p>Generate a report of your account activity:</p>
1243
+
1244
+ <div style="margin: 20px 0;">
1245
+ <button onclick="generateReport('summary')">Summary Report</button>
1246
+ <button onclick="generateReport('detailed')">Detailed Report</button>
1247
+ </div>
1248
+
1249
+ <div id="report-result" style="display: none;" class="message"></div>
1250
+ </div>
1251
+
1252
+ <div class="card">
1253
+ <h3>Code Example</h3>
1254
+ <pre>
1255
+ # Track profile view
1256
+ client = get_amplitude_client()
1257
+ if client:
1258
+ client.track(BaseEvent(
1259
+ event_type='Profile Viewed',
1260
+ user_id=current_user.email,
1261
+ ))</pre>
1262
+ </div>
1263
+ {% endblock %}
1264
+
1265
+ {% block scripts %}
1266
+ <script>
1267
+ async function generateReport(reportType) {
1268
+ const resultDiv = document.getElementById('report-result');
1269
+ try {
1270
+ const formData = new FormData();
1271
+ formData.append('report_type', reportType);
1272
+
1273
+ const response = await fetch('/api/reports/activity', {
1274
+ method: 'POST',
1275
+ body: formData
1276
+ });
1277
+ const data = await response.json();
1278
+
1279
+ resultDiv.style.display = 'block';
1280
+ resultDiv.className = 'message success';
1281
+ resultDiv.innerHTML = '<strong>' + data.report_type + ' report generated</strong> (' + data.row_count + ' rows)<br><pre>' + JSON.stringify(data.data, null, 2) + '</pre>';
1282
+ } catch (error) {
1283
+ console.error('Error:', error);
1284
+ resultDiv.style.display = 'block';
1285
+ resultDiv.className = 'message error';
1286
+ resultDiv.textContent = 'Request failed: ' + error.message;
1287
+ }
1288
+ }
1289
+ </script>
1290
+ {% endblock %}
1291
+
1292
+ ```
1293
+
1294
+ ---
1295
+
1296
+ ## app/templates/signup.html
1297
+
1298
+ ```html
1299
+ {% extends "base.html" %}
1300
+
1301
+ {% block title %}Sign Up - Amplitude FastAPI Example{% endblock %}
1302
+
1303
+ {% block content %}
1304
+ <div class="card">
1305
+ <h1>Create an Account</h1>
1306
+ <p>Sign up to explore the Amplitude FastAPI integration example.</p>
1307
+
1308
+ <form method="POST">
1309
+ <label for="email">Email *</label>
1310
+ <input type="email" id="email" name="email" required>
1311
+
1312
+ <label for="password">Password *</label>
1313
+ <input type="password" id="password" name="password" required>
1314
+
1315
+ <label for="password_confirm">Confirm Password *</label>
1316
+ <input type="password" id="password_confirm" name="password_confirm" required>
1317
+
1318
+ <button type="submit">Sign Up</button>
1319
+ </form>
1320
+
1321
+ <p style="margin-top: 16px; font-size: 14px; color: #666;">
1322
+ Already have an account? <a href="/">Login here</a>
1323
+ </p>
1324
+ </div>
1325
+
1326
+ <div class="card">
1327
+ <h2>Amplitude Integration</h2>
1328
+ <p>When you sign up, the following Amplitude events are captured:</p>
1329
+ <ul style="margin-left: 20px; color: #666;">
1330
+ <li><code>client.identify()</code> - Sets user properties (email, is_staff, date_joined)</li>
1331
+ <li><code>User Signed Up</code> event - Tracks the signup action</li>
1332
+ </ul>
1333
+
1334
+ <h3 style="margin-top: 20px;">Code Example</h3>
1335
+ <pre>
1336
+ # After creating the user
1337
+ client = get_amplitude_client()
1338
+ if client:
1339
+ identify_obj = Identify()
1340
+ identify_obj.set('email', user.email)
1341
+ identify_obj.set('is_staff', user.is_staff)
1342
+ identify_obj.set('date_joined', user.date_joined.isoformat())
1343
+ client.identify(identify_obj, {'user_id': user.email})
1344
+
1345
+ client.track(BaseEvent(
1346
+ event_type='User Signed Up',
1347
+ user_id=user.email,
1348
+ event_properties={'signup_method': 'form'},
1349
+ ))</pre>
1350
+ </div>
1351
+ {% endblock %}
1352
+
1353
+ ```
1354
+
1355
+ ---
1356
+
1357
+ ## requirements.txt
1358
+
1359
+ ```txt
1360
+ fastapi>=0.109.0
1361
+ uvicorn>=0.27.0
1362
+ sqlalchemy>=2.0.0
1363
+ python-dotenv>=1.0.0
1364
+ amplitude-analytics>=1.0.0
1365
+ pydantic>=2.0.0
1366
+ pydantic-settings>=2.0.0
1367
+ jinja2>=3.0.0
1368
+ python-multipart>=0.0.9
1369
+ werkzeug>=3.0.0
1370
+ itsdangerous>=2.0.0
1371
+
1372
+ ```
1373
+
1374
+ ---
1375
+
1376
+ ## run.py
1377
+
1378
+ ```py
1379
+ """Development server entry point."""
1380
+
1381
+ import uvicorn
1382
+
1383
+ if __name__ == "__main__":
1384
+ uvicorn.run("app.main:app", host="0.0.0.0", port=5002, reload=True)
1385
+
1386
+ ```
1387
+
1388
+ ---
1389
+