├── run_frontend.sh
├── run_backend.sh
├── .env.example
├── backend
└── python
│ ├── start.sh
│ ├── README.md
│ ├── pyproject.toml
│ ├── Dockerfile
│ ├── main.py
│ └── poetry.lock
├── .vscode
└── settings.json
├── .gitignore
├── fixenv.sh
├── frontend
├── public
│ ├── favicon.ico
│ └── vercel.svg
├── next.config.js
├── next-env.d.ts
├── styles
│ ├── globals.css
│ └── Home.module.css
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── hello.ts
│ └── index.tsx
├── components
│ ├── Card.tsx
│ ├── dashboard
│ │ ├── VitalLogo.tsx
│ │ ├── HorizontalBarChart.tsx
│ │ ├── InfoIcon.tsx
│ │ ├── DetailedLineGraph.tsx
│ │ ├── ActivityGraph.tsx
│ │ ├── SleepGraph.tsx
│ │ ├── customRadio.tsx
│ │ ├── WorkoutPanel.tsx
│ │ ├── ActivityPanel.tsx
│ │ ├── SleepPanel.tsx
│ │ └── LineGraph.tsx
│ ├── LinkButton.tsx
│ └── CreateUserVital.tsx
├── tsconfig.json
├── package.json
├── lib
│ ├── client.tsx
│ └── utils.tsx
├── README.md
├── Dockerfile
└── models
│ └── index.tsx
├── docker-compose.yml
└── README.md
/run_frontend.sh:
--------------------------------------------------------------------------------
1 | cd frontend/ && yarn dev
--------------------------------------------------------------------------------
/run_backend.sh:
--------------------------------------------------------------------------------
1 | cd backend/python/ && ./start.sh
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | VITAL_API_KEY=
2 | VITAL_ENV=
3 | VITAL_REGION=
--------------------------------------------------------------------------------
/backend/python/start.sh:
--------------------------------------------------------------------------------
1 | uvicorn main:app --reload --port=8000
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.formatting.provider": "black"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | __pycache__
3 | .env
4 | .DS_Store
5 | .next
6 | .env.local
7 | /**/*.env.local
--------------------------------------------------------------------------------
/fixenv.sh:
--------------------------------------------------------------------------------
1 | cp .env ./backend/python/ && cat .env | sed -e s/VITAL_/NEXT_PUBLIC_VITAL_/g > ./frontend/.env.local
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryVital/quickstart/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/backend/python/README.md:
--------------------------------------------------------------------------------
1 | # Python quickstart backend written in python
2 |
3 | ```
4 | uvicorn main:app --reload --port=8000
5 | ```
6 |
--------------------------------------------------------------------------------
/frontend/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | output: 'standalone'
5 | }
6 |
7 | module.exports = nextConfig
8 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.9"
2 | services:
3 | backend-python:
4 | build: ./backend/python
5 | ports:
6 | - "8000:8000"
7 | frontend:
8 | build: "./frontend"
9 | ports:
10 | - "3000:3000"
--------------------------------------------------------------------------------
/frontend/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/frontend/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import type { AppProps } from "next/app";
3 | import { ChakraProvider } from "@chakra-ui/react";
4 |
5 | function MyApp({ Component, pageProps }: AppProps) {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default MyApp;
14 |
--------------------------------------------------------------------------------
/frontend/components/Card.tsx:
--------------------------------------------------------------------------------
1 | import { VStack } from "@chakra-ui/react";
2 |
3 | export const Card = ({ children }: { children: any }) => {
4 | return (
5 |
12 | {children}
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next'
3 |
4 | type Data = {
5 | name: string
6 | }
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: 'John Doe' })
13 | }
14 |
--------------------------------------------------------------------------------
/backend/python/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "quickstart"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Maitham "]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.9"
9 | vital = "^1.1.22"
10 | fastapi = "^0.77.1"
11 | uvicorn = "^0.17.6"
12 | python-dotenv = "^0.20.0"
13 |
14 | [tool.poetry.dev-dependencies]
15 |
16 | [tool.poetry.group.dev.dependencies]
17 | cffi = "1.17.1"
18 |
19 | [build-system]
20 | requires = ["poetry-core>=1.0.0"]
21 | build-backend = "poetry.core.masonry.api"
22 |
--------------------------------------------------------------------------------
/backend/python/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the official Python image.
2 | # https://hub.docker.com/_/python
3 | FROM python:3.10
4 |
5 | # Copy local code to the container image.
6 | ENV APP_HOME /app
7 | WORKDIR /app
8 |
9 | RUN pip install 'poetry==1.3.2'
10 | COPY . .
11 | COPY .env .env
12 |
13 | RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
14 | RUN python -m pip install --upgrade pip
15 |
16 | RUN pip install -r requirements.txt
17 |
18 | EXPOSE 8000
19 | CMD exec uvicorn main:app --host 0.0.0.0 --port 8000
20 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vital-frontend-quickstart",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@chakra-ui/react": "^2.2.0",
13 | "@emotion/react": "^11.9.3",
14 | "@emotion/styled": "^11.9.3",
15 | "@tryvital/vital-link": "^0.1.7",
16 | "framer-motion": "^6",
17 | "moment": "^2.29.3",
18 | "next": "12.2.0",
19 | "react": "18.2.0",
20 | "react-dom": "18.2.0",
21 | "react-icons": "^4.4.0",
22 | "recharts": "^2.1.12",
23 | "swr": "^1.3.0"
24 | },
25 | "devDependencies": {
26 | "@types/node": "17.0.32",
27 | "@types/react": "18.0.9",
28 | "@types/react-dom": "18.0.4",
29 | "eslint": "8.15.0",
30 | "eslint-config-next": "12.1.6",
31 | "typescript": "4.6.4"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/VitalLogo.tsx:
--------------------------------------------------------------------------------
1 | import { chakra, HTMLChakraProps, useToken } from '@chakra-ui/react'
2 |
3 | export const VitalLogo = (
4 | props: HTMLChakraProps<'svg'> & { iconColor?: string }
5 | ) => {
6 | const { iconColor = 'black', ...rest } = props
7 | const color = useToken('colors', iconColor)
8 |
9 | return (
10 |
17 |
24 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vital quickstart
2 |
3 | To get started with Vital quickstart, start with reading our [Docs](https://docs.tryvital.io/welcome/quickstart)
4 |
5 |
6 | ### Step 1 - Obtain and set Credentials
7 |
8 | The first step is to obtain your credentials from the Vital Dashboard.
9 |
10 | Once you have done this:
11 |
12 | ```
13 | # Copy the .env.example file:
14 | cp .env.example .env
15 |
16 | # Fill all the env variables in the .env file
17 | VITAL_API_KEY=..
18 | VITAL_ENV=sandbox
19 | VITAL_REGION=us
20 | ```
21 |
22 |
23 | ### Step 2a - Run using Docker
24 |
25 | ```
26 | # Run fixenv.sh this copies the environment variables into the correct locations
27 | ./fixenv.sh
28 |
29 | # Run Docker
30 | docker compose up --build
31 | ```
32 |
33 | ### Step 2b - Run manually
34 |
35 |
36 | ```
37 | # Run fixenv.sh
38 | ./fixenv.sh
39 |
40 | cd backend/python
41 |
42 | # Note: must use python 3
43 | # For virtualenv users:
44 | # virtualenv venv
45 | # source venv/bin/activate
46 | poetry shell
47 | poetry install
48 |
49 | # While still in the shell, start the backend app
50 | cd ../../ && ./run_backend.sh
51 | ```
52 |
53 | To run the frontend:
54 |
55 | ```
56 | # Install dependencies
57 | cd quickstart/frontend
58 | npm install
59 |
60 | # Start the frontend app
61 | npm run dev
62 |
63 | # Go to http://localhost:3000
64 | ```
65 |
66 | ### Step 3
67 |
68 | Head to localhost:3000 and begin interacting with the frontend
69 | Head to localhost:8000 to begin interacting with the backend.
70 |
--------------------------------------------------------------------------------
/frontend/lib/client.tsx:
--------------------------------------------------------------------------------
1 | const API_URL = "http://0.0.0.0:8000";
2 |
3 | const BACKEND_IS_PYTHON = true;
4 | const URL_PREFIX = BACKEND_IS_PYTHON ? API_URL : "/api";
5 |
6 | export const fetcher = (url: string) =>
7 | fetch(`${URL_PREFIX}${url}`).then((res) => res.json());
8 |
9 | export const fetchSummaryData = (
10 | data_type: string,
11 | userID: string,
12 | start_date: string,
13 | end_date: string,
14 | key: string
15 | ) =>
16 | fetch(
17 | `${URL_PREFIX}/summary/${data_type}/${userID}?start_date=${start_date}&end_date=${end_date}`
18 | )
19 | .then((res) => res.json())
20 | .then((data) => data[key]);
21 |
22 | export class Client {
23 | constructor() {}
24 |
25 | getTokenFromBackend = async (userID: string) => {
26 | const data = await this._fetch("GET", `/token/${userID}`);
27 | return data;
28 | };
29 |
30 | createUser = async (client_user_id: string) => {
31 | const data = await this._fetch("POST", `/user/`, { client_user_id });
32 | return data;
33 | };
34 |
35 | getUsers = async () => {
36 | const data = await this._fetch("GET", `/users/`);
37 | return data;
38 | };
39 |
40 | _fetch = async (
41 | method: string,
42 | resource: string,
43 | body?: Record
44 | ) => {
45 | const resp = await fetch(`${API_URL}${resource}`, {
46 | method: method,
47 | body: body ? JSON.stringify(body) : null,
48 | headers: { "content-type": "application/json" },
49 | });
50 | const data = await resp.json();
51 | return data;
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/HorizontalBarChart.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | BarChart,
4 | Bar,
5 | Cell,
6 | XAxis,
7 | YAxis,
8 | Tooltip,
9 | ResponsiveContainer,
10 | } from "recharts";
11 |
12 | const COLORS = ["#F6BDC0", "#F1959B", "#F07470", "#EA4C46", "#DC1C13"];
13 |
14 | interface HorizontalBarChartProps {
15 | data: any;
16 | reversed?: boolean;
17 | }
18 |
19 | export const HorizontalBarChart: React.FunctionComponent<
20 | HorizontalBarChartProps
21 | > = ({ data, reversed }) => {
22 | return (
23 |
24 |
25 | {/* */}
26 |
27 | {data.map((entry: any, index: any) => {
28 | return | ;
29 | })}
30 |
31 | [`${value} min`]}
41 | labelFormatter={(x) => ""}
42 | />
43 |
49 |
57 |
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/frontend/lib/utils.tsx:
--------------------------------------------------------------------------------
1 | import { Workout } from "../models";
2 | import _ from "lodash";
3 | import moment from "moment";
4 |
5 | export const roundNum = (num:any) => Math.round((num + Number.EPSILON) * 100) / 100;
6 |
7 | export const getDiffInMinutes = (workouts: Workout[]) => {
8 | return Math.round(
9 | _.sumBy(workouts, (x) => {
10 | const diff = moment(x.time_end).diff(moment(x.time_start));
11 | return moment.duration(diff).asMinutes();
12 | })
13 | );
14 | };
15 |
16 | export const getDiffInMins = (start:any, end:any) => {
17 | const diff = moment(end).diff(moment(start));
18 | return Math.round(moment.duration(diff).asMinutes());
19 | };
20 |
21 | export const parseHrZoneData = (workouts: Workout[]) => [
22 | {
23 | name: "Zone 1",
24 | value: Math.round(_.sumBy(workouts, (x) => x.hr_zones[0]) / 60),
25 | },
26 | {
27 | name: "Zone 2",
28 | value: Math.round(_.sumBy(workouts, (x) => x.hr_zones[1]) / 60),
29 | },
30 | {
31 | name: "Zone 3",
32 | value: Math.round(_.sumBy(workouts, (x) => x.hr_zones[2]) / 60),
33 | },
34 | {
35 | name: "Zone 4",
36 | value: Math.round(_.sumBy(workouts, (x) => x.hr_zones[3]) / 60),
37 | },
38 | {
39 | name: "Zone 5",
40 | value: Math.round(_.sumBy(workouts, (x) => x.hr_zones[4]) / 60),
41 | },
42 | ];
43 |
44 | export const parseSecs = (num:any) => {
45 | if (!num) return { hours: 0, mins: 0 };
46 | const time = num / 3600;
47 | const mins = time - Math.floor(time);
48 | return {
49 | hours: Math.floor(time),
50 | minutes: Math.round(mins * 60),
51 | };
52 | };
53 |
54 | export const parseMins = (num:any) => {
55 | if (!num) return { hours: 0, mins: 0 };
56 | const time = num / 60;
57 | const mins = time - Math.floor(time);
58 | return {
59 | hours: Math.floor(time) ? Math.floor(time) : 0,
60 | minutes: Math.round(mins * 60) ? Math.round(mins * 60) : 0,
61 | };
62 | };
63 |
--------------------------------------------------------------------------------
/frontend/components/LinkButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@chakra-ui/react";
2 | import { useState, useCallback } from "react";
3 | import { useVitalLink } from "@tryvital/vital-link";
4 |
5 | // URL for the mock backend which creates the vital link token
6 | const API_URL = "http://0.0.0.0:8000";
7 |
8 | const getTokenFromBackend = async (userID: string) => {
9 | const resp = await fetch(`${API_URL}/token/${userID}`);
10 | const data = await resp.json();
11 | return data;
12 | };
13 |
14 | export const LinkButton: React.FC<{ userID: string | null }> = ({ userID }) => {
15 | const [isLoading, setLoading] = useState(false);
16 |
17 | const onSuccess = useCallback((metadata: any) => {
18 | // Device is now connected.
19 | console.log("onSuccess", metadata);
20 | }, []);
21 |
22 | const onExit = useCallback((metadata: any) => {
23 | // User has quit the link flow.
24 | console.log("onExit", metadata);
25 | }, []);
26 |
27 | const onError = useCallback((metadata: any) => {
28 | // Error encountered in connecting device.
29 | console.log("onError", metadata);
30 | }, []);
31 |
32 | const config = {
33 | onSuccess,
34 | onExit,
35 | onError,
36 | env: process.env.NEXT_PUBLIC_VITAL_ENV as string,
37 | region: process.env.NEXT_PUBLIC_VITAL_REGION as string,
38 | };
39 |
40 | const { open, ready, error } = useVitalLink(config);
41 |
42 | const handleVitalOpen = async () => {
43 | setLoading(true);
44 | const token = await getTokenFromBackend(userID as string);
45 | open(token.link_token);
46 | setLoading(false);
47 | };
48 |
49 | return (
50 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/frontend/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage } from "next";
2 | import { VStack, Heading, HStack, Text, Box } from "@chakra-ui/react";
3 | import { Card } from "../components/Card";
4 | import { CreateUserVital } from "../components/CreateUserVital";
5 | import { useState } from "react";
6 | import useSWR from "swr";
7 | import { fetcher } from "../lib/client";
8 | import { SleepPanel } from "../components/dashboard/SleepPanel";
9 | import { ActivityPanel } from "../components/dashboard/ActivityPanel";
10 |
11 | const Home: NextPage = () => {
12 | const [userID, setUserID] = useState(null);
13 | const { data } = useSWR("/users/", fetcher);
14 |
15 | const usersFiltered = data?.users ? data.users : [];
16 |
17 | return (
18 |
26 |
27 | Vital Quickstart
28 |
29 |
30 |
31 |
36 |
37 |
38 |
39 | 2. Visualize user data
40 |
41 | Request user data and plot activity, workout sleep and other
42 | health information.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default Home;
60 |
--------------------------------------------------------------------------------
/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine AS base
2 |
3 | # Install dependencies only when needed
4 | FROM base AS deps
5 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
6 | RUN apk add --no-cache libc6-compat
7 | WORKDIR /app
8 |
9 | # Install dependencies based on the preferred package manager
10 | COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
11 | RUN \
12 | if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
13 | elif [ -f package-lock.json ]; then npm ci; \
14 | elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
15 | else echo "Lockfile not found." && exit 1; \
16 | fi
17 |
18 |
19 | # Rebuild the source code only when needed
20 | FROM base AS builder
21 | WORKDIR /app
22 | COPY --from=deps /app/node_modules ./node_modules
23 | COPY . .
24 |
25 | # Next.js collects completely anonymous telemetry data about general usage.
26 | # Learn more here: https://nextjs.org/telemetry
27 | # Uncomment the following line in case you want to disable telemetry during the build.
28 | # ENV NEXT_TELEMETRY_DISABLED 1
29 |
30 | RUN yarn build
31 |
32 | # If using npm comment out above and use below instead
33 | # RUN npm run build
34 |
35 | # Production image, copy all the files and run next
36 | FROM base AS runner
37 | WORKDIR /app
38 |
39 | ENV NODE_ENV production
40 | # Uncomment the following line in case you want to disable telemetry during runtime.
41 | # ENV NEXT_TELEMETRY_DISABLED 1
42 |
43 | RUN addgroup --system --gid 1001 nodejs
44 | RUN adduser --system --uid 1001 nextjs
45 |
46 | COPY --from=builder /app/public ./public
47 |
48 | # Automatically leverage output traces to reduce image size
49 | # https://nextjs.org/docs/advanced-features/output-file-tracing
50 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
51 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
52 |
53 | USER nextjs
54 |
55 | EXPOSE 3000
56 |
57 | ENV PORT 3000
58 |
59 | CMD ["node", "server.js"]
--------------------------------------------------------------------------------
/frontend/components/dashboard/InfoIcon.tsx:
--------------------------------------------------------------------------------
1 | import { VStack, Box, HStack, Text, Progress } from "@chakra-ui/react";
2 |
3 | export const InfoIcon = ({ icon, label, value }:any) => {
4 | return (
5 |
6 | {icon}
7 | {label}
8 |
9 | {value}
10 |
11 |
12 | );
13 | };
14 |
15 | export const InfoText = ({ label, value1, value2, suffix1, suffix2 } :any) => {
16 | return (
17 |
18 |
19 | {value1}
20 |
21 | {suffix1}
22 |
23 | {value2}
24 |
25 | {suffix2}
26 |
27 |
28 |
29 |
30 | {label}
31 |
32 |
33 | );
34 | };
35 |
36 | export const InfoBar = ({
37 | label,
38 | barValue,
39 | value1,
40 | value2,
41 | suffix1,
42 | suffix2,
43 | color,
44 | }:any) => {
45 | return (
46 |
47 |
48 |
56 | {label}
57 |
58 |
59 |
60 |
67 |
68 |
69 | {value1}
70 |
71 | {suffix1}
72 |
73 | {value2}
74 |
75 | {suffix2}
76 |
77 |
78 |
79 | );
80 | };
81 |
--------------------------------------------------------------------------------
/backend/python/main.py:
--------------------------------------------------------------------------------
1 | # TEST BACKEND IMPLEMENTATION
2 | from http.client import HTTPException
3 | from tracemalloc import start
4 | from typing import Optional
5 | from vital import Client
6 | from fastapi import FastAPI
7 | from starlette.middleware.cors import CORSMiddleware
8 | from pydantic import BaseModel
9 | import os
10 | from dotenv import load_dotenv
11 | load_dotenv()
12 |
13 | app = FastAPI()
14 | VITAL_API_KEY = os.getenv("VITAL_API_KEY")
15 | VITAL_ENVIRONMENT = os.getenv("VITAL_ENV")
16 | VITAL_REGION = os.getenv("VITAL_REGION")
17 |
18 | client = Client(api_key=VITAL_API_KEY, environment=VITAL_ENVIRONMENT, region=os.getenv("VITAL_REGION"))
19 |
20 | app.add_middleware( # type: ignore
21 | CORSMiddleware,
22 | allow_origins=["*"],
23 | allow_credentials=True,
24 | allow_methods=["*"],
25 | allow_headers=["*"],
26 | )
27 |
28 |
29 | @app.get("/token/{user_key}")
30 | def get_token(user_key: str):
31 | return client.Link.create(user_key)
32 |
33 |
34 | class CreateUserData(BaseModel):
35 | client_user_id: str
36 |
37 |
38 | @app.post("/user/")
39 | def create_user(data: CreateUserData):
40 | return client.User.create(data.client_user_id)
41 |
42 |
43 | @app.get("/users/")
44 | def get_users():
45 | return client.User.get_all()
46 |
47 |
48 | @app.get("/summary/{data_type}/{user_id}")
49 | def get_users(data_type: str, user_id: str, start_date: str, end_date: str):
50 | func_map = {
51 | "sleep": client.Sleep.get,
52 | "activity": client.Activity.get,
53 | "body": client.Body.get,
54 | "workouts": client.Workouts.get,
55 | }
56 | func = func_map.get(data_type)
57 | if not func:
58 | raise HTTPException(400, "Failed to find data type")
59 | data = func(user_id, start_date, end_date)
60 | return data
61 |
62 |
63 | @app.get("/summary/{user_id}")
64 | def get_users(user_id: str, start_date: str, end_date: str):
65 | sleep = client.Sleep.get(user_id, start_date, end_date)
66 | activity = client.Activity.get(user_id, start_date, end_date)
67 | body = client.Body.get(user_id, start_date, end_date)
68 | workouts = client.Workouts.get(user_id, start_date, end_date)
69 | return {"sleep": sleep, "activity": activity, "body": body, "workouts": workouts}
70 |
--------------------------------------------------------------------------------
/frontend/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 0 2rem;
3 | }
4 |
5 | .main {
6 | min-height: 100vh;
7 | padding: 4rem 0;
8 | flex: 1;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .footer {
16 | display: flex;
17 | flex: 1;
18 | padding: 2rem 0;
19 | border-top: 1px solid #eaeaea;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | .footer a {
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | flex-grow: 1;
29 | }
30 |
31 | .title a {
32 | color: #0070f3;
33 | text-decoration: none;
34 | }
35 |
36 | .title a:hover,
37 | .title a:focus,
38 | .title a:active {
39 | text-decoration: underline;
40 | }
41 |
42 | .title {
43 | margin: 0;
44 | line-height: 1.15;
45 | font-size: 4rem;
46 | }
47 |
48 | .title,
49 | .description {
50 | text-align: center;
51 | }
52 |
53 | .description {
54 | margin: 4rem 0;
55 | line-height: 1.5;
56 | font-size: 1.5rem;
57 | }
58 |
59 | .code {
60 | background: #fafafa;
61 | border-radius: 5px;
62 | padding: 0.75rem;
63 | font-size: 1.1rem;
64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
65 | Bitstream Vera Sans Mono, Courier New, monospace;
66 | }
67 |
68 | .grid {
69 | display: flex;
70 | align-items: center;
71 | justify-content: center;
72 | flex-wrap: wrap;
73 | max-width: 800px;
74 | }
75 |
76 | .card {
77 | margin: 1rem;
78 | padding: 1.5rem;
79 | text-align: left;
80 | color: inherit;
81 | text-decoration: none;
82 | border: 1px solid #eaeaea;
83 | border-radius: 10px;
84 | transition: color 0.15s ease, border-color 0.15s ease;
85 | max-width: 300px;
86 | }
87 |
88 | .card:hover,
89 | .card:focus,
90 | .card:active {
91 | color: #0070f3;
92 | border-color: #0070f3;
93 | }
94 |
95 | .card h2 {
96 | margin: 0 0 1rem 0;
97 | font-size: 1.5rem;
98 | }
99 |
100 | .card p {
101 | margin: 0;
102 | font-size: 1.25rem;
103 | line-height: 1.5;
104 | }
105 |
106 | .logo {
107 | height: 1em;
108 | margin-left: 0.5rem;
109 | }
110 |
111 | @media (max-width: 600px) {
112 | .grid {
113 | width: 100%;
114 | flex-direction: column;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/DetailedLineGraph.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import moment from "moment";
3 | import {
4 | LineChart,
5 | Line,
6 | XAxis,
7 | YAxis,
8 | CartesianGrid,
9 | Tooltip,
10 | Legend,
11 | AreaChart,
12 | Area,
13 | ResponsiveContainer,
14 | } from "recharts";
15 | import { HStack, Text } from "@chakra-ui/react";
16 | import { IoBed } from "react-icons/io5";
17 | import { AiFillHeart } from "react-icons/ai";
18 | import { FaRunning } from "react-icons/fa";
19 | import _ from "lodash";
20 |
21 | interface DataPoint {
22 | value: number;
23 | }
24 |
25 | interface LineProps {
26 | data: DataPoint[];
27 | }
28 |
29 | export const DetailedLineGraph: React.FunctionComponent = ({
30 | data,
31 | }) => {
32 | const maxValue = _.maxBy(data, (x) => x.value);
33 | const max = Math.ceil(maxValue?.value || 0 / 25) * 25;
34 |
35 | const shouldRotate = data.length > 2000;
36 | return (
37 |
38 |
39 | moment.unix(unixTime).format("HH:mm A")}
54 | />
55 | el < max + 50)}
60 | domain={[0, `${max}`]}
61 | />
62 |
63 |
64 |
72 | moment.unix(x).format("HH:mm A")}
74 | labelStyle={{ fontSize: 12, color: "gray" }}
75 | contentStyle={{ borderRadius: 10 }}
76 | formatter={(value: any, name: any, props: any) => {
77 | if (name === "value") {
78 | return [
79 |
80 |
81 | {value}
82 | ,
83 | ];
84 | }
85 | }}
86 | />
87 |
88 |
89 | );
90 | };
91 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/ActivityGraph.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import moment from "moment";
3 | import {
4 | XAxis,
5 | YAxis,
6 | CartesianGrid,
7 | Tooltip,
8 | AreaChart,
9 | Area,
10 | ResponsiveContainer,
11 | } from "recharts";
12 | import _ from "lodash";
13 | import { Activity } from "../../models";
14 | import { parseMins, parseSecs } from "../../lib/utils";
15 |
16 | export const ActivityGraph: React.FunctionComponent<{ data: Activity[] }> = ({
17 | data,
18 | }) => {
19 | const fixedData = data.map((el) => ({ ...el, date: moment(el.date).unix() }));
20 | return (
21 |
22 |
23 |
34 | moment.unix(unixTime).format("ddd MMM D")
35 | }
36 | />
37 | el < max + 50)}
42 | // domain={[0, `${max}`]}
43 | tickFormatter={(value) => {
44 | const time = parseMins(value);
45 | return `${time.hours ? time.hours : 0}h ${
46 | time.minutes ? time.minutes : 0
47 | } min`;
48 | }}
49 | />
50 |
51 | moment.unix(x).format("ddd MMM D")}
53 | labelStyle={{ fontSize: 12, color: "gray" }}
54 | itemStyle={{ color: "rgba(153, 192, 74,1)" }}
55 | contentStyle={{ borderRadius: 10 }}
56 | formatter={(value:any, name:any, props:any) => {
57 | const time = parseMins(value);
58 | return `${time.hours ? time.hours : 0}h ${
59 | time.minutes ? time.minutes : 0
60 | } min`;
61 | }}
62 | />
63 |
64 |
72 |
79 |
86 |
87 |
88 | );
89 | };
90 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/SleepGraph.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import moment from "moment";
3 | import {
4 | LineChart,
5 | Line,
6 | XAxis,
7 | YAxis,
8 | CartesianGrid,
9 | Tooltip,
10 | Legend,
11 | AreaChart,
12 | Area,
13 | ResponsiveContainer,
14 | } from "recharts";
15 | import { HStack, Text } from "@chakra-ui/react";
16 | import { IoBed } from "react-icons/io5";
17 | import { AiFillHeart } from "react-icons/ai";
18 | import { FaRunning } from "react-icons/fa";
19 | import _ from "lodash";
20 | import { Sleep } from "../../models";
21 | import { parseSecs } from "../../lib/utils";
22 |
23 | export const SleepGraph: React.FunctionComponent<{ data: Sleep[] }> = ({
24 | data,
25 | }) => {
26 | const fixedData = data.map((el) => ({ ...el, date: moment(el.date).unix() }));
27 | return (
28 |
29 |
30 |
41 | moment.unix(unixTime).format("ddd MMM D")
42 | }
43 | />
44 | el < max + 50)}
49 | // domain={[0, `${max}`]}
50 | tickFormatter={(value) => {
51 | const time = parseSecs(value);
52 | return `${time.hours}h ${time.minutes ? time.minutes : 0} min`;
53 | }}
54 | />
55 |
56 | moment.unix(x).format("ddd MMM D")}
58 | labelStyle={{ fontSize: 12, color: "gray" }}
59 | contentStyle={{ borderRadius: 10 }}
60 | formatter={(value:any, name:any, props:any) => {
61 | const time = parseSecs(value);
62 | return `${time.hours}h ${time.minutes}min`;
63 | }}
64 | />
65 |
66 |
74 |
81 |
88 |
95 |
96 |
97 | );
98 | };
99 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/customRadio.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import { Box, HStack, useRadio, useRadioGroup } from "@chakra-ui/react";
3 |
4 | export const RadioCard = (props) => {
5 | const { getInputProps, getCheckboxProps } = useRadio(props);
6 |
7 | const input = getInputProps();
8 | const checkbox = getCheckboxProps();
9 |
10 | return (
11 |
12 |
13 |
31 | {props.children}
32 |
33 |
34 | );
35 | };
36 |
37 | export const RadioCardSquare = (props) => {
38 | const { getInputProps, getCheckboxProps } = useRadio(props);
39 |
40 | const input = getInputProps();
41 | const checkbox = getCheckboxProps();
42 |
43 | return (
44 |
45 |
46 |
65 | {props.children}
66 |
67 |
68 | );
69 | };
70 |
71 | interface RadioButtonProps {
72 | options: Any;
73 | onChange: Any;
74 | selectedColor?: string;
75 | isSquare?: boolean;
76 | defaultValue: string;
77 | }
78 |
79 | // Step 2: Use the `useRadioGroup` hook to control a group of custom radios.
80 | export const RadioButtons: React.FunctionComponent = ({
81 | options,
82 | onChange,
83 | selectedColor,
84 | isSquare,
85 | defaultValue,
86 | }) => {
87 | const { getRootProps, getRadioProps } = useRadioGroup({
88 | name: "framework",
89 | defaultValue: defaultValue,
90 | onChange: onChange,
91 | });
92 |
93 | const group = getRootProps();
94 |
95 | return (
96 |
102 | {options.map((value) => {
103 | const radio = getRadioProps({ value });
104 | return isSquare ? (
105 |
106 | {value}
107 |
108 | ) : (
109 |
110 | {value}
111 |
112 | );
113 | })}
114 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/frontend/models/index.tsx:
--------------------------------------------------------------------------------
1 | export interface User {
2 | uid: string;
3 | }
4 |
5 | export interface ConnectedSource {
6 | source_id: Number;
7 | id: string;
8 | }
9 |
10 | export interface Profile {
11 | auth0_id: string;
12 | avatar: string;
13 | email: string;
14 | id: string;
15 | name: string;
16 | }
17 |
18 | export interface Invite {
19 | id: string;
20 | email: string;
21 | role?: string;
22 | invited_at: string;
23 | team_id: string;
24 | verification_code: string;
25 | invited_by: string;
26 | team_name: string;
27 | }
28 |
29 | export interface Team {
30 | id: string;
31 | name: string;
32 | description?: string;
33 | members: Member[];
34 | sandbox_client_id: string;
35 | sandbox_client_secret: string;
36 | prod_client_id: string;
37 | prod_client_secret: string;
38 | sandbox_connected_sources: ConnectedSource[];
39 | prod_connected_sources: ConnectedSource[];
40 | prod_users: User[];
41 | sandbox_users: User[];
42 | sandbox_airtable_api_key: string;
43 | sandbox_airtable_base_id: string;
44 | prod_airtable_api_key: string;
45 | prod_airtable_base_id: string;
46 | sandbox_webhook_secret: string;
47 | prod_webhook_secret: string;
48 | has_production_access: boolean;
49 | has_dashboard_access: boolean;
50 | prod_application_status?: "applied" | "success";
51 | invites: Invite[];
52 | }
53 |
54 | export interface Member {
55 | auth0_id: string;
56 | avatar: string;
57 | email: string;
58 | id: string;
59 | name: string;
60 | teams: Team[];
61 | role: string;
62 | }
63 |
64 | export interface CreateUserResponse {
65 | client_user_id: string;
66 | team_id: string;
67 | }
68 |
69 | export interface LinkTokenResponse {
70 | user_key: string;
71 | team_id: string;
72 | }
73 |
74 | export interface HeartRate {
75 | value: number;
76 | timestamp: number;
77 | }
78 | export interface Source {
79 | name: string;
80 | slug: string;
81 | logo: string;
82 | auth_type: string;
83 | }
84 |
85 | export interface Sport {
86 | name: string;
87 | icon: string;
88 | category: string;
89 | id: string;
90 | }
91 | export interface Workout {
92 | average_hr: number;
93 | calories: number;
94 | distance: number;
95 | heart_rate: HeartRate[];
96 | hr_zones: number[];
97 | id: string;
98 | max_hr: number;
99 | priority: number;
100 | source: Source;
101 | sport: Sport;
102 | time_end: string;
103 | time_start: string;
104 | }
105 |
106 | export interface Sleep {
107 | average_hrv: number;
108 | awake: number;
109 | bedtime_start: string;
110 | bedtime_stop: string;
111 | date: string;
112 | deep: number;
113 | duration: number;
114 | efficiency: number;
115 | hr_average: number;
116 | hr_lowest: number;
117 | latency: number;
118 | light: number;
119 | priority: number;
120 | rem: number;
121 | respiratory_rate: number;
122 | source: Source;
123 | temperature_delta: number;
124 | total: number;
125 | }
126 |
127 | export interface Activity {
128 | calories_active: number;
129 | calories_total: number;
130 | daily_movement: number;
131 | date: string;
132 | high: number;
133 | id: string;
134 | low: number;
135 | medium: number;
136 | priority: number;
137 | source: Source;
138 | steps: null;
139 | user_id: string;
140 | }
141 |
142 | export interface Webhook {
143 | id: string;
144 | url: string;
145 | event_type: string;
146 | last_event_sent: string;
147 | }
148 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/WorkoutPanel.tsx:
--------------------------------------------------------------------------------
1 | import { VStack, Box, HStack, Avatar, Text, Heading } from "@chakra-ui/react";
2 | import { DetailedLineGraph } from "./DetailedLineGraph";
3 | import moment from "moment";
4 | import { RiHeartPulseFill } from "react-icons/ri";
5 | import { AiFillHeart, AiFillFire } from "react-icons/ai";
6 | import { IoIosTimer } from "react-icons/io";
7 | import { getDiffInMins, parseHrZoneData } from "../../lib/utils";
8 | import _ from "lodash";
9 | import { HorizontalBarChart } from "./HorizontalBarChart";
10 | import { InfoIcon } from "./InfoIcon";
11 | import { Workout } from "../../models";
12 |
13 | const WorkoutCard: React.FunctionComponent<{ workout: Workout }> = ({
14 | workout,
15 | }) => {
16 | const hrZoneData = parseHrZoneData([workout]);
17 |
18 | return (
19 |
29 |
30 |
31 |
32 | {workout.sport.name}
33 |
34 | {moment(workout.time_start).format("ddd Do HH:MM A")} -{" "}
35 | {moment(workout.time_end).format("ddd Do HH:MM A")}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
48 | }
49 | value={workout.max_hr}
50 | />
51 | }
54 | value={workout.average_hr}
55 | />
56 | }
59 | value={`${getDiffInMins(workout.time_start, workout.time_end)}`}
60 | />
61 | }
64 | value={workout.calories}
65 | />
66 |
67 |
68 |
69 | ({
71 | ...el,
72 | timestamp: moment(el.timestamp).unix(),
73 | }))}
74 | />
75 |
76 |
77 |
78 | );
79 | };
80 |
81 | export const WorkoutPanel = ({ workouts }:any) => {
82 | ({ workouts });
83 | const workoutsSorted = _.orderBy(
84 | workouts,
85 | (x) => moment(x.time_start).unix(),
86 | "desc"
87 | );
88 | return (
89 | <>
90 | {workoutsSorted.map((el) => (
91 |
92 | ))}
93 |
94 | {workoutsSorted.length === 0 && (
95 |
105 |
106 |
107 | This user doesn't have any workouts.
108 |
109 |
110 |
111 | )}
112 | >
113 | );
114 | };
115 |
--------------------------------------------------------------------------------
/frontend/components/CreateUserVital.tsx:
--------------------------------------------------------------------------------
1 | import { Heading, HStack, VStack } from "@chakra-ui/react";
2 | import { Card } from "./Card";
3 | import {
4 | Text,
5 | Button,
6 | InputGroup,
7 | Input,
8 | InputRightElement,
9 | Image,
10 | } from "@chakra-ui/react";
11 | import { useState } from "react";
12 | import { Client } from "../lib/client";
13 | import { LinkButton } from "./LinkButton";
14 |
15 | const CreateUser = ({ onCreate }: { onCreate: any }) => {
16 | const [clientUserId, setClientUserId] = useState("");
17 | const [isLoading, setLoading] = useState(false);
18 |
19 | const handleClick = async () => {
20 | // fetch data
21 | const client = new Client();
22 | if (clientUserId) {
23 | setLoading(true);
24 | try {
25 | const data = await client.createUser(clientUserId);
26 | onCreate(data.user_id);
27 | setClientUserId("");
28 | setLoading(false);
29 | } catch (e) {
30 | console.log("Failed to create");
31 | setLoading(false);
32 | }
33 | }
34 | };
35 |
36 | return (
37 |
38 | setClientUserId(e.target.value)}
43 | />
44 |
45 |
53 |
54 |
55 | );
56 | };
57 |
58 | export const CreateUserVital = ({
59 | users,
60 | onCreate,
61 | onSelect,
62 | }: {
63 | users: any;
64 | onCreate: any;
65 | onSelect: any;
66 | }) => {
67 | return (
68 |
69 | 1. Create a Vital user
70 |
71 | First step is to create a vital user using a unique id you have stored
72 | against your user.
73 |
74 |
75 |
76 |
77 | Existing Users (Most recent 5)
78 |
79 |
80 | {users
81 | .sort((a: any, b: any) => {
82 | return a.created_on < b.created_on
83 | ? -1
84 | : a.created_on > b.created_on
85 | ? 1
86 | : 0;
87 | })
88 | .reverse()
89 | .slice(0, 5)
90 | .map((el: any) => {
91 | return (
92 |
93 |
98 |
99 | vital_user_id: {el.user_id}
100 |
101 | {el.client_user_id}
102 |
103 |
104 | {el.connected_sources.map((el: any) => (
105 |
112 | ))}
113 |
114 |
115 |
116 |
119 |
120 |
121 |
122 |
123 | );
124 | })}
125 |
126 |
127 | );
128 | };
129 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/ActivityPanel.tsx:
--------------------------------------------------------------------------------
1 | import { VStack, Box, HStack, Heading } from "@chakra-ui/react";
2 | import { ActivityGraph } from "./ActivityGraph";
3 | import moment from "moment";
4 | import { parseMins } from "../../lib/utils";
5 | import { fetchSummaryData } from "../../lib/client";
6 | import _ from "lodash";
7 | import { Activity, Sleep } from "../../models";
8 | import { InfoBar, InfoText } from "./InfoIcon";
9 | import { useState } from "react";
10 | import useSWR from "swr";
11 | import { RadioButtons } from "./customRadio";
12 |
13 | const defaultActvity = {
14 | calories_active: 0,
15 | calories_total: 0,
16 | daily_movement: 0,
17 | date: new Date().toISOString(),
18 | high: 0,
19 | id: "123131",
20 | low: 0,
21 | medium: 0,
22 | priority: 0,
23 | source: {
24 | name: "Not loaded",
25 | logo: null,
26 | },
27 | steps: 0,
28 | user_id: "",
29 | };
30 |
31 | const ActivityCard: React.FunctionComponent<{ latestActivity: Activity }> = ({
32 | latestActivity,
33 | }) => {
34 | const latestActivitydefault = latestActivity
35 | ? latestActivity
36 | : defaultActvity;
37 |
38 | const high = parseMins(latestActivitydefault?.high);
39 | const medium = parseMins(latestActivitydefault?.medium);
40 | const low = parseMins(latestActivitydefault?.low);
41 |
42 | return (
43 |
44 |
45 | {moment(latestActivitydefault?.date).format("Do ddd MMM")}
46 |
47 |
48 |
49 |
56 |
63 |
64 |
65 |
74 |
83 |
92 |
93 |
94 |
95 | );
96 | };
97 |
98 | export const ActivityPanel = ({ userId }: { userId: any }) => {
99 | const [startDate, setStartDate] = useState(
100 | moment().subtract(7, "days").toISOString()
101 | );
102 | const [endDate, setEndDate] = useState(moment().toISOString());
103 |
104 | const { data: activity = [], error: errorSleep } = useSWR(
105 | userId ? ["activity", userId, startDate, endDate, "activity"] : null,
106 | fetchSummaryData
107 | );
108 |
109 | const handleDateChange = (period: "1w" | "1m") => {
110 | switch (period) {
111 | case "1m":
112 | setStartDate(moment().subtract(30, "days").toISOString());
113 | return;
114 | case "1w":
115 | setStartDate(moment().subtract(7, "days").toISOString());
116 | return;
117 | default:
118 | return;
119 | }
120 | };
121 | const activitySorted = _.orderBy(
122 | activity,
123 | (x) => moment(x.date).unix(),
124 | "desc"
125 | );
126 | return (
127 |
137 | {activitySorted && }
138 |
139 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | );
153 | };
154 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/SleepPanel.tsx:
--------------------------------------------------------------------------------
1 | import { VStack, Box, HStack, Heading } from "@chakra-ui/react";
2 | import { SleepGraph } from "./SleepGraph";
3 | import moment from "moment";
4 | import { parseSecs } from "../../lib/utils";
5 | import { fetchSummaryData } from "../../lib/client";
6 |
7 | import _ from "lodash";
8 | import { Sleep } from "../../models";
9 | import { InfoBar, InfoText } from "./InfoIcon";
10 | import { useState } from "react";
11 | import useSWR from "swr";
12 | import { RadioButtons } from "./customRadio";
13 |
14 | const SleepDayCard: React.FunctionComponent<{ latestSleep: Sleep }> = ({
15 | latestSleep,
16 | }) => {
17 | const totalTime = parseSecs(latestSleep?.total);
18 | const rem = parseSecs(latestSleep?.rem);
19 | const deep = parseSecs(latestSleep?.deep);
20 | const latency = parseSecs(latestSleep?.latency);
21 |
22 | return (
23 |
24 |
25 | {moment(latestSleep?.bedtime_start).format("Do ddd HH:mm A")} -{" "}
26 | {moment(latestSleep?.bedtime_stop).format("HH:mm A")}
27 |
28 |
29 |
30 |
37 |
44 |
51 |
52 |
53 |
62 |
71 |
80 |
89 |
98 |
99 |
100 |
101 | );
102 | };
103 |
104 | export const SleepPanel = ({ userId }:any) => {
105 | const [startDate, setStartDate] = useState(
106 | moment().subtract(7, "days").toISOString()
107 | );
108 | const [endDate, setEndDate] = useState(moment().toISOString());
109 |
110 | const { data: sleeps = [], error: errorSleep } = useSWR(
111 | userId ? ["sleep", userId, startDate, endDate, "sleep"] : null,
112 | fetchSummaryData
113 | );
114 |
115 | const handleDateChange = (period: "1w" | "1m") => {
116 | switch (period) {
117 | case "1m":
118 | setStartDate(moment().subtract(30, "days").toISOString());
119 | return;
120 | case "1w":
121 | setStartDate(moment().subtract(7, "days").toISOString());
122 | return;
123 | default:
124 | return;
125 | }
126 | };
127 | const sleepsSorted = _.orderBy(sleeps, (x) => moment(x.date).unix(), "desc");
128 | return (
129 |
139 | {sleeps && }
140 |
141 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | );
154 | };
155 |
--------------------------------------------------------------------------------
/frontend/components/dashboard/LineGraph.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import moment from "moment";
3 | import {
4 | LineChart,
5 | Line,
6 | XAxis,
7 | YAxis,
8 | Tooltip,
9 | Legend,
10 | ResponsiveContainer,
11 | } from "recharts";
12 | import { HStack, Text } from "@chakra-ui/react";
13 | import { IoBed } from "react-icons/io5";
14 | import { AiFillHeart } from "react-icons/ai";
15 | import { FaRunning } from "react-icons/fa";
16 |
17 | interface LineGraphProps {
18 | data: any;
19 | expanded?: boolean;
20 | }
21 |
22 | export const LineGraph: React.FunctionComponent = ({
23 | data,
24 | expanded,
25 | }) => {
26 | return (
27 |
28 |
29 | moment.unix(unixTime).format("DD")}
38 | />
39 |
48 |
58 |
68 | moment.unix(x).format("ddd Do YYYY")}
70 | formatter={(value: any, name: any, props: any) => {
71 | if (name === "sleep_total") {
72 | const v = Math.round((value / 3600) * 100) / 100;
73 | var decimal = v - Math.floor(v);
74 | var mins = Math.round(decimal * 60);
75 | return [
76 |
77 |
78 |
79 | {Math.floor(v)}hr {mins}min
80 |
81 | ,
82 | ];
83 | }
84 | if (name === "average_hr") {
85 | return [
86 |
87 |
88 | {value}
89 | ,
90 | ];
91 | }
92 | if (name === "calories_active") {
93 | return [
94 |
95 |
96 | {value}
97 | ,
98 | ];
99 | }
100 | return value;
101 | }}
102 | itemStyle={{ padding: 0 }}
103 | labelStyle={{ fontSize: 12, color: "gray" }}
104 | contentStyle={{ borderRadius: 10 }}
105 | />
106 |
115 |
124 |
133 |
167 |
168 | );
169 | };
170 |
--------------------------------------------------------------------------------
/backend/python/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "anyio"
5 | version = "3.5.0"
6 | description = "High level compatibility layer for multiple asynchronous event loop implementations"
7 | optional = false
8 | python-versions = ">=3.6.2"
9 | files = [
10 | {file = "anyio-3.5.0-py3-none-any.whl", hash = "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e"},
11 | {file = "anyio-3.5.0.tar.gz", hash = "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6"},
12 | ]
13 |
14 | [package.dependencies]
15 | idna = ">=2.8"
16 | sniffio = ">=1.1"
17 |
18 | [package.extras]
19 | doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
20 | test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
21 | trio = ["trio (>=0.16)"]
22 |
23 | [[package]]
24 | name = "arrow"
25 | version = "1.2.2"
26 | description = "Better dates & times for Python"
27 | optional = false
28 | python-versions = ">=3.6"
29 | files = [
30 | {file = "arrow-1.2.2-py3-none-any.whl", hash = "sha256:d622c46ca681b5b3e3574fcb60a04e5cc81b9625112d5fb2b44220c36c892177"},
31 | {file = "arrow-1.2.2.tar.gz", hash = "sha256:05caf1fd3d9a11a1135b2b6f09887421153b94558e5ef4d090b567b47173ac2b"},
32 | ]
33 |
34 | [package.dependencies]
35 | python-dateutil = ">=2.7.0"
36 |
37 | [[package]]
38 | name = "asgiref"
39 | version = "3.5.1"
40 | description = "ASGI specs, helper code, and adapters"
41 | optional = false
42 | python-versions = ">=3.7"
43 | files = [
44 | {file = "asgiref-3.5.1-py3-none-any.whl", hash = "sha256:45a429524fba18aba9d512498b19d220c4d628e75b40cf5c627524dbaebc5cc1"},
45 | {file = "asgiref-3.5.1.tar.gz", hash = "sha256:fddeea3c53fa99d0cdb613c3941cc6e52d822491fc2753fba25768fb5bf4e865"},
46 | ]
47 |
48 | [package.extras]
49 | tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
50 |
51 | [[package]]
52 | name = "auth0-python"
53 | version = "3.23.0"
54 | description = "Auth0 Python SDK"
55 | optional = false
56 | python-versions = ">=2.7, !=3.0.*, !=3.1.*"
57 | files = [
58 | {file = "auth0-python-3.23.0.tar.gz", hash = "sha256:dc7e5ccbc1ee36138656324ec9a1a530c6890177e242d95b25d3e6919dce362b"},
59 | {file = "auth0_python-3.23.0-py2.py3-none-any.whl", hash = "sha256:8a1950fc488afae15982751bd4bf27f22da826795b571297c0c92f48ca9f0a31"},
60 | ]
61 |
62 | [package.dependencies]
63 | pyjwt = {version = ">=1.7.1", extras = ["crypto"]}
64 | requests = ">=2.14.0"
65 |
66 | [package.extras]
67 | test = ["coverage", "mock (>=1.3.0)", "pre-commit"]
68 |
69 | [[package]]
70 | name = "certifi"
71 | version = "2021.10.8"
72 | description = "Python package for providing Mozilla's CA Bundle."
73 | optional = false
74 | python-versions = "*"
75 | files = [
76 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
77 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
78 | ]
79 |
80 | [[package]]
81 | name = "cffi"
82 | version = "1.17.1"
83 | description = "Foreign Function Interface for Python calling C code."
84 | optional = false
85 | python-versions = ">=3.8"
86 | files = [
87 | {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
88 | {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
89 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
90 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
91 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
92 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
93 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
94 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
95 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
96 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
97 | {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
98 | {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
99 | {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
100 | {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
101 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
102 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
103 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
104 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
105 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
106 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
107 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
108 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
109 | {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
110 | {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
111 | {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
112 | {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
113 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
114 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
115 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
116 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
117 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
118 | {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
119 | {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
120 | {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
121 | {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
122 | {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
123 | {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
124 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
125 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
126 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
127 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
128 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
129 | {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
130 | {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
131 | {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
132 | {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
133 | {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
134 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
135 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
136 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
137 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
138 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
139 | {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
140 | {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
141 | {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
142 | {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
143 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
144 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
145 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
146 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
147 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
148 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
149 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
150 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
151 | {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
152 | {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
153 | {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
154 | ]
155 |
156 | [package.dependencies]
157 | pycparser = "*"
158 |
159 | [[package]]
160 | name = "charset-normalizer"
161 | version = "2.0.12"
162 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
163 | optional = false
164 | python-versions = ">=3.5.0"
165 | files = [
166 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
167 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
168 | ]
169 |
170 | [package.extras]
171 | unicode-backport = ["unicodedata2"]
172 |
173 | [[package]]
174 | name = "click"
175 | version = "8.1.3"
176 | description = "Composable command line interface toolkit"
177 | optional = false
178 | python-versions = ">=3.7"
179 | files = [
180 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
181 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
182 | ]
183 |
184 | [package.dependencies]
185 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
186 |
187 | [[package]]
188 | name = "colorama"
189 | version = "0.4.4"
190 | description = "Cross-platform colored terminal text."
191 | optional = false
192 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
193 | files = [
194 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
195 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
196 | ]
197 |
198 | [[package]]
199 | name = "cryptography"
200 | version = "37.0.2"
201 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
202 | optional = false
203 | python-versions = ">=3.6"
204 | files = [
205 | {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"},
206 | {file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"},
207 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"},
208 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"},
209 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"},
210 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"},
211 | {file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"},
212 | {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"},
213 | {file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"},
214 | {file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"},
215 | {file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"},
216 | {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"},
217 | {file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"},
218 | {file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"},
219 | {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"},
220 | {file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"},
221 | {file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"},
222 | {file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"},
223 | {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"},
224 | {file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"},
225 | {file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"},
226 | {file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"},
227 | ]
228 |
229 | [package.dependencies]
230 | cffi = ">=1.12"
231 |
232 | [package.extras]
233 | docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
234 | docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
235 | pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
236 | sdist = ["setuptools-rust (>=0.11.4)"]
237 | ssh = ["bcrypt (>=3.1.5)"]
238 | test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
239 |
240 | [[package]]
241 | name = "fastapi"
242 | version = "0.77.1"
243 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
244 | optional = false
245 | python-versions = ">=3.6.1"
246 | files = [
247 | {file = "fastapi-0.77.1-py3-none-any.whl", hash = "sha256:041d8935a13c8f22a7ef80c76d8eea54953c44476d7057e7734138ee8951b9dc"},
248 | {file = "fastapi-0.77.1.tar.gz", hash = "sha256:6dfa715a334d1fafedcea33d74c7382afd2ce123093f83be2f4e322e89d45e22"},
249 | ]
250 |
251 | [package.dependencies]
252 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
253 | starlette = "0.19.1"
254 |
255 | [package.extras]
256 | all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
257 | dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
258 | doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"]
259 | test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
260 |
261 | [[package]]
262 | name = "h11"
263 | version = "0.13.0"
264 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
265 | optional = false
266 | python-versions = ">=3.6"
267 | files = [
268 | {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"},
269 | {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"},
270 | ]
271 |
272 | [[package]]
273 | name = "idna"
274 | version = "3.3"
275 | description = "Internationalized Domain Names in Applications (IDNA)"
276 | optional = false
277 | python-versions = ">=3.5"
278 | files = [
279 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
280 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
281 | ]
282 |
283 | [[package]]
284 | name = "importlib-metadata"
285 | version = "3.10.1"
286 | description = "Read metadata from Python packages"
287 | optional = false
288 | python-versions = ">=3.6"
289 | files = [
290 | {file = "importlib_metadata-3.10.1-py3-none-any.whl", hash = "sha256:2ec0faae539743ae6aaa84b49a169670a465f7f5d64e6add98388cc29fd1f2f6"},
291 | {file = "importlib_metadata-3.10.1.tar.gz", hash = "sha256:c9356b657de65c53744046fa8f7358afe0714a1af7d570c00c3835c2d724a7c1"},
292 | ]
293 |
294 | [package.dependencies]
295 | zipp = ">=0.5"
296 |
297 | [package.extras]
298 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
299 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"]
300 |
301 | [[package]]
302 | name = "pycparser"
303 | version = "2.21"
304 | description = "C parser in Python"
305 | optional = false
306 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
307 | files = [
308 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
309 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
310 | ]
311 |
312 | [[package]]
313 | name = "pydantic"
314 | version = "1.10.21"
315 | description = "Data validation and settings management using python type hints"
316 | optional = false
317 | python-versions = ">=3.7"
318 | files = [
319 | {file = "pydantic-1.10.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:245e486e0fec53ec2366df9cf1cba36e0bbf066af7cd9c974bbbd9ba10e1e586"},
320 | {file = "pydantic-1.10.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c54f8d4c151c1de784c5b93dfbb872067e3414619e10e21e695f7bb84d1d1fd"},
321 | {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b64708009cfabd9c2211295144ff455ec7ceb4c4fb45a07a804309598f36187"},
322 | {file = "pydantic-1.10.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a148410fa0e971ba333358d11a6dea7b48e063de127c2b09ece9d1c1137dde4"},
323 | {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:36ceadef055af06e7756eb4b871cdc9e5a27bdc06a45c820cd94b443de019bbf"},
324 | {file = "pydantic-1.10.21-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0501e1d12df6ab1211b8cad52d2f7b2cd81f8e8e776d39aa5e71e2998d0379f"},
325 | {file = "pydantic-1.10.21-cp310-cp310-win_amd64.whl", hash = "sha256:c261127c275d7bce50b26b26c7d8427dcb5c4803e840e913f8d9df3f99dca55f"},
326 | {file = "pydantic-1.10.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b6350b68566bb6b164fb06a3772e878887f3c857c46c0c534788081cb48adf4"},
327 | {file = "pydantic-1.10.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:935b19fdcde236f4fbf691959fa5c3e2b6951fff132964e869e57c70f2ad1ba3"},
328 | {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6a04efdcd25486b27f24c1648d5adc1633ad8b4506d0e96e5367f075ed2e0b"},
329 | {file = "pydantic-1.10.21-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1ba253eb5af8d89864073e6ce8e6c8dec5f49920cff61f38f5c3383e38b1c9f"},
330 | {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:57f0101e6c97b411f287a0b7cf5ebc4e5d3b18254bf926f45a11615d29475793"},
331 | {file = "pydantic-1.10.21-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e85834f0370d737c77a386ce505c21b06bfe7086c1c568b70e15a568d9670d"},
332 | {file = "pydantic-1.10.21-cp311-cp311-win_amd64.whl", hash = "sha256:6a497bc66b3374b7d105763d1d3de76d949287bf28969bff4656206ab8a53aa9"},
333 | {file = "pydantic-1.10.21-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ed4a5f13cf160d64aa331ab9017af81f3481cd9fd0e49f1d707b57fe1b9f3ae"},
334 | {file = "pydantic-1.10.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b7693bb6ed3fbe250e222f9415abb73111bb09b73ab90d2d4d53f6390e0ccc1"},
335 | {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185d5f1dff1fead51766da9b2de4f3dc3b8fca39e59383c273f34a6ae254e3e2"},
336 | {file = "pydantic-1.10.21-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38e6d35cf7cd1727822c79e324fa0677e1a08c88a34f56695101f5ad4d5e20e5"},
337 | {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1d7c332685eafacb64a1a7645b409a166eb7537f23142d26895746f628a3149b"},
338 | {file = "pydantic-1.10.21-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c9b782db6f993a36092480eeaab8ba0609f786041b01f39c7c52252bda6d85f"},
339 | {file = "pydantic-1.10.21-cp312-cp312-win_amd64.whl", hash = "sha256:7ce64d23d4e71d9698492479505674c5c5b92cda02b07c91dfc13633b2eef805"},
340 | {file = "pydantic-1.10.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0067935d35044950be781933ab91b9a708eaff124bf860fa2f70aeb1c4be7212"},
341 | {file = "pydantic-1.10.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5e8148c2ce4894ce7e5a4925d9d3fdce429fb0e821b5a8783573f3611933a251"},
342 | {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4973232c98b9b44c78b1233693e5e1938add5af18042f031737e1214455f9b8"},
343 | {file = "pydantic-1.10.21-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:662bf5ce3c9b1cef32a32a2f4debe00d2f4839fefbebe1d6956e681122a9c839"},
344 | {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98737c3ab5a2f8a85f2326eebcd214510f898881a290a7939a45ec294743c875"},
345 | {file = "pydantic-1.10.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0bb58bbe65a43483d49f66b6c8474424d551a3fbe8a7796c42da314bac712738"},
346 | {file = "pydantic-1.10.21-cp313-cp313-win_amd64.whl", hash = "sha256:e622314542fb48542c09c7bd1ac51d71c5632dd3c92dc82ede6da233f55f4848"},
347 | {file = "pydantic-1.10.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d356aa5b18ef5a24d8081f5c5beb67c0a2a6ff2a953ee38d65a2aa96526b274f"},
348 | {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08caa8c0468172d27c669abfe9e7d96a8b1655ec0833753e117061febaaadef5"},
349 | {file = "pydantic-1.10.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c677aa39ec737fec932feb68e4a2abe142682f2885558402602cd9746a1c92e8"},
350 | {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:79577cc045d3442c4e845df53df9f9202546e2ba54954c057d253fc17cd16cb1"},
351 | {file = "pydantic-1.10.21-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:b6b73ab347284719f818acb14f7cd80696c6fdf1bd34feee1955d7a72d2e64ce"},
352 | {file = "pydantic-1.10.21-cp37-cp37m-win_amd64.whl", hash = "sha256:46cffa24891b06269e12f7e1ec50b73f0c9ab4ce71c2caa4ccf1fb36845e1ff7"},
353 | {file = "pydantic-1.10.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:298d6f765e3c9825dfa78f24c1efd29af91c3ab1b763e1fd26ae4d9e1749e5c8"},
354 | {file = "pydantic-1.10.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2f4a2305f15eff68f874766d982114ac89468f1c2c0b97640e719cf1a078374"},
355 | {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35b263b60c519354afb3a60107d20470dd5250b3ce54c08753f6975c406d949b"},
356 | {file = "pydantic-1.10.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e23a97a6c2f2db88995496db9387cd1727acdacc85835ba8619dce826c0b11a6"},
357 | {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3c96fed246ccc1acb2df032ff642459e4ae18b315ecbab4d95c95cfa292e8517"},
358 | {file = "pydantic-1.10.21-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b92893ebefc0151474f682e7debb6ab38552ce56a90e39a8834734c81f37c8a9"},
359 | {file = "pydantic-1.10.21-cp38-cp38-win_amd64.whl", hash = "sha256:b8460bc256bf0de821839aea6794bb38a4c0fbd48f949ea51093f6edce0be459"},
360 | {file = "pydantic-1.10.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d387940f0f1a0adb3c44481aa379122d06df8486cc8f652a7b3b0caf08435f7"},
361 | {file = "pydantic-1.10.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:266ecfc384861d7b0b9c214788ddff75a2ea123aa756bcca6b2a1175edeca0fe"},
362 | {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61da798c05a06a362a2f8c5e3ff0341743e2818d0f530eaac0d6898f1b187f1f"},
363 | {file = "pydantic-1.10.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a621742da75ce272d64ea57bd7651ee2a115fa67c0f11d66d9dcfc18c2f1b106"},
364 | {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9e3e4000cd54ef455694b8be9111ea20f66a686fc155feda1ecacf2322b115da"},
365 | {file = "pydantic-1.10.21-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f198c8206640f4c0ef5a76b779241efb1380a300d88b1bce9bfe95a6362e674d"},
366 | {file = "pydantic-1.10.21-cp39-cp39-win_amd64.whl", hash = "sha256:e7f0cda108b36a30c8fc882e4fc5b7eec8ef584aa43aa43694c6a7b274fb2b56"},
367 | {file = "pydantic-1.10.21-py3-none-any.whl", hash = "sha256:db70c920cba9d05c69ad4a9e7f8e9e83011abb2c6490e561de9ae24aee44925c"},
368 | {file = "pydantic-1.10.21.tar.gz", hash = "sha256:64b48e2b609a6c22178a56c408ee1215a7206077ecb8a193e2fda31858b2362a"},
369 | ]
370 |
371 | [package.dependencies]
372 | typing-extensions = ">=4.2.0"
373 |
374 | [package.extras]
375 | dotenv = ["python-dotenv (>=0.10.4)"]
376 | email = ["email-validator (>=1.0.3)"]
377 |
378 | [[package]]
379 | name = "pyjwt"
380 | version = "2.4.0"
381 | description = "JSON Web Token implementation in Python"
382 | optional = false
383 | python-versions = ">=3.6"
384 | files = [
385 | {file = "PyJWT-2.4.0-py3-none-any.whl", hash = "sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf"},
386 | {file = "PyJWT-2.4.0.tar.gz", hash = "sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba"},
387 | ]
388 |
389 | [package.dependencies]
390 | cryptography = {version = ">=3.3.1", optional = true, markers = "extra == \"crypto\""}
391 |
392 | [package.extras]
393 | crypto = ["cryptography (>=3.3.1)"]
394 | dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.3.1)", "mypy", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"]
395 | docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
396 | tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
397 |
398 | [[package]]
399 | name = "python-dateutil"
400 | version = "2.8.2"
401 | description = "Extensions to the standard Python datetime module"
402 | optional = false
403 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
404 | files = [
405 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
406 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
407 | ]
408 |
409 | [package.dependencies]
410 | six = ">=1.5"
411 |
412 | [[package]]
413 | name = "python-dotenv"
414 | version = "0.20.0"
415 | description = "Read key-value pairs from a .env file and set them as environment variables"
416 | optional = false
417 | python-versions = ">=3.5"
418 | files = [
419 | {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"},
420 | {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"},
421 | ]
422 |
423 | [package.extras]
424 | cli = ["click (>=5.0)"]
425 |
426 | [[package]]
427 | name = "requests"
428 | version = "2.27.1"
429 | description = "Python HTTP for Humans."
430 | optional = false
431 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
432 | files = [
433 | {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
434 | {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
435 | ]
436 |
437 | [package.dependencies]
438 | certifi = ">=2017.4.17"
439 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
440 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
441 | urllib3 = ">=1.21.1,<1.27"
442 |
443 | [package.extras]
444 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
445 | use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"]
446 |
447 | [[package]]
448 | name = "six"
449 | version = "1.16.0"
450 | description = "Python 2 and 3 compatibility utilities"
451 | optional = false
452 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
453 | files = [
454 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
455 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
456 | ]
457 |
458 | [[package]]
459 | name = "sniffio"
460 | version = "1.2.0"
461 | description = "Sniff out which async library your code is running under"
462 | optional = false
463 | python-versions = ">=3.5"
464 | files = [
465 | {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
466 | {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
467 | ]
468 |
469 | [[package]]
470 | name = "starlette"
471 | version = "0.19.1"
472 | description = "The little ASGI library that shines."
473 | optional = false
474 | python-versions = ">=3.6"
475 | files = [
476 | {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
477 | {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
478 | ]
479 |
480 | [package.dependencies]
481 | anyio = ">=3.4.0,<5"
482 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
483 |
484 | [package.extras]
485 | full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
486 |
487 | [[package]]
488 | name = "svix"
489 | version = "0.41.2"
490 | description = "Svix"
491 | optional = false
492 | python-versions = ">=3.6"
493 | files = [
494 | {file = "svix-0.41.2.tar.gz", hash = "sha256:f81feae9553d5cfc5a1e76e1751b667915a4ba2b493b122d8207e48e0259947f"},
495 | ]
496 |
497 | [package.dependencies]
498 | python-dateutil = "*"
499 | urllib3 = ">=1.25.3"
500 |
501 | [[package]]
502 | name = "typing-extensions"
503 | version = "4.2.0"
504 | description = "Backported and Experimental Type Hints for Python 3.7+"
505 | optional = false
506 | python-versions = ">=3.7"
507 | files = [
508 | {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
509 | {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
510 | ]
511 |
512 | [[package]]
513 | name = "urllib3"
514 | version = "1.26.9"
515 | description = "HTTP library with thread-safe connection pooling, file post, and more."
516 | optional = false
517 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
518 | files = [
519 | {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
520 | {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
521 | ]
522 |
523 | [package.extras]
524 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
525 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"]
526 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
527 |
528 | [[package]]
529 | name = "uvicorn"
530 | version = "0.17.6"
531 | description = "The lightning-fast ASGI server."
532 | optional = false
533 | python-versions = ">=3.7"
534 | files = [
535 | {file = "uvicorn-0.17.6-py3-none-any.whl", hash = "sha256:19e2a0e96c9ac5581c01eb1a79a7d2f72bb479691acd2b8921fce48ed5b961a6"},
536 | {file = "uvicorn-0.17.6.tar.gz", hash = "sha256:5180f9d059611747d841a4a4c4ab675edf54c8489e97f96d0583ee90ac3bfc23"},
537 | ]
538 |
539 | [package.dependencies]
540 | asgiref = ">=3.4.0"
541 | click = ">=7.0"
542 | h11 = ">=0.8"
543 |
544 | [package.extras]
545 | standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchgod (>=0.6)", "websockets (>=10.0)"]
546 |
547 | [[package]]
548 | name = "vital"
549 | version = "1.1.22"
550 | description = ""
551 | optional = false
552 | python-versions = ">=3.8,<4.0"
553 | files = [
554 | {file = "vital-1.1.22-py3-none-any.whl", hash = "sha256:a89099544a7645bae397f78e93259175592d6601c70d5262f88e582f50017b93"},
555 | {file = "vital-1.1.22.tar.gz", hash = "sha256:33ac657656c0a44d606eb8621f6ae626239a33bdfc712c33c2428a7209b5a61b"},
556 | ]
557 |
558 | [package.dependencies]
559 | arrow = "*"
560 | auth0-python = "*"
561 | importlib-metadata = ">=3.7.3,<4.0.0"
562 | PyJWT = ">=2.0.1,<3.0.0"
563 | requests = "*"
564 | svix = ">=0.41.2,<0.42.0"
565 |
566 | [[package]]
567 | name = "zipp"
568 | version = "3.8.0"
569 | description = "Backport of pathlib-compatible object wrapper for zip files"
570 | optional = false
571 | python-versions = ">=3.7"
572 | files = [
573 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
574 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
575 | ]
576 |
577 | [package.extras]
578 | docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
579 | testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
580 |
581 | [metadata]
582 | lock-version = "2.0"
583 | python-versions = "^3.9"
584 | content-hash = "5934591ac9d12d306a60c2a71fafb4abadb93901af7ed3f9d997050fac1ad18d"
585 |
--------------------------------------------------------------------------------