feat(create-turbo): apply official-starter transform

This commit is contained in:
Turbobot 2024-08-11 23:39:29 -07:00 committed by DESKTOP-RTLN3BA\$punk
parent 55332d1ddb
commit 856eb69577
201 changed files with 2812 additions and 14413 deletions

View file

@ -1,4 +0,0 @@
DB_URL=supabase_url
DB_API_KEY=supabase_api_key
DB_EMAIl=email_address
DB_PASSWORD=password

View file

@ -1,13 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: FastAPI",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": ["src.main:app", "--reload"],
"jinja": true
}
]
}

View file

@ -1,5 +0,0 @@
{
"python.testing.pytestArgs": ["tests"],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

View file

@ -1,8 +0,0 @@
Generate requirements.txt using Poetry package manager:
```
poetry export --without-hashes --format=requirements.txt > requirements.txt
```
Python monorepo info:
https://medium.com/@ashley.e.shultz/python-mono-repo-with-only-built-in-tooling-7c2d52c2fc66

View file

@ -1,78 +0,0 @@
id,name,description
c76a2922-ba4c-4278-baab-44defb631236,Aberto,Opens locked doors
06485500-d023-4799-93fd-77f2c3341aa3,Accio,Summons objects
acbc0ae1-12e1-4813-b51e-09d22de40475,Aguamenti,Summons water
c9d2f389-a419-4f7e-8d3d-254959638019,Alohomora,Unlocks objects
018429a5-15d5-41af-bf8f-98a966733d77,Anapneo,Clears someone's airway
c828685c-52d2-466d-bcc6-fbcd8376cfb5,Aparecium,Reveals secret written messages
7fdd393c-2608-4ef3-9fd0-f691ad6f8b88,Apparate,A non-verbal transportation spell that allows a witch or wizard to instantly travel on the spot and appear at another location (disapparate is the opposite)
73886d47-2808-4861-ae40-956f4cb56272,Ascendio,Propells someone into the air
9a6b6854-8858-4b21-b761-12526a154597,Avada Kedavra,"Also known as The Killing Curse, the most evil spell in the Wizarding World; one of three Unforgivable Curses; Harry Potter is the only known witch or wizard to survive it"
b6f20bba-c0db-4ad2-8ac6-2a375a596287,Avis,Conjures a small flock of birds
48edfe4d-ddfc-49ac-8065-bd7e73c73778,Bat,Bogey Hex - Turns the target's boogers into bats
6bd8d5c1-9375-4b70-8d6e-ad019176c7a2,Bombardo,Creates an explosion
8fc19d10-3130-4b85-95c1-f2a51ba5ee3c,Brackium Emendo,Heals broken bones
f08c17fa-7bf9-49bf-9fba-a7806815bc80,Capacious Extremis,"Known as the Extension Charm, it's a complicated spell that can greatly expand or extend the capacity of an object or space without affecting it externally"
55dec867-ac07-4975-94c7-090f6fd25c86,Confundo,"Known as the Confundus Charm, it causes confusion of the target"
816d9fee-b78f-47b4-be46-b48626c013f9,Conjunctivitis Curse,Affects the eyes and sight of a target
58b8727a-6c0c-469c-b91d-8ac0cc0dd2d8,Crinus Muto,Changes hair color and style
f1e91049-e866-4f6f-9d87-d6fd366aecbf,Crucio,"One of three Unforgivable Curses, it causes unbearable pain in the target"
7324e645-8f41-4c83-a367-0d10a72906ff,Diffindo,Used to precisely cut an object
638072b9-b7ac-405d-914b-d9293c5f9d25,Disillusionment Charm,Causes the target to take on the appearance of its surroundings
b7643e32-ef9c-41b8-83f2-03f6b5015e04,Disapparate,A non-verbal transportation spell that allows a witch or wizard to instantly travel on the spot and leave for another location (apparate is the opposite)
20476c31-4f27-49ac-876f-a4c4028f1b5b,Engorgio,Causes rapid growth in the targeted object
ecb9a882-d6d7-495e-9958-a1a06902bb65,Episkey,Heals minor injuries
317ff981-ad65-421e-92fb-5f6647d95232,Expecto patronum,"The Patronus Charm is a powerful projection of hope and happiness that drives away Dementors; a corpeal Patronus takes the the respective animal form of the caster, while a non-corpeal appears as a wisp of light; at 13, Harry Potter was the youngest known witch or wizard to prouduce a corpeal Patronus"
60149246-91cf-44a5-8885-78a7acc4bf90,Erecto,"Allows a witch or wizard to build a structure, like a tent"
6d8138c3-0773-4c23-b0bf-aab0e5c6fd95,Evanesco,Vanishes objects
678474e6-fb30-4bf0-a18c-228f6b36592d,Expelliarmus,Forces an opponent to drop whatever's in their possession
31b38b6c-4775-4e20-815d-dbf302433de6,Ferula,A healing charm that conjures wraps and bandages for wounds
37d262c9-28ab-408f-9576-acf54ce50203,Fidelius Charm,"A complex charm that conceals a secret into the soul of a chosen ""Secret Keeper"". If a location is the subject of concealment, it becomes undetectable to others"
9121b557-0ebf-4b60-a119-9d1c5ff05dee,Fiendfyre Curse,"Conjures destructive, enormous enchanted flames"
de23025f-5e6a-4ec3-b827-5c526a922a89,Finite Incantatem,A general counter-spell that's used to reverse or counter already cast charms
d536cbe5-bc0f-49e5-b063-e02c231a3988,Furnunculus Curse,A jinx that causes a breakout of boils or pimples
7915b07a-d26e-4057-9083-e457643e57a6,Geminio,Duplicates objects
2a942514-7a19-4f0e-9353-171c573abcba,Glisseo,Transforms a staircase into a slide
a42028b6-67f5-463b-b759-452103533227,Homenum Revelio,Reveals the presence of another person
552cd4ee-2c67-48fd-ae20-a83773262a8a,Homonculus Charm,Detects anyone's true identity and location on a piece of parchment; used to create the Marauder's Map
2dfca7d2-ec9b-4150-b3f3-fd972a5fd1bc,Immobulus,Immobilises living targets
a49300cc-ddbf-4ff4-b8c2-e8bddbbe4118,Impedimenta,A temporary jinx that slows the movement of the target
e5c22d31-26f1-4c88-a586-d9c09cb88c1f,Incarcerous,Conjures ropes
a53ad5be-00ee-4254-b3c0-4cec60b0c034,Imperio,"One of the three Unforgivable Curses, it places the target under the complete control of the caster"
a3b34bf6-1ff7-4fe3-81ee-e617150f5da9,Impervius,Makes an object waterproof
de048df0-b227-4376-a29b-90fe6878d950,Incendio,Conjures flames
c4a4520b-b80d-49e8-9e5a-3ca0a7f376ca,Langlock,Causes the target's tongue to stick to the roof of their mouth
0da7cb76-dabc-46ff-b8e9-c23a4f03caea,Legilimens,Invading or navigating another's mind
723dd9c9-ee62-495b-9071-cddd16087b86,Levicorpus,Levitates the target by their ankle
8add16ef-b4b1-4e2b-a91e-80aa194da438,Locomotor Mortis,The Leg-Locker curse bounds the target's legs
3b7a10ce-3339-4a36-9493-292c8775e47b,Lumos,Illuminates the caster's wand
832edaca-dbff-4a57-80c7-1d8a827c8416,Morsmordre,Conjures and projects Lord Voldemort's Dark Mark
7f4b43e0-3356-43f9-9299-15ec37cfaf76,Mucus Ad Nauseam,Inflicts an extreme runny nose or cold
f86bbf7e-94ea-4c22-89fb-809af8214a85,Muffliato,Creates a buzzing sound in the target's ears to prevent eavesdropping
66be613d-532c-46d8-a3e9-f5a2d9cccf0c,Nox,"Reverses the lumos charm, extinguishing a wand's light"
9e3c0217-652a-4763-82f8-5519026a1ea6,Obliviate,Erases the target's memory
0af49753-c8ae-4748-87a7-b7cfc47d33a0,Obscuro,Conjures a blindfold
67e838c1-4623-414e-9a91-12125631dbad,Oculus Reparo,Repairs eyeglasses
12251f32-af9d-408f-a652-3a4cc9602bc0,Oppugno,Directs an object or person to attack a victim
da9eab7b-2c7c-42de-861c-fb254bd9423c,Petrificus Totalus,Temporarily freezes or petrifies the body of the target
3e5fd245-2ecf-40c4-937d-b2c2f9eee003,Periculum,Conjures flares/red sparks
ad5685f8-6e05-49b1-a41c-d72786001d72,Piertotum Locomotor,Incantation used to bring to life inanimate objects and artifacts
0a267162-0594-4372-a3d5-89382926f495,Protean Charm,Links objects together for better communication
8808aa30-39f4-400c-a0e5-1dcbad657931,Protego,"Casts an invisible shield around the caster, protecting against spells and objects (except for The Killing Curse)"
56742dd7-3c93-4085-bea3-971e88d81dc2,Reducto,Reduces the target to pieces
2f177949-1f80-4663-9840-da8197411f2a,Reducio,Shrinks an enlarged object to its regular size
358ecb3c-e684-492c-b706-47cbd1eae02e,Renneverate,Awakens or revives the target
1b7a8a4c-8d4f-4001-8155-e68f1198ef72,Reparifors,Heals magical ailments like poisoning or paralysis
799f31a3-799e-411f-b67c-a64e48a5f503,Reparo,Fixes broken objects
32dbeb89-0978-4037-ab1b-413d62be02c3,Rictusempra,A charm that disarms an opponent by tickling them
c9dc8bed-5834-4001-8fa1-852690d027f2,Riddikulus,"Used to defeat a Boggart, the charm allows the scary creature to assume a comedic form, disarming it"
14c47e18-cbf3-4aec-afd3-5473d18ee7c0,Scourgify,Cleans objects
3617c34c-e650-4e3b-a13a-651d18471225,Sectumsempra,Inflicts severe lacerations and haemorrhaging on the target
53747fb8-bdab-466e-90fb-ca75c66f3dd9,Serpensortia,Conjures a live snake
43d3d53e-7ab9-4145-bda7-d96be99c5d31,Silencio,Silences the target
d5f71164-fa43-4566-b537-8852859bde01,Sonorus,Amplifies the witch or wizard's voice
9ec3258c-bc2f-4427-8440-ebea450a44aa,Spongify,Softens the target
37110a48-07e3-4fd7-9aae-ac1145161e1e,Stupefy,The Stunning spell freezes objects and renders living targets unconscious
daeb6f2a-5aff-43e1-964a-a06da7f66a3c,Tarantallegra,"Aimed at the legs, causes uncontrollable dancing movement"
4eaa6532-3ef2-428d-922f-101aee3d66ed,Unbreakable Vow,A magically binding contract that results in the death of whoever breaks it
e23728b2-f6fd-4c70-a1d2-ce602940d873,Wingardium Leviosa,"Causes an object to levitate; but remember what Hermione said: ""It's Wing-gar-dium Levi-o-sa, make the 'gar' nice and long.'"""
1 id name description
2 c76a2922-ba4c-4278-baab-44defb631236 Aberto Opens locked doors
3 06485500-d023-4799-93fd-77f2c3341aa3 Accio Summons objects
4 acbc0ae1-12e1-4813-b51e-09d22de40475 Aguamenti Summons water
5 c9d2f389-a419-4f7e-8d3d-254959638019 Alohomora Unlocks objects
6 018429a5-15d5-41af-bf8f-98a966733d77 Anapneo Clears someone's airway
7 c828685c-52d2-466d-bcc6-fbcd8376cfb5 Aparecium Reveals secret written messages
8 7fdd393c-2608-4ef3-9fd0-f691ad6f8b88 Apparate A non-verbal transportation spell that allows a witch or wizard to instantly travel on the spot and appear at another location (disapparate is the opposite)
9 73886d47-2808-4861-ae40-956f4cb56272 Ascendio Propells someone into the air
10 9a6b6854-8858-4b21-b761-12526a154597 Avada Kedavra Also known as The Killing Curse, the most evil spell in the Wizarding World; one of three Unforgivable Curses; Harry Potter is the only known witch or wizard to survive it
11 b6f20bba-c0db-4ad2-8ac6-2a375a596287 Avis Conjures a small flock of birds
12 48edfe4d-ddfc-49ac-8065-bd7e73c73778 Bat Bogey Hex - Turns the target's boogers into bats
13 6bd8d5c1-9375-4b70-8d6e-ad019176c7a2 Bombardo Creates an explosion
14 8fc19d10-3130-4b85-95c1-f2a51ba5ee3c Brackium Emendo Heals broken bones
15 f08c17fa-7bf9-49bf-9fba-a7806815bc80 Capacious Extremis Known as the Extension Charm, it's a complicated spell that can greatly expand or extend the capacity of an object or space without affecting it externally
16 55dec867-ac07-4975-94c7-090f6fd25c86 Confundo Known as the Confundus Charm, it causes confusion of the target
17 816d9fee-b78f-47b4-be46-b48626c013f9 Conjunctivitis Curse Affects the eyes and sight of a target
18 58b8727a-6c0c-469c-b91d-8ac0cc0dd2d8 Crinus Muto Changes hair color and style
19 f1e91049-e866-4f6f-9d87-d6fd366aecbf Crucio One of three Unforgivable Curses, it causes unbearable pain in the target
20 7324e645-8f41-4c83-a367-0d10a72906ff Diffindo Used to precisely cut an object
21 638072b9-b7ac-405d-914b-d9293c5f9d25 Disillusionment Charm Causes the target to take on the appearance of its surroundings
22 b7643e32-ef9c-41b8-83f2-03f6b5015e04 Disapparate A non-verbal transportation spell that allows a witch or wizard to instantly travel on the spot and leave for another location (apparate is the opposite)
23 20476c31-4f27-49ac-876f-a4c4028f1b5b Engorgio Causes rapid growth in the targeted object
24 ecb9a882-d6d7-495e-9958-a1a06902bb65 Episkey Heals minor injuries
25 317ff981-ad65-421e-92fb-5f6647d95232 Expecto patronum The Patronus Charm is a powerful projection of hope and happiness that drives away Dementors; a corpeal Patronus takes the the respective animal form of the caster, while a non-corpeal appears as a wisp of light; at 13, Harry Potter was the youngest known witch or wizard to prouduce a corpeal Patronus
26 60149246-91cf-44a5-8885-78a7acc4bf90 Erecto Allows a witch or wizard to build a structure, like a tent
27 6d8138c3-0773-4c23-b0bf-aab0e5c6fd95 Evanesco Vanishes objects
28 678474e6-fb30-4bf0-a18c-228f6b36592d Expelliarmus Forces an opponent to drop whatever's in their possession
29 31b38b6c-4775-4e20-815d-dbf302433de6 Ferula A healing charm that conjures wraps and bandages for wounds
30 37d262c9-28ab-408f-9576-acf54ce50203 Fidelius Charm A complex charm that conceals a secret into the soul of a chosen "Secret Keeper". If a location is the subject of concealment, it becomes undetectable to others
31 9121b557-0ebf-4b60-a119-9d1c5ff05dee Fiendfyre Curse Conjures destructive, enormous enchanted flames
32 de23025f-5e6a-4ec3-b827-5c526a922a89 Finite Incantatem A general counter-spell that's used to reverse or counter already cast charms
33 d536cbe5-bc0f-49e5-b063-e02c231a3988 Furnunculus Curse A jinx that causes a breakout of boils or pimples
34 7915b07a-d26e-4057-9083-e457643e57a6 Geminio Duplicates objects
35 2a942514-7a19-4f0e-9353-171c573abcba Glisseo Transforms a staircase into a slide
36 a42028b6-67f5-463b-b759-452103533227 Homenum Revelio Reveals the presence of another person
37 552cd4ee-2c67-48fd-ae20-a83773262a8a Homonculus Charm Detects anyone's true identity and location on a piece of parchment; used to create the Marauder's Map
38 2dfca7d2-ec9b-4150-b3f3-fd972a5fd1bc Immobulus Immobilises living targets
39 a49300cc-ddbf-4ff4-b8c2-e8bddbbe4118 Impedimenta A temporary jinx that slows the movement of the target
40 e5c22d31-26f1-4c88-a586-d9c09cb88c1f Incarcerous Conjures ropes
41 a53ad5be-00ee-4254-b3c0-4cec60b0c034 Imperio One of the three Unforgivable Curses, it places the target under the complete control of the caster
42 a3b34bf6-1ff7-4fe3-81ee-e617150f5da9 Impervius Makes an object waterproof
43 de048df0-b227-4376-a29b-90fe6878d950 Incendio Conjures flames
44 c4a4520b-b80d-49e8-9e5a-3ca0a7f376ca Langlock Causes the target's tongue to stick to the roof of their mouth
45 0da7cb76-dabc-46ff-b8e9-c23a4f03caea Legilimens Invading or navigating another's mind
46 723dd9c9-ee62-495b-9071-cddd16087b86 Levicorpus Levitates the target by their ankle
47 8add16ef-b4b1-4e2b-a91e-80aa194da438 Locomotor Mortis The Leg-Locker curse bounds the target's legs
48 3b7a10ce-3339-4a36-9493-292c8775e47b Lumos Illuminates the caster's wand
49 832edaca-dbff-4a57-80c7-1d8a827c8416 Morsmordre Conjures and projects Lord Voldemort's Dark Mark
50 7f4b43e0-3356-43f9-9299-15ec37cfaf76 Mucus Ad Nauseam Inflicts an extreme runny nose or cold
51 f86bbf7e-94ea-4c22-89fb-809af8214a85 Muffliato Creates a buzzing sound in the target's ears to prevent eavesdropping
52 66be613d-532c-46d8-a3e9-f5a2d9cccf0c Nox Reverses the lumos charm, extinguishing a wand's light
53 9e3c0217-652a-4763-82f8-5519026a1ea6 Obliviate Erases the target's memory
54 0af49753-c8ae-4748-87a7-b7cfc47d33a0 Obscuro Conjures a blindfold
55 67e838c1-4623-414e-9a91-12125631dbad Oculus Reparo Repairs eyeglasses
56 12251f32-af9d-408f-a652-3a4cc9602bc0 Oppugno Directs an object or person to attack a victim
57 da9eab7b-2c7c-42de-861c-fb254bd9423c Petrificus Totalus Temporarily freezes or petrifies the body of the target
58 3e5fd245-2ecf-40c4-937d-b2c2f9eee003 Periculum Conjures flares/red sparks
59 ad5685f8-6e05-49b1-a41c-d72786001d72 Piertotum Locomotor Incantation used to bring to life inanimate objects and artifacts
60 0a267162-0594-4372-a3d5-89382926f495 Protean Charm Links objects together for better communication
61 8808aa30-39f4-400c-a0e5-1dcbad657931 Protego Casts an invisible shield around the caster, protecting against spells and objects (except for The Killing Curse)
62 56742dd7-3c93-4085-bea3-971e88d81dc2 Reducto Reduces the target to pieces
63 2f177949-1f80-4663-9840-da8197411f2a Reducio Shrinks an enlarged object to its regular size
64 358ecb3c-e684-492c-b706-47cbd1eae02e Renneverate Awakens or revives the target
65 1b7a8a4c-8d4f-4001-8155-e68f1198ef72 Reparifors Heals magical ailments like poisoning or paralysis
66 799f31a3-799e-411f-b67c-a64e48a5f503 Reparo Fixes broken objects
67 32dbeb89-0978-4037-ab1b-413d62be02c3 Rictusempra A charm that disarms an opponent by tickling them
68 c9dc8bed-5834-4001-8fa1-852690d027f2 Riddikulus Used to defeat a Boggart, the charm allows the scary creature to assume a comedic form, disarming it
69 14c47e18-cbf3-4aec-afd3-5473d18ee7c0 Scourgify Cleans objects
70 3617c34c-e650-4e3b-a13a-651d18471225 Sectumsempra Inflicts severe lacerations and haemorrhaging on the target
71 53747fb8-bdab-466e-90fb-ca75c66f3dd9 Serpensortia Conjures a live snake
72 43d3d53e-7ab9-4145-bda7-d96be99c5d31 Silencio Silences the target
73 d5f71164-fa43-4566-b537-8852859bde01 Sonorus Amplifies the witch or wizard's voice
74 9ec3258c-bc2f-4427-8440-ebea450a44aa Spongify Softens the target
75 37110a48-07e3-4fd7-9aae-ac1145161e1e Stupefy The Stunning spell freezes objects and renders living targets unconscious
76 daeb6f2a-5aff-43e1-964a-a06da7f66a3c Tarantallegra Aimed at the legs, causes uncontrollable dancing movement
77 4eaa6532-3ef2-428d-922f-101aee3d66ed Unbreakable Vow A magically binding contract that results in the death of whoever breaks it
78 e23728b2-f6fd-4c70-a1d2-ce602940d873 Wingardium Leviosa Causes an object to levitate; but remember what Hermione said: "It's Wing-gar-dium Levi-o-sa, make the 'gar' nice and long.'"

View file

@ -1,114 +0,0 @@
id,forename,surname,email
fd142190-f1d7-4ce2-bdb3-6ed6b3edc020,Patricia,Stimpson,patriciastimpson@hogwarts.com
f94086b8-03ae-4457-ba2c-e624d0980869,Lavender,Brown,lavenderbrown@hogwarts.com
ecca342d-d345-4fb3-8a85-ece848ab8938,Milicent,Bullstroude,milicentbullstroude@hogwarts.com
ec714982-e604-40d4-bd4c-dc5155506957,Morag,MacDougal,moragmacdougal@hogwarts.com
eaea5eb3-48a3-41c6-9ea5-c695299bab16,Lisa,Turpin,lisaturpin@hogwarts.com
e7f4554e-8193-4b16-a40b-a8b38a0c3e57,Graham,Montague,grahammontague@hogwarts.com
e65c8acb-0dfc-4f15-bcf8-d32b78811093,Rose,Zeller,rosezeller@hogwarts.com
e4653b01-76a5-4769-a6a2-1f2efaf89cbb,Rose,Weasley,roseweasley@hogwarts.com
e32dd37c-91cd-4950-8ef2-e2ba1b87bd75,Lily,Moon,lilymoon@hogwarts.com
dcdc063e-cf3e-48fc-b777-65922e899b38,Albus,Severus,albusseverus@hogwarts.com
d9cec110-a1d0-4437-9a55-dced475dfe6d,Andrew,Kirke,andrewkirke@hogwarts.com
d5c4daa3-c726-426a-aa98-fb40f3fba816,Cedric,Diggory,cedricdiggory@hogwarts.com
cf3707ad-e816-4b54-90d0-403800a06ecd,Emma,Dobbs,emmadobbs@hogwarts.com
cb263aed-289b-43ad-8647-db54b8a5fc92,Michael,Corner,michaelcorner@hogwarts.com
c8aed011-ab8f-46df-9e8d-dde938256ea9,Miles,Bletchley,milesbletchley@hogwarts.com
c74b1fae-4793-4b47-bec2-ee652beabce2,Ritchie,Coote,ritchiecoote@hogwarts.com
c61b5c80-2c8e-404f-88ca-349a6344f35c,Cassius,Warrington,cassiuswarrington@hogwarts.com
c5acae3e-1a05-4f1d-bb83-3f8c7639d84e,Mandy,Brocklehurst,mandybrocklehurst@hogwarts.com
c3b1f9a5-b87b-48bf-b00d-95b093ea6390,Ron,Weasley,ronweasley@hogwarts.com
c29cd5f9-d2c3-4be9-ba1c-04169cdf511b,Alicia,Spinet,aliciaspinet@hogwarts.com
bff82738-5bb0-4edc-9cec-f80d1af4801f,Vicky,Frobisher,vickyfrobisher@hogwarts.com
b78e6677-8bb4-4eb7-97cb-2f86677e27ea,Adrian,Pucey,adrianpucey@hogwarts.com
b634f0a1-7b48-49b6-b039-27f947ee76fd,Angelina,Johnson,angelinajohnson@hogwarts.com
b01be346-290b-4f65-9c88-a49922e116ee,Orla,Quirke,orlaquirke@hogwarts.com
af95bd8a-dfae-45bb-bc69-533860d34129,Draco,Malfoy,dracomalfoy@hogwarts.com
ae068570-8419-4063-bf61-ba4a0ef41fe3,Laura,Madley,lauramadley@hogwarts.com
a93b80a0-987d-4148-944d-16043df95e8c,Dennis,Creevey,denniscreevey@hogwarts.com
a506574f-c8cf-46c6-a8ac-2f805c25e49e,Graham,Pritchard,grahampritchard@hogwarts.com
a3e5ea64-b103-4f47-bc26-dc08b799c668,Eddie,Carmichael,eddiecarmichael@hogwarts.com
a31ddc78-af12-4978-929c-3cc8a00a833e,Gregory,Goyle,gregorygoyle@hogwarts.com
a01f6dbb-bad5-426b-a7df-f9613fa1021d,Euan,Abercrombie,euanabercrombie@hogwarts.com
9e3f7ce4-b9a7-4244-b709-dae5c1f1d4a8,Harry,Potter,harrypotter@hogwarts.com
9c8ce8c7-ae0a-4646-920f-09c071862f10,James,Potter,jamespotter@hogwarts.com
9ba0ca6e-4fba-410d-9b5e-e20694dde413,Melinda,Bobbin,melindabobbin@hogwarts.com
9ac09267-92ea-444a-a395-28f3b0f6fe6f,Terrence,Higgs,terrencehiggs@hogwarts.com
98546bab-8d5b-4627-95f6-38e306d58a91,Owen,Cauldwell,owencauldwell@hogwarts.com
979ab773-944f-4ff8-88be-943a4bc2c18a,Lee,Jordan,leejordan@hogwarts.com
938559ee-e8e5-4963-8437-e7da04fd1b31,Marcus,Belby,marcusbelby@hogwarts.com
9055a7b1-6ac9-4363-977c-4dec78572fad,Terry,Boot,terryboot@hogwarts.com
8f9aa40b-5d7c-441e-ad32-4564ecda3b70,Cho,Chang,chochang@hogwarts.com
8f3b8796-c7b9-442e-ac02-113d48306fc7,Percy,Weasley,percyweasley@hogwarts.com
8e557e86-28d5-433f-8ac1-d2cecfeb8fb7,Colin,Creevey,colincreevey@hogwarts.com
88886e27-9ce2-416f-9dd6-56d4cd94a4fb,Geoffrey,Hooper,geoffreyhooper@hogwarts.com
861c4cde-2f0f-4796-8d8f-9492e74b2573,Luna,Lovegood,lunalovegood@hogwarts.com
7f2f6207-8998-4f98-92c2-8d02898a82eb,Scorpius,Malfoy,scorpiusmalfoy@hogwarts.com
7cc5e694-850d-4c44-830b-7154e23bb5c3,Susan,Bones,susanbones@hogwarts.com
781f1061-5413-40c7-8a35-b078e2e969b1,Barnabas,the,barnabasthe@hogwarts.com
7772cb4e-5c33-405a-970d-c05cae167917,Daphne,Greengrass,daphnegreengrass@hogwarts.com
6fa93583-b935-4228-91e0-729e6713bdab,Zacharias,Smith,zachariassmith@hogwarts.com
6c4350a9-2356-4bba-96bd-0458c12d99b5,Romilda,Vane,romildavane@hogwarts.com
69c18f6a-cd97-4218-9f2f-740393e6eb1f,Padma,Patil,padmapatil@hogwarts.com
61b6d68e-4128-408c-9f71-9ef167cb0e69,Anthony,Goldstein,anthonygoldstein@hogwarts.com
58a287c2-8c7a-485a-b095-8c6dcfc7f31d,Lucian,Bole,lucianbole@hogwarts.com
57fe29d4-312a-4711-bd9a-c320253d9176,Victoire,Weasley,victoireweasley@hogwarts.com
575fbbc2-ac94-4c58-92f6-e5d75846da91,Jack,Sloper,jacksloper@hogwarts.com
4eef1e03-cf1c-4441-9119-a6e47a61f880,Kevin,Whitby,kevinwhitby@hogwarts.com
4c7e6819-a91a-45b2-a454-f931e4a7cce3,Hermione,Granger,hermionegranger@hogwarts.com
4a0f4c3b-14dc-4a9e-a2f8-da23734f5d34,Marietta,Edgecombe,mariettaedgecombe@hogwarts.com
48880498-3903-4914-bd11-ec650d803199,Natalie,McDonald,nataliemcdonald@hogwarts.com
47aa7511-59b9-4760-9bd7-822a1103177b,Theodore,Nott,theodorenott@hogwarts.com
458828b3-82a5-4cad-a784-e23215825765,Peregrine,Derrick,peregrinederrick@hogwarts.com
42915280-ba56-4ab8-8b17-9511ba2ab093,Penelope,Clearwater,penelopeclearwater@hogwarts.com
3db6dc51-b461-4fa4-a6e4-b1ff352221c5,Neville,Longbottom,nevillelongbottom@hogwarts.com
3d629315-1dbb-4e1e-840d-ffbf45bd5894,Sally-Anne,Perks,sally-anneperks@hogwarts.com
341e65d4-6917-48d7-80b2-1f9af607e95a,Cormac,McLaggen,cormacmclaggen@hogwarts.com
34155375-c8c0-415e-873a-b6483f0cbf17,Justin,Finch-Fletchley,justinfinch-fletchley@hogwarts.com
2f8db183-e935-4b91-884f-fb9effe42ab8,Malcolm,Baddock,malcolmbaddock@hogwarts.com
2b203c7e-7b3d-4f27-8b3c-11473904da73,Hugo,Weasley,hugoweasley@hogwarts.com
2a0615de-8aa4-41e7-9504-dd875f5f3f01,George,Weasley,georgeweasley@hogwarts.com
29adbbf0-417a-4c97-8467-adb5341f75e5,Eleanor,Branstone,eleanorbranstone@hogwarts.com
28e9fe6b-3ca5-41ca-8a14-b995e0fb398b,Jimmy,Peakes,jimmypeakes@hogwarts.com
2899e63f-ed02-4152-8ace-0270a068a70d,Pansy,Parkinson,pansyparkinson@hogwarts.com
28741184-263c-4000-b011-ca7c60466ef4,Fred,Weasley,fredweasley@hogwarts.com
2832bea8-7aad-4160-a748-442f5770d586,Katie,Bell,katiebell@hogwarts.com
26bd4437-73fa-4865-afdd-2fc1456f4592,Kenneth,Towler,kennethtowler@hogwarts.com
1fab149b-52b1-4ffe-be52-4eda25d98f5d,Blaise,Zabini,blaisezabini@hogwarts.com
1cd6dc64-01a9-4379-9cfd-1a7167ba1bb1,Ginny,Weasley,ginnyweasley@hogwarts.com
14aca981-2b60-413e-8f8e-3534961b534b,Millicent,Bulstrode,millicentbulstrode@hogwarts.com
1413e1b3-2903-4a47-a2d5-e8abc5ce8014,Seamus,Finnigan,seamusfinnigan@hogwarts.com
13a54f8a-7f68-4add-a1b4-49f60c8e7bcc,Eloise,Midgen,eloisemidgen@hogwarts.com
0e53860c-7679-49e4-891e-fb92286f0e5b,Demelza,Robins,demelzarobins@hogwarts.com
0e42ecbe-27b2-4940-9b03-00182a92c415,Marcus,Flint,marcusflint@hogwarts.com
0c80d701-fa23-4126-9711-efe5f3c4789a,Ernie,Macmillan,erniemacmillan@hogwarts.com
0af82694-e24f-45ec-a8d7-5bb1199ce631,Hannah,Abbott,hannahabbott@hogwarts.com
0a13bf8e-a763-44cc-ac76-c6c53a639809,Roger,Davies,rogerdavies@hogwarts.com
09396e81-d317-499f-b330-25b90ba17d20,Oliver,Wood,oliverwood@hogwarts.com
05bd5fd1-f347-45e6-8ec0-59b7f11c2aec,Stewart,Ackerley,stewartackerley@hogwarts.com
04f9eb45-d843-4e29-a7d3-0bd49ed87f85,Vincent,Crabbe,vincentcrabbe@hogwarts.com
0201cf73-8a86-4358-b232-2abaa23f09af,Parvati,Patil,parvatipatil@hogwarts.com
ca3827f0-375a-4891-aaa5-f5e8a5bad225,Minerva,McGonagall,minervamcgonagall@hogwarts.com
3569d265-bd27-44d8-88e8-82fb0a848374,Severus,Snape,severussnape@hogwarts.com
36bfefd0-e0bb-4d11-be98-d1ef6117a77a,Rubeus,Hagrid,rubeushagrid@hogwarts.com
b8f9095b-9de6-4d7d-83e0-4391af8f22e4,Remus,Lupin,remuslupin@hogwarts.com
2fb675cd-5505-4c8e-a54e-579e73bf4174,Horace,Slughorn,horaceslughorn@hogwarts.com
d58e7249-19d1-40bd-a43f-1da0497fe8aa,Dolores,Umbridge,doloresumbridge@hogwarts.com
b0620914-858d-46fc-8e6d-033c565e138b,Mrs,Norris,mrsnorris@hogwarts.com
2b82cfb8-0440-4a57-a030-6d75a40c0d98,Argus,Filch,argusfilch@hogwarts.com
b415c867-1cff-455e-b194-748662ac2cca,Albus,Dumbledore,albusdumbledore@hogwarts.com
e9457467-d10a-4893-afa9-19f9602b218a,Madam,Pomfrey,madampomfrey@hogwarts.com
ba19be27-178b-4594-95b7-51ba0e3ba1dd,Quirinus,Quirrel,quirinusquirrel@hogwarts.com
e8694719-a975-48fb-9523-f4cade1c38aa,Pomona,Sprout,pomonasprout@hogwarts.com
6ea188f3-d95c-407c-ab00-a0bec8678a71,Cuthbert,Binns,cuthbertbinns@hogwarts.com
a61e0783-7914-4f8d-a800-c409c06315cf,Filius,Flitwick,filiusflitwick@hogwarts.com
0a81c4f9-b80d-45a7-a4fd-9191453815a1,Madam,Hooch,madamhooch@hogwarts.com
3d687c4d-852e-470f-bac5-5a02758b1f8f,Gilderoy,Lockhart,gilderoylockhart@hogwarts.com
cdec9b95-c7a5-4623-ad12-6fa76d168588,Madame,Pince,madamepince@hogwarts.com
8ea29415-012d-4781-ba5f-d0de63a05abe,Sybill,Trelawney,sybilltrelawney@hogwarts.com
58f2cf41-392c-4e84-b441-dbbce585f78d,Septima,Vector,septimavector@hogwarts.com
99d3ce6b-6a45-495a-a7c6-132203697d45,Aurora,Sinistra,aurorasinistra@hogwarts.com
41ebe856-f0f4-4c77-8795-4735d3a87f3d,Alastor,Moody,alastormoody@hogwarts.com
b48c5b8a-4066-4c24-ba26-7677f5ed2b6f,Wilhelmina,Grubbly-Plank,wilhelminagrubbly-plank@hogwarts.com
c4e73590-3ee2-4125-87fb-692dd991819b,Galatea,Merrythought,galateamerrythought@hogwarts.com
61d78dce-890b-4f02-844f-b41d66553802,Charity,Burbage,charityburbage@hogwarts.com
1 id forename surname email
2 fd142190-f1d7-4ce2-bdb3-6ed6b3edc020 Patricia Stimpson patriciastimpson@hogwarts.com
3 f94086b8-03ae-4457-ba2c-e624d0980869 Lavender Brown lavenderbrown@hogwarts.com
4 ecca342d-d345-4fb3-8a85-ece848ab8938 Milicent Bullstroude milicentbullstroude@hogwarts.com
5 ec714982-e604-40d4-bd4c-dc5155506957 Morag MacDougal moragmacdougal@hogwarts.com
6 eaea5eb3-48a3-41c6-9ea5-c695299bab16 Lisa Turpin lisaturpin@hogwarts.com
7 e7f4554e-8193-4b16-a40b-a8b38a0c3e57 Graham Montague grahammontague@hogwarts.com
8 e65c8acb-0dfc-4f15-bcf8-d32b78811093 Rose Zeller rosezeller@hogwarts.com
9 e4653b01-76a5-4769-a6a2-1f2efaf89cbb Rose Weasley roseweasley@hogwarts.com
10 e32dd37c-91cd-4950-8ef2-e2ba1b87bd75 Lily Moon lilymoon@hogwarts.com
11 dcdc063e-cf3e-48fc-b777-65922e899b38 Albus Severus albusseverus@hogwarts.com
12 d9cec110-a1d0-4437-9a55-dced475dfe6d Andrew Kirke andrewkirke@hogwarts.com
13 d5c4daa3-c726-426a-aa98-fb40f3fba816 Cedric Diggory cedricdiggory@hogwarts.com
14 cf3707ad-e816-4b54-90d0-403800a06ecd Emma Dobbs emmadobbs@hogwarts.com
15 cb263aed-289b-43ad-8647-db54b8a5fc92 Michael Corner michaelcorner@hogwarts.com
16 c8aed011-ab8f-46df-9e8d-dde938256ea9 Miles Bletchley milesbletchley@hogwarts.com
17 c74b1fae-4793-4b47-bec2-ee652beabce2 Ritchie Coote ritchiecoote@hogwarts.com
18 c61b5c80-2c8e-404f-88ca-349a6344f35c Cassius Warrington cassiuswarrington@hogwarts.com
19 c5acae3e-1a05-4f1d-bb83-3f8c7639d84e Mandy Brocklehurst mandybrocklehurst@hogwarts.com
20 c3b1f9a5-b87b-48bf-b00d-95b093ea6390 Ron Weasley ronweasley@hogwarts.com
21 c29cd5f9-d2c3-4be9-ba1c-04169cdf511b Alicia Spinet aliciaspinet@hogwarts.com
22 bff82738-5bb0-4edc-9cec-f80d1af4801f Vicky Frobisher vickyfrobisher@hogwarts.com
23 b78e6677-8bb4-4eb7-97cb-2f86677e27ea Adrian Pucey adrianpucey@hogwarts.com
24 b634f0a1-7b48-49b6-b039-27f947ee76fd Angelina Johnson angelinajohnson@hogwarts.com
25 b01be346-290b-4f65-9c88-a49922e116ee Orla Quirke orlaquirke@hogwarts.com
26 af95bd8a-dfae-45bb-bc69-533860d34129 Draco Malfoy dracomalfoy@hogwarts.com
27 ae068570-8419-4063-bf61-ba4a0ef41fe3 Laura Madley lauramadley@hogwarts.com
28 a93b80a0-987d-4148-944d-16043df95e8c Dennis Creevey denniscreevey@hogwarts.com
29 a506574f-c8cf-46c6-a8ac-2f805c25e49e Graham Pritchard grahampritchard@hogwarts.com
30 a3e5ea64-b103-4f47-bc26-dc08b799c668 Eddie Carmichael eddiecarmichael@hogwarts.com
31 a31ddc78-af12-4978-929c-3cc8a00a833e Gregory Goyle gregorygoyle@hogwarts.com
32 a01f6dbb-bad5-426b-a7df-f9613fa1021d Euan Abercrombie euanabercrombie@hogwarts.com
33 9e3f7ce4-b9a7-4244-b709-dae5c1f1d4a8 Harry Potter harrypotter@hogwarts.com
34 9c8ce8c7-ae0a-4646-920f-09c071862f10 James Potter jamespotter@hogwarts.com
35 9ba0ca6e-4fba-410d-9b5e-e20694dde413 Melinda Bobbin melindabobbin@hogwarts.com
36 9ac09267-92ea-444a-a395-28f3b0f6fe6f Terrence Higgs terrencehiggs@hogwarts.com
37 98546bab-8d5b-4627-95f6-38e306d58a91 Owen Cauldwell owencauldwell@hogwarts.com
38 979ab773-944f-4ff8-88be-943a4bc2c18a Lee Jordan leejordan@hogwarts.com
39 938559ee-e8e5-4963-8437-e7da04fd1b31 Marcus Belby marcusbelby@hogwarts.com
40 9055a7b1-6ac9-4363-977c-4dec78572fad Terry Boot terryboot@hogwarts.com
41 8f9aa40b-5d7c-441e-ad32-4564ecda3b70 Cho Chang chochang@hogwarts.com
42 8f3b8796-c7b9-442e-ac02-113d48306fc7 Percy Weasley percyweasley@hogwarts.com
43 8e557e86-28d5-433f-8ac1-d2cecfeb8fb7 Colin Creevey colincreevey@hogwarts.com
44 88886e27-9ce2-416f-9dd6-56d4cd94a4fb Geoffrey Hooper geoffreyhooper@hogwarts.com
45 861c4cde-2f0f-4796-8d8f-9492e74b2573 Luna Lovegood lunalovegood@hogwarts.com
46 7f2f6207-8998-4f98-92c2-8d02898a82eb Scorpius Malfoy scorpiusmalfoy@hogwarts.com
47 7cc5e694-850d-4c44-830b-7154e23bb5c3 Susan Bones susanbones@hogwarts.com
48 781f1061-5413-40c7-8a35-b078e2e969b1 Barnabas the barnabasthe@hogwarts.com
49 7772cb4e-5c33-405a-970d-c05cae167917 Daphne Greengrass daphnegreengrass@hogwarts.com
50 6fa93583-b935-4228-91e0-729e6713bdab Zacharias Smith zachariassmith@hogwarts.com
51 6c4350a9-2356-4bba-96bd-0458c12d99b5 Romilda Vane romildavane@hogwarts.com
52 69c18f6a-cd97-4218-9f2f-740393e6eb1f Padma Patil padmapatil@hogwarts.com
53 61b6d68e-4128-408c-9f71-9ef167cb0e69 Anthony Goldstein anthonygoldstein@hogwarts.com
54 58a287c2-8c7a-485a-b095-8c6dcfc7f31d Lucian Bole lucianbole@hogwarts.com
55 57fe29d4-312a-4711-bd9a-c320253d9176 Victoire Weasley victoireweasley@hogwarts.com
56 575fbbc2-ac94-4c58-92f6-e5d75846da91 Jack Sloper jacksloper@hogwarts.com
57 4eef1e03-cf1c-4441-9119-a6e47a61f880 Kevin Whitby kevinwhitby@hogwarts.com
58 4c7e6819-a91a-45b2-a454-f931e4a7cce3 Hermione Granger hermionegranger@hogwarts.com
59 4a0f4c3b-14dc-4a9e-a2f8-da23734f5d34 Marietta Edgecombe mariettaedgecombe@hogwarts.com
60 48880498-3903-4914-bd11-ec650d803199 Natalie McDonald nataliemcdonald@hogwarts.com
61 47aa7511-59b9-4760-9bd7-822a1103177b Theodore Nott theodorenott@hogwarts.com
62 458828b3-82a5-4cad-a784-e23215825765 Peregrine Derrick peregrinederrick@hogwarts.com
63 42915280-ba56-4ab8-8b17-9511ba2ab093 Penelope Clearwater penelopeclearwater@hogwarts.com
64 3db6dc51-b461-4fa4-a6e4-b1ff352221c5 Neville Longbottom nevillelongbottom@hogwarts.com
65 3d629315-1dbb-4e1e-840d-ffbf45bd5894 Sally-Anne Perks sally-anneperks@hogwarts.com
66 341e65d4-6917-48d7-80b2-1f9af607e95a Cormac McLaggen cormacmclaggen@hogwarts.com
67 34155375-c8c0-415e-873a-b6483f0cbf17 Justin Finch-Fletchley justinfinch-fletchley@hogwarts.com
68 2f8db183-e935-4b91-884f-fb9effe42ab8 Malcolm Baddock malcolmbaddock@hogwarts.com
69 2b203c7e-7b3d-4f27-8b3c-11473904da73 Hugo Weasley hugoweasley@hogwarts.com
70 2a0615de-8aa4-41e7-9504-dd875f5f3f01 George Weasley georgeweasley@hogwarts.com
71 29adbbf0-417a-4c97-8467-adb5341f75e5 Eleanor Branstone eleanorbranstone@hogwarts.com
72 28e9fe6b-3ca5-41ca-8a14-b995e0fb398b Jimmy Peakes jimmypeakes@hogwarts.com
73 2899e63f-ed02-4152-8ace-0270a068a70d Pansy Parkinson pansyparkinson@hogwarts.com
74 28741184-263c-4000-b011-ca7c60466ef4 Fred Weasley fredweasley@hogwarts.com
75 2832bea8-7aad-4160-a748-442f5770d586 Katie Bell katiebell@hogwarts.com
76 26bd4437-73fa-4865-afdd-2fc1456f4592 Kenneth Towler kennethtowler@hogwarts.com
77 1fab149b-52b1-4ffe-be52-4eda25d98f5d Blaise Zabini blaisezabini@hogwarts.com
78 1cd6dc64-01a9-4379-9cfd-1a7167ba1bb1 Ginny Weasley ginnyweasley@hogwarts.com
79 14aca981-2b60-413e-8f8e-3534961b534b Millicent Bulstrode millicentbulstrode@hogwarts.com
80 1413e1b3-2903-4a47-a2d5-e8abc5ce8014 Seamus Finnigan seamusfinnigan@hogwarts.com
81 13a54f8a-7f68-4add-a1b4-49f60c8e7bcc Eloise Midgen eloisemidgen@hogwarts.com
82 0e53860c-7679-49e4-891e-fb92286f0e5b Demelza Robins demelzarobins@hogwarts.com
83 0e42ecbe-27b2-4940-9b03-00182a92c415 Marcus Flint marcusflint@hogwarts.com
84 0c80d701-fa23-4126-9711-efe5f3c4789a Ernie Macmillan erniemacmillan@hogwarts.com
85 0af82694-e24f-45ec-a8d7-5bb1199ce631 Hannah Abbott hannahabbott@hogwarts.com
86 0a13bf8e-a763-44cc-ac76-c6c53a639809 Roger Davies rogerdavies@hogwarts.com
87 09396e81-d317-499f-b330-25b90ba17d20 Oliver Wood oliverwood@hogwarts.com
88 05bd5fd1-f347-45e6-8ec0-59b7f11c2aec Stewart Ackerley stewartackerley@hogwarts.com
89 04f9eb45-d843-4e29-a7d3-0bd49ed87f85 Vincent Crabbe vincentcrabbe@hogwarts.com
90 0201cf73-8a86-4358-b232-2abaa23f09af Parvati Patil parvatipatil@hogwarts.com
91 ca3827f0-375a-4891-aaa5-f5e8a5bad225 Minerva McGonagall minervamcgonagall@hogwarts.com
92 3569d265-bd27-44d8-88e8-82fb0a848374 Severus Snape severussnape@hogwarts.com
93 36bfefd0-e0bb-4d11-be98-d1ef6117a77a Rubeus Hagrid rubeushagrid@hogwarts.com
94 b8f9095b-9de6-4d7d-83e0-4391af8f22e4 Remus Lupin remuslupin@hogwarts.com
95 2fb675cd-5505-4c8e-a54e-579e73bf4174 Horace Slughorn horaceslughorn@hogwarts.com
96 d58e7249-19d1-40bd-a43f-1da0497fe8aa Dolores Umbridge doloresumbridge@hogwarts.com
97 b0620914-858d-46fc-8e6d-033c565e138b Mrs Norris mrsnorris@hogwarts.com
98 2b82cfb8-0440-4a57-a030-6d75a40c0d98 Argus Filch argusfilch@hogwarts.com
99 b415c867-1cff-455e-b194-748662ac2cca Albus Dumbledore albusdumbledore@hogwarts.com
100 e9457467-d10a-4893-afa9-19f9602b218a Madam Pomfrey madampomfrey@hogwarts.com
101 ba19be27-178b-4594-95b7-51ba0e3ba1dd Quirinus Quirrel quirinusquirrel@hogwarts.com
102 e8694719-a975-48fb-9523-f4cade1c38aa Pomona Sprout pomonasprout@hogwarts.com
103 6ea188f3-d95c-407c-ab00-a0bec8678a71 Cuthbert Binns cuthbertbinns@hogwarts.com
104 a61e0783-7914-4f8d-a800-c409c06315cf Filius Flitwick filiusflitwick@hogwarts.com
105 0a81c4f9-b80d-45a7-a4fd-9191453815a1 Madam Hooch madamhooch@hogwarts.com
106 3d687c4d-852e-470f-bac5-5a02758b1f8f Gilderoy Lockhart gilderoylockhart@hogwarts.com
107 cdec9b95-c7a5-4623-ad12-6fa76d168588 Madame Pince madamepince@hogwarts.com
108 8ea29415-012d-4781-ba5f-d0de63a05abe Sybill Trelawney sybilltrelawney@hogwarts.com
109 58f2cf41-392c-4e84-b441-dbbce585f78d Septima Vector septimavector@hogwarts.com
110 99d3ce6b-6a45-495a-a7c6-132203697d45 Aurora Sinistra aurorasinistra@hogwarts.com
111 41ebe856-f0f4-4c77-8795-4735d3a87f3d Alastor Moody alastormoody@hogwarts.com
112 b48c5b8a-4066-4c24-ba26-7677f5ed2b6f Wilhelmina Grubbly-Plank wilhelminagrubbly-plank@hogwarts.com
113 c4e73590-3ee2-4125-87fb-692dd991819b Galatea Merrythought galateamerrythought@hogwarts.com
114 61d78dce-890b-4f02-844f-b41d66553802 Charity Burbage charityburbage@hogwarts.com

View file

@ -1,8 +0,0 @@
{
"name": "api",
"version": "0.0.0",
"scripts": {
"dev": ".\\.venv\\Scripts\\python run.py",
"generate:requirements": "poetry export --without-hashes --format=requirements.txt > requirements.txt"
}
}

1789
apps/api/poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
[virtualenvs]
in-project = true
create = true

View file

@ -1,24 +0,0 @@
[tool.poetry]
name = "api"
version = "0.1.0"
description = ""
authors = ["cording12 <joncording12@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.9"
fastapi = "^0.109.2"
uvicorn = "^0.27.1"
email-validator = "^2.1.0"
pydantic-settings = "^2.2.1"
python-dotenv = "^1.0.1"
supabase-py-async = "^2.5.5"
[tool.poetry.dev-dependencies]
isort = "^5.10.1"
black = "^22.6.0"
pytest = "^8.0.1"
pylint-pydantic = "^0.3.2"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View file

@ -1,54 +0,0 @@
aiohttp==3.9.3; python_version >= "3.9" and python_version < "4.0"
aiosignal==1.3.1; python_version >= "3.9" and python_version < "4.0"
annotated-types==0.6.0; python_version >= "3.8"
anyio==4.3.0; python_version >= "3.9" and python_version < "4.0"
argcomplete==3.2.3; python_version >= "3.9" and python_version < "4.0"
async-timeout==4.0.3; python_version >= "3.9" and python_version < "3.11"
attrs==23.2.0; python_version >= "3.9" and python_version < "4.0"
certifi==2024.2.2; python_version >= "3.9" and python_version < "4.0"
charset-normalizer==3.3.2; python_version >= "3.9" and python_version < "4.0" and python_full_version >= "3.7.0"
click==8.1.7; python_version >= "3.8"
colorama==0.4.6; python_version >= "3.9" and python_full_version < "3.0.0" and platform_system == "Windows" and python_version < "4.0" or platform_system == "Windows" and python_version >= "3.9" and python_full_version >= "3.7.0" and python_version < "4.0"
commitizen==3.18.4; python_version >= "3.9" and python_version < "4.0"
decli==0.6.1; python_version >= "3.9" and python_version < "4.0"
deprecation==2.1.0; python_version >= "3.9" and python_version < "4.0"
dnspython==2.6.1; python_version >= "3.8"
email-validator==2.1.1; python_version >= "3.8"
exceptiongroup==1.2.0; python_version < "3.11" and python_version >= "3.8"
fastapi==0.109.2; python_version >= "3.8"
frozenlist==1.4.1; python_version >= "3.9" and python_version < "4.0"
gotrue==2.4.1; python_version >= "3.9" and python_version < "4.0"
h11==0.14.0; python_version >= "3.9" and python_version < "4.0"
httpcore==1.0.4; python_version >= "3.9" and python_version < "4.0"
httpx==0.25.2; python_version >= "3.9" and python_version < "4.0"
idna==3.6; python_version >= "3.9" and python_version < "4.0"
importlib-metadata==7.0.2; python_version >= "3.9" and python_version < "4.0"
jinja2==3.1.3; python_version >= "3.9" and python_version < "4.0"
markupsafe==2.1.5; python_version >= "3.9" and python_version < "4.0"
multidict==6.0.5; python_version >= "3.9" and python_version < "4.0"
packaging==24.0; python_version >= "3.9" and python_version < "4.0"
postgrest==0.16.1; python_version >= "3.9" and python_version < "4.0"
prompt-toolkit==3.0.36; python_version >= "3.9" and python_version < "4.0" and python_full_version >= "3.6.2"
pydantic-core==2.16.3; python_version >= "3.8"
pydantic-settings==2.2.1; python_version >= "3.8"
pydantic==2.6.4; python_version >= "3.9" and python_version < "4.0"
python-dateutil==2.9.0.post0; python_version >= "3.9" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.9" and python_version < "4.0" and python_full_version >= "3.3.0"
python-dotenv==1.0.1; python_version >= "3.8"
pyyaml==6.0.1; python_version >= "3.9" and python_version < "4.0"
questionary==2.0.1; python_version >= "3.9" and python_version < "4.0"
realtime==1.0.2; python_version >= "3.9" and python_version < "4.0"
six==1.16.0; python_version >= "3.9" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.9" and python_version < "4.0" and python_full_version >= "3.3.0"
sniffio==1.3.1; python_version >= "3.9" and python_version < "4.0"
starlette==0.36.3; python_version >= "3.8"
storage3==0.7.3; python_version >= "3.9" and python_version < "4.0"
strenum==0.4.15; python_version >= "3.9" and python_version < "4.0"
supabase-py-async==2.5.5; python_version >= "3.9" and python_version < "4.0"
supafunc==0.4.0; python_version >= "3.9" and python_version < "4.0"
termcolor==2.4.0; python_version >= "3.9" and python_version < "4.0"
tomlkit==0.12.4; python_version >= "3.9" and python_version < "4.0"
typing-extensions==4.10.0; python_version < "3.10" and python_version >= "3.9"
uvicorn==0.27.1; python_version >= "3.8"
wcwidth==0.2.13; python_version >= "3.9" and python_version < "4.0" and python_full_version >= "3.6.2"
websockets==11.0.3; python_version >= "3.9" and python_version < "4.0"
yarl==1.9.4; python_version >= "3.9" and python_version < "4.0"
zipp==3.18.1; python_version >= "3.9" and python_version < "4.0"

View file

@ -1,4 +0,0 @@
import uvicorn
if __name__ == "__main__":
uvicorn.run("src.main:app", reload=True)

View file

@ -1 +0,0 @@
__version__ = "0.1.0"

View file

@ -1,6 +0,0 @@
from fastapi import APIRouter
from src.api.api_v1.endpoints import users, spells
api_router = APIRouter()
api_router.include_router(users.router, prefix="/users", tags=["users"], responses={404: {"description": "Not found"}})
api_router.include_router(spells.router, prefix="/spells", tags=["spells"], responses={404: {"description": "Not found"}})

View file

@ -1,63 +0,0 @@
from typing import Literal, Optional, Union
from fastapi import APIRouter, HTTPException
from src.api.deps import SessionDep
from src.crud import spell
from src.schemas import Spell, SpellSearchResults
router = APIRouter()
@router.get("/get/", status_code=200, response_model=Spell)
async def get_spell(session: SessionDep, spell_id: str) -> Spell:
"""Returns a spell from a spell_id.
**Returns:**
- spell: spell object.
"""
return await spell.get(session, id=spell_id)
@router.get("/get-all/", status_code=200, response_model=list[Spell])
async def get_all_spells(session: SessionDep) -> list[Spell]:
"""Returns a list of all spells.
**Returns:**
- list[spell]: List of all spells.
"""
return await spell.get_all(session)
@router.get("/search/", status_code=200, response_model=SpellSearchResults)
async def search_spells(
session: SessionDep,
search_on: Literal["id", "name", "description"] = "name",
keyword: Optional[Union[str, int]] = None,
max_results: Optional[int] = 10,
) -> SpellSearchResults:
"""
Search for spells based on a keyword and return the top `max_results` items.
**Args:**
- search_on (str, optional): The field to perform the search on. Defaults to "name".
- keyword (str, optional): The keyword to search for. Defaults to None.
- max_results (int, optional): The maximum number of search results to return. Defaults to 10.
**Returns:**
- SpellSearchResults: Object containing a list of the top `max_results` items that match the keyword.
"""
if not keyword:
results = await spell.get_all(session)
return SpellSearchResults(results=results)
results = await spell.search_all(
session, field=search_on, search_value=keyword, max_results=max_results
)
if not results:
raise HTTPException(
status_code=404, detail="No spells found matching the search criteria"
)
return SpellSearchResults(results=results)

View file

@ -1,76 +0,0 @@
from typing import Literal, Optional, Union
from fastapi import APIRouter, HTTPException
from src.api.deps import SessionDep
from src.crud import user
from src.schemas import User, UserCreate, UserSearchResults
router = APIRouter()
@router.get("/get/", status_code=200, response_model=User)
async def get_user(session: SessionDep, user_id: str) -> User:
"""Returns a user from a user_id.
**Returns:**
- User: User object.
"""
return await user.get(session, id=user_id)
@router.get("/get-all/", status_code=200, response_model=list[User])
async def get_all_users(session: SessionDep) -> list[User]:
"""Returns a list of all users.
**Returns:**
- list[User]: List of all users.
"""
return await user.get_all(session)
@router.get("/search/", status_code=200, response_model=UserSearchResults)
async def search_users(
session: SessionDep,
search_on: Literal["id", "email", "forename", "surname"] = "email",
keyword: Optional[Union[str, int]] = None,
max_results: Optional[int] = 10,
) -> UserSearchResults:
"""
Search for users based on a keyword and return the top `max_results` items.
**Args:**
- keyword (str, optional): The keyword to search for. Defaults to None.
- max_results (int, optional): The maximum number of search results to return. Defaults to 10.
- search_on (str, optional): The field to perform the search on. Defaults to "email".
**Returns:**
- UserSearchResults: Object containing a list of the top `max_results` items that match the keyword.
"""
if not keyword:
results = await user.get_all(session)
return UserSearchResults(results=results)
results = await user.search_all(
session, field=search_on, search_value=keyword, max_results=max_results
)
if not results:
raise HTTPException(
status_code=404, detail="No users found matching the search criteria"
)
return UserSearchResults(results=results)
@router.post("/create", status_code=201, response_model=User)
async def create_user(user_in: UserCreate, session: SessionDep) -> User:
"""Craete a new user.
**Args:**
- user_in (UserCreate): JSON of the user to create. Forename, surname and email. Email must be unique.
**Returns:**
- User: User object
"""
return await user.create(session, obj_in=user_in)

View file

@ -1,30 +0,0 @@
from typing import Annotated
from fastapi import Depends, HTTPException
from supabase_py_async import AsyncClient, create_client
from supabase_py_async.lib.client_options import ClientOptions
from src.config import settings
async def get_db() -> AsyncClient:
client: AsyncClient | None = None
try:
client = await create_client(
settings.DB_URL,
settings.DB_API_KEY,
options=ClientOptions(
postgrest_client_timeout=10, storage_client_timeout=10
),
)
# client = await client.auth.sign_in_with_password(
# {"email": settings.DB_EMAIL, "password": settings.DB_PASSWORD}
# )
yield client
except Exception as e:
print(e)
raise
SessionDep = Annotated[AsyncClient, Depends(get_db)]

View file

@ -1,30 +0,0 @@
import os
from dotenv import load_dotenv
from pydantic_settings import BaseSettings, SettingsConfigDict
load_dotenv()
# If the environment is Gitpod, the root path will be the workspace cluster host
# If not using gitpod, you can delete this if statement, but keep the else clause
if os.getenv("USER") == "gitpod":
ROOT_PATH = f"https://8000-cording12-nextfastturbo-qqfo0frc496.{os.getenv('GITPOD_WORKSPACE_CLUSTER_HOST')}"
else:
# Otherwise, the root path will be the local host. ROOT_PATH is an env var configured in Vercel deployment.
# The value for production is equal to the root path of the deployment URL in Vercel.
ROOT_PATH = os.getenv("ROOT_PATH", "http://127.0.0.1:8000")
class Settings(BaseSettings):
PROJECT_NAME: str = "FastAPI App"
PROJECT_DESCRIPTION: str = "A simple FastAPI app"
DB_URL: str = os.getenv("DB_URL")
DB_API_KEY: str = os.getenv("DB_API_KEY")
DB_EMAIL: str = os.getenv("DB_EMAIL")
DB_PASSWORD: str = os.getenv("DB_PASSWORD")
model_config = SettingsConfigDict(env_file=".env")
API_VERSION: str = "/api/v1"
ROOT: str = ROOT_PATH
settings = Settings()

View file

@ -1,2 +0,0 @@
from .crud_spell import spell
from .crud_user import user

View file

@ -1,74 +0,0 @@
from typing import Generic, Optional, TypeVar
from supabase_py_async import AsyncClient
from src.schemas.base import CreateBase, ResponseBase, UpdateBase
ModelType = TypeVar("ModelType", bound=ResponseBase)
CreateSchemaType = TypeVar("CreateSchemaType", bound=CreateBase)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=UpdateBase)
class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
def __init__(self, model: type[ModelType]):
"""CRUD object with default methods to do CRUD ops
Args:
model (type[ModelType]): Model class type
"""
self.model = model
async def get(self, db: AsyncClient, *, id: str) -> Optional[ModelType]:
"""get by table_name by id"""
data, count = (
await db.table(self.model.table_name).select("*").eq("id", id).execute()
)
_, got = data
return self.model(**got[0]) if got else None
async def get_all(self, db: AsyncClient) -> list[ModelType]:
"""get all by table_name"""
data, count = await db.table(self.model.table_name).select("*").execute()
_, got = data
return [self.model(**item) for item in got]
async def search_all(
self, db: AsyncClient, *, field: str, search_value: str, max_results: int
) -> list[ModelType]:
"""search all by table_name"""
data, count = (
await db.table(self.model.table_name)
.select("*")
.ilike(field, f"%{search_value}%")
.limit(max_results)
.execute()
)
_, got = data
return [self.model(**item) for item in got]
async def create(self, db: AsyncClient, *, obj_in: CreateSchemaType) -> ModelType:
"""create by CreateSchemaType"""
data, count = (
await db.table(self.model.table_name).insert(obj_in.model_dump()).execute()
)
_, created = data
return self.model(**created[0])
async def update(self, db: AsyncClient, *, obj_in: UpdateSchemaType) -> ModelType:
"""update by UpdateSchemaType"""
data, count = (
await db.table(self.model.table_name)
.update(obj_in.model_dump())
.eq("id", obj_in.id)
.execute()
)
_, updated = data
return self.model(**updated[0])
async def delete(self, db: AsyncClient, *, id: str) -> ModelType:
"""remove by UpdateSchemaType"""
data, count = (
await db.table(self.model.table_name).delete().eq("id", id).execute()
)
_, deleted = data
return self.model(**deleted[0])

View file

@ -1,43 +0,0 @@
from typing import Optional
from fastapi import HTTPException
from supabase_py_async import AsyncClient
from src.crud.base import CRUDBase
from src.schemas import Spell, SpellCreate, SpellUpdate
class CRUDSpell(CRUDBase[Spell, SpellCreate, SpellUpdate]):
async def get(self, db: AsyncClient, *, id: str) -> Optional[Spell]:
try:
return await super().get(db, id=id)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"{e.code}: Spell not found. {e.details}",
)
async def get_all(self, db: AsyncClient) -> list[Spell]:
try:
return await super().get_all(db)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"An error occurred while fetching spells. {e}",
)
async def search_all(
self, db: AsyncClient, *, field: str, search_value: str, max_results: int
) -> list[Spell]:
try:
return await super().search_all(
db, field=field, search_value=search_value, max_results=max_results
)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"An error occurred while searching for users. {e}",
)
spell = CRUDSpell(Spell)

View file

@ -1,58 +0,0 @@
from typing import Optional
from fastapi import HTTPException
from supabase_py_async import AsyncClient
from src.crud.base import CRUDBase
from src.schemas import User, UserCreate, UserUpdate
class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]):
async def create(self, db: AsyncClient, *, obj_in: UserCreate) -> User:
try:
return await super().create(db, obj_in=obj_in)
except Exception as e:
raise HTTPException(
status_code=400,
detail=f"{e.code}: Failed to create user. {e.details}",
)
async def get(self, db: AsyncClient, *, id: str) -> Optional[User]:
try:
return await super().get(db, id=id)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"{e.code}: User not found. {e.details}",
)
async def get_all(self, db: AsyncClient) -> list[User]:
try:
return await super().get_all(db)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"An error occurred while fetching users. {e}",
)
async def search_all(
self, db: AsyncClient, *, field: str, search_value: str, max_results: int
) -> list[User]:
try:
return await super().search_all(
db, field=field, search_value=search_value, max_results=max_results
)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"An error occurred while searching for users. {e}",
)
async def update(self, db: AsyncClient, *, obj_in: UserUpdate) -> User:
return await super().update(db, obj_in=obj_in)
async def delete(self, db: AsyncClient, *, id: str) -> User:
return await super().delete(db, id=id)
user = CRUDUser(User)

View file

@ -1,51 +0,0 @@
from fastapi import APIRouter, FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.routing import APIRoute
from src.api.api_v1.api import api_router
from src.config import settings
info_router = APIRouter()
@info_router.get("/", status_code=200, include_in_schema=False)
async def info():
return [{"Status": "API Running"}]
def custom_generate_unique_id(route: APIRoute):
"""Generates a custom ID when using the TypeScript Generator Client
Args:
route (APIRoute): The route to be customised
Returns:
str: tag-route_name, e.g. items-CreateItem
"""
return f"{route.tags[0]}-{route.name}"
def get_application():
_app = FastAPI(
title=settings.PROJECT_NAME,
description=settings.PROJECT_DESCRIPTION,
generate_unique_id_function=custom_generate_unique_id,
root_path=settings.ROOT,
root_path_in_servers=True,
)
_app.include_router(api_router, prefix=settings.API_VERSION)
_app.include_router(info_router, tags=[""])
_app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
return _app
app = get_application()

View file

@ -1,2 +0,0 @@
from .spell import Spell, SpellCreate, SpellSearchResults, SpellUpdate
from .user import User, UserCreate, UserSearchResults, UserUpdate

View file

@ -1,40 +0,0 @@
from typing import ClassVar
from pydantic import BaseModel, ConfigDict
# Shared properties
# class CRUDBaseModel(BaseModel):
# # where the data
# table_name: str
# Properties to receive on item creation
# in
class CreateBase(BaseModel):
# inherent to add more properties for creating
pass
# Properties to receive on item update
# in
class UpdateBase(BaseModel):
# inherent to add more properties for updating
id: str
# response
# Properties shared by models stored in DB
class InDBBase(BaseModel):
id: str
user_id: str
created_at: str
# Properties to return to client
# crud model
# out
class ResponseBase(InDBBase):
# inherent to add more properties for responding
table_name: ClassVar[str] = "ResponseBase".lower()
Config: ClassVar[ConfigDict] = ConfigDict(
extra="ignore", arbitrary_types_allowed=True
)

View file

@ -1,26 +0,0 @@
from typing import ClassVar, Sequence
from pydantic import BaseModel
class Spell(BaseModel):
id: str
name: str
description: str
table_name: ClassVar[str] = "spells"
class SpellCreate(BaseModel):
id: str
name: str
description: str
class SpellUpdate(BaseModel):
id: str
name: str
description: str
class SpellSearchResults(BaseModel):
results: Sequence[Spell]

View file

@ -1,31 +0,0 @@
from typing import ClassVar, Sequence
from pydantic import BaseModel, EmailStr
class User(BaseModel):
id: str
forename: str
surname: str
email: EmailStr
table_name: ClassVar[str] = "user"
class UserCreate(BaseModel):
forename: str
surname: str
email: EmailStr
class UserUpdate(BaseModel):
forename: str
surname: str
email: EmailStr
class ResponseMessage(BaseModel):
message: str
class UserSearchResults(BaseModel):
results: Sequence[User]

View file

@ -1,5 +0,0 @@
from src import __version__
def test_version():
assert __version__ == '0.1.0'

View file

@ -1,18 +0,0 @@
{
"version": 2,
"builds": [
{
"src": "/src/main.py",
"use": "@vercel/python"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "src/main.py"
}
],
"env": {
"APP_MODULE": "src.main:app"
}
}

9
apps/docs/.eslintrc.js Normal file
View file

@ -0,0 +1,9 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: ["@repo/eslint-config/next.js"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
};

36
apps/docs/.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# env files (can opt-in for commiting if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -1,3 +0,0 @@
{
"recommendations": ["unifiedjs.vscode-mdx", "xyc.vscode-mdx-preview"]
}

36
apps/docs/README.md Normal file
View file

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
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.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View file

@ -1,3 +0,0 @@
---
openapi: get /api/v1/spells/get-all/
---

View file

@ -1,3 +0,0 @@
---
openapi: get /api/v1/spells/get/
---

View file

@ -1,3 +0,0 @@
---
openapi: get /api/v1/spells/search/
---

View file

@ -1,3 +0,0 @@
---
openapi: post /api/v1/users/create
---

View file

@ -1,3 +0,0 @@
---
openapi: get /api/v1/users/get-all/
---

View file

@ -1,3 +0,0 @@
---
openapi: get /api/v1/users/get/
---

View file

@ -1,3 +0,0 @@
---
openapi: get /api/v1/users/search/
---

BIN
apps/docs/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

39
apps/docs/app/globals.css Normal file
View file

@ -0,0 +1,39 @@
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: var(--foreground);
background: var(--background);
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}

31
apps/docs/app/layout.tsx Normal file
View file

@ -0,0 +1,31 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
{children}
</body>
</html>
);
}

View file

@ -0,0 +1,188 @@
.page {
--gray-rgb: 0, 0, 0;
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
--button-primary-hover: #383838;
--button-secondary-hover: #f2f2f2;
display: grid;
grid-template-rows: 20px 1fr 20px;
align-items: center;
justify-items: center;
min-height: 100svh;
padding: 80px;
gap: 64px;
font-synthesis: none;
}
@media (prefers-color-scheme: dark) {
.page {
--gray-rgb: 255, 255, 255;
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
--button-primary-hover: #ccc;
--button-secondary-hover: #1a1a1a;
}
}
.main {
display: flex;
flex-direction: column;
gap: 32px;
grid-row-start: 2;
}
.main ol {
font-family: var(--font-geist-mono);
padding-left: 0;
margin: 0;
font-size: 14px;
line-height: 24px;
letter-spacing: -0.01em;
list-style-position: inside;
}
.main li:not(:last-of-type) {
margin-bottom: 8px;
}
.main code {
font-family: inherit;
background: var(--gray-alpha-100);
padding: 2px 4px;
border-radius: 4px;
font-weight: 600;
}
.ctas {
display: flex;
gap: 16px;
}
.ctas a {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: none;
font-family: var(--font-geist-sans);
border: 1px solid transparent;
transition: background 0.2s, color 0.2s, border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
}
a.primary {
background: var(--foreground);
color: var(--background);
gap: 8px;
}
a.secondary {
border-color: var(--gray-alpha-200);
min-width: 180px;
}
button.secondary {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: none;
font-family: var(--font-geist-sans);
border: 1px solid transparent;
transition: background 0.2s, color 0.2s, border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
background: transparent;
border-color: var(--gray-alpha-200);
min-width: 180px;
}
.footer {
font-family: var(--font-geist-sans);
grid-row-start: 3;
display: flex;
gap: 24px;
}
.footer a {
display: flex;
align-items: center;
gap: 8px;
}
.footer img {
flex-shrink: 0;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
a.primary:hover {
background: var(--button-primary-hover);
border-color: transparent;
}
a.secondary:hover {
background: var(--button-secondary-hover);
border-color: transparent;
}
.footer a:hover {
text-decoration: underline;
text-underline-offset: 4px;
}
}
@media (max-width: 600px) {
.page {
padding: 32px;
padding-bottom: 80px;
}
.main {
align-items: center;
}
.main ol {
text-align: center;
}
.ctas {
flex-direction: column;
}
.ctas a {
font-size: 14px;
height: 40px;
padding: 0 16px;
}
a.secondary {
min-width: auto;
}
.footer {
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
}
@media (prefers-color-scheme: dark) {
.logo {
filter: invert();
}
}

99
apps/docs/app/page.tsx Normal file
View file

@ -0,0 +1,99 @@
import Image from "next/image";
import { Button } from "@repo/ui/button";
import styles from "./page.module.css";
export default function Home() {
return (
<div className={styles.page}>
<main className={styles.main}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol>
<li>
Get started by editing <code>app/page.tsx</code>
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className={styles.ctas}>
<a
className={styles.primary}
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className={styles.logo}
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
className={styles.secondary}
>
Read our docs
</a>
</div>
<Button appName="docs" className={styles.secondary}>
Open alert
</Button>
</main>
<footer className={styles.footer}>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file-text.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
);
}

View file

@ -1,161 +0,0 @@
---
title: Documentation config
"og:title": Mintlify setup and configuration
description: Mintlify setup and configuration
---
## Introduction
The documentation site is built entirely using Mintlify. Mintlify docs are rendered from MDX files and configurations. Mintlify works with a Github integration to
constantly build an independent docs site based off your latest published repository.
Documentation can be edited in raw MDX, or directly through the Mintlify dashboard (which stays in sync with your local Github repo).
Mintlify is fast to setup and provides tonnes of features out-the-box without the need to pay for a subscription for the basic services.
You can run the **docs site only** by running the `pnpm run dev` command from a terminal within the `docs` directory.
## Docs structure
The docs root directory is structured as follows:
```
docs
├── _images
├── api
├── configuration
├── deployment
├── logos
├── *.mdx
├── mint.json
└── package.json
```
The docs directory is relatively simple and is made mostly of MDX files. Some notable files are:
| Item | Description |
| ------------ | ----------------------------------------------------------- |
| api | Automatically generated files (except for Introduction.mdx) |
| mint.json | Mintlify configuration object |
| package.json | Scripts needed in the monorepo/for generating the API files |
## Creating API endpoint documentation
We can leverage FastAPI's baked in OpenAPI support to automatically generate API documentation.
Using Mintlify's `@mintlify/scraping` package, we can scrape our FastAPI's OpenAPI schema to generate an interactive API documentation site.
<Tip>
For a more detailed description, read the [official
documentation](https://mintlify.com/docs/api-playground/openapi/setup#create-mdx-files-for-openapi-endpoints)
</Tip>
### Generating OpenAPI schema
#### Step 1: Generate MDX
Run the `generate-api` scripts within the `package.json` to generate the required MDX
files. There are two scripts here, one configured to use the production
`OpenAPI.json` and the second configured to use the development version.\
\
It is typically preferred to use the production version once any changes to
your API have been made and published.
#### Step 2: Update mint.json
<Steps>
<Step title="Add new MDX files to navigation">
Upon generating the relevant API information, `@mintlify/scraping` helpfully outputs the suggested navigation to the terminal:
```bash
navigation object suggestion:
[
{
"group": "users",
"pages": [
"api/users/get-user",
"api/users/get-all-users",
"api/users/search-users",
"api/users/create-user"
]
},
{
"group": "spells",
"pages": [
"api/spells/get-spell",
"api/spells/get-all-spells",
"api/spells/search-spells"
]
}
]
```
For sake of simplicity, copy this navigation object suggestion and add it your `mint.json` under the `navigation` object:
```json
"navigation": [
{
"group": "Getting Started",
"pages": ["documentation/introduction", "documentation/local-development"]
},
{
"group": "Configuration",
"pages": [
"documentation/configuration/turbo",
"documentation/configuration/fastapi",
"documentation/configuration/nextjs",
"documentation/configuration/docs"
]
},
{
"group": "Deployment",
"pages": [
"documentation/deployment/vercel",
"documentation/deployment/deployment"
]
},
{
"group": "API Reference",
"pages": ["api/introduction"]
},
{
"group": "Users",
"pages": [
"api/users/get-user",
"api/users/get-all-users",
"api/users/search-users",
"api/users/create-user"
]
},
{
"group": "Spells",
"pages": [
"api/spells/get-spell",
"api/spells/get-all-spells",
"api/spells/search-spells"
]
}
],
```
</Step>
<Step title="Add OpenAPI endpoint to `mint.json`">
Add the `openapi` key to your `mint.json`. This, preferably, can be linked to your published OpenAPI schema:
```json
{
...
"openapi":"https://next-fast-turbo-api.vercel.app/openapi.json"
...
}
```
This enables the generated MDX files to describe and interact with your API.
<Warning>
This has been hit and miss in the past for me. If doing this does not auto-populate your API reference with the documentation, the solution is to manually\
create an `openapi.json` file in the `docs` root. Manually copy and paste/save your `openapi.json` into this file.
</Warning>
</Step>
</Steps>

View file

@ -1,377 +0,0 @@
---
title: FastAPI config
"og:title": "FastAPI configuration and setup"
description: FastAPI configuration and setup
---
## Introduction
The backend uses [FastAPI](https://fastapi.tiangolo.com/). FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.8+ based on standard Python type hints
You can run the **api only** by running the `pnpm run dev` command from a terminal within the `api` directory. Alternatively, you can directly run the `run.py` file.
## API structure
The API root directory is structured as follows:
```
api
├── src
│ ├── api
│ ├── crud
│ ├── schemas
│ ├── __init__.py
│ ├── config.py
│ └── main.py
├── tests
├── .env
├── .env.example
├── harry-potter-db-seed-spells.csv
├── harry-potter-db-seed-users.csv
├── package.json
├── poetry.lock
├── pyproject.toml
├── requirements.txt
├── run.py
└── vercel.json
```
### Src directory
The `src` directory is where all the application code sits. Below briefly explains each folder/file
| Item | Description |
| --------- | ----------------------------------------------- |
| api | The API endpoints for the application |
| crud | The CRUD operations used within the application |
| schemas | The schemas used within the application |
| config.py | Main application configuration |
| main.py | Application |
### Root directory
The root directory contains the typical files one would expect to see in a Python project. The below files are worth describing:
| Item | Description |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| package.json | Not typical in a Python programme; used due to the nature of the monorepo. Running `pnpm run dev` at the project root will execute the `dev` script in this `package.json` |
| vercel.json | The configuration for deploying the FastAPI aspect of the application to Vercel. |
| \*.csv | Simple seed data for the database, sourced from the [Harry Potter API](https://hp-api.onrender.com/) |
## Dependencies
### Python 3.9
Because the project is being deployed on Vercel, Python version `3.9` must be used as this is the latest supported version of Python. For more information, read the [official documentation](https://vercel.com/docs/functions/runtimes/python).
If deploying to somewhere other than Vercel, check which version of Python you may use and adjust the project according to your needs.
### Supabase
The project uses [Supabase](https://supabase.com/) as a database. Since Planetscale [removed their free Hobby tier](https://planetscale.com/blog/planetscale-forever),
Supabase has seemed like a good alternative to use. You do not have to use Supabase with the backend, but the project is written from the perspective of using it.
<Tip>
If you are using Supabase,
[`supabase-py-async`](https://pypi.org/project/supabase-py-async/) is already
included as a project dependency within the `pyproject.toml`. If you are not
using Supabase, this can be removed.
</Tip>
### Poetry
[Poetry](https://python-poetry.org/) is used to manage the virtual environment and project dependencies.
A `requirements.txt` has been generated to enable the installation of Python packages via the `pip install` command.
If you do not use Poetry, you can remove the `poetry.lock` and `pyproject.toml` files.
<Tip>You will need a `requirements.txt` when deploying. Vercel also accepts a `Pipenv` file if you use Pipenv, otherwise, you'll need the `requirements.txt` for the `api` to build correctly</Tip>
## Adding your own endpoints
Given the project structure, there are three areas that you must be aware of when adding your own endpoints with new models. The main areas are:
- Schemas
- CRUD
- API
### Example: Creation of Spells endpoints
#### Step 1: Create a schema
<Steps>
<Step title="Add a schema">
Add a new schema to the `schemas` directory.
```python src/schemas/spell.py
from typing import ClassVar, Sequence
from pydantic import BaseModel
class Spell(BaseModel):
id: str
name: str
description: str
table_name: ClassVar[str] = "spells"
class SpellCreate(BaseModel):
id: str
name: str
description: str
class SpellUpdate(BaseModel):
id: str
name: str
description: str
class SpellSearchResults(BaseModel):
results: Sequence[Spell]
```
<Info>The `table_name` value needs to be included in the base `Spell` class. This **must** be the same as the name of the table created in Supabase. It is used in the CRUD operations to identify the table to work with.</Info>
| Item | Description |
|--------------------|---------------------------------------------------------------------------------------------|
| `Spell` | The base class describing the columns that will be in the Supabase table |
| SpellCreate | The class for creating a new `Spell` |
| SpellUpdate | The class for updating an existing `Spell` |
| SpellSearchResults | Describes how data will be returned in the API response. It will be a `Sequence` of `Spell` |
</Step>
<Step title="Add to __init__.py">
To easily import the new `Spell` classes throughout our application, they need to be added to the `src/schemas/__init__.py` file.
```python src/schemas/__init__.py
from .user import User, UserCreate, UserSearchResults, UserUpdate
from .spell import Spell, SpellCreate, SpellSearchResults, SpellUpdate
```
This allows us to import from `src/schemas` like so:
```python
from src.schemas import Spell, SpellCreate, SpellSearchResults, SpellUpdate
```
</Step>
</Steps>
#### Step 2: Setup CRUD operations
Specific CRUD operations can be created for each endpoint. However, generic CRUD operations in `src/crud/base.py` can also be used without modification.
<Steps>
<Step title="Create CRUD file">
```python src/crud/crud_spell.py
from fastapi import HTTPException
from supabase_py_async import AsyncClient
from src.crud.base import CRUDBase
from src.schemas import Spell, SpellCreate, SpellUpdate
class CRUDSpell(CRUDBase[Spell, SpellCreate, SpellUpdate]):
async def get(self, db: AsyncClient, *, id: str) -> Spell | None:
try:
return await super().get(db, id=id)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"{e.code}: Spell not found. {e.details}",
)
async def get_all(self, db: AsyncClient) -> list[Spell]:
try:
return await super().get_all(db)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"An error occurred while fetching spells. {e}",
)
async def search_all(
self, db: AsyncClient, *, field: str, search_value: str, max_results: int
) -> list[Spell]:
try:
return await super().search_all(
db, field=field, search_value=search_value, max_results=max_results
)
except Exception as e:
raise HTTPException(
status_code=404,
detail=f"An error occurred while searching for spells. {e}",
)
spell = CRUDSpell(Spell)
```
The `CRUDSpell` class inherits from the `CRUDBase` class located in `src/crud/base.py`. The `Spell`, `SpellCreate` and `SpellUpdate` classes are passed to the `CRUDBase` class to specify the types of data that will be used in the CRUD operations.
The operations that will be used in the API endpoints are functions of the `CRUDSpell` class.
Finally, `spell` is an instance of `CRUDSpell`. We import this instance to use in the API endpoints like so: `spell.get_all(db)` or `spell.get(db, id=id)`.
<Note>
**This part of the project is intended to be read-only**, however, the `SpellCreate` and `SpellUpdate` classes are created in the schema regardless as they are required by the `CRUDBase` model's function arguments. `CRUDBase` can be refactored to have optional parameters, or a read-only version of the `CRUDBase` class can be created. This is beyond the scope of this project.
</Note>
</Step>
<Step title="Add to __init__.py">
Similar to setting up the schemas, the `CRUDSpell` class needs to be added to the `src/crud/__init__.py` file.
```python src/crud/__init__.py
from .crud_user import user
from .crud_spell import spell
```
</Step>
</Steps>
#### Step 3: Create the API endpoints
<Steps>
<Step title="Create the endpoints">
Create the endpoints in the `src/api/api_v1/endpoints/spells.py` file.
```python src/api/api_v1/endpoints/spells.py
from typing import Literal, Optional, Union
from fastapi import APIRouter, HTTPException
from src.api.deps import SessionDep
from src.crud import spell
from src.schemas import Spell, SpellSearchResults
router = APIRouter()
@router.get("/get/", status_code=200, response_model=Spell)
async def get_spell(session: SessionDep, spell_id: str) -> Spell:
"""Returns a spell from a spell_id.
**Returns:**
- spell: spell object.
"""
return await spell.get(session, id=spell_id)
@router.get("/get-all/", status_code=200, response_model=list[Spell])
async def get_all_spells(session: SessionDep) -> list[Spell]:
"""Returns a list of all spells.
**Returns:**
- list[spell]: List of all spells.
"""
return await spell.get_all(session)
@router.get("/search/", status_code=200, response_model=SpellSearchResults)
async def search_spells(
session: SessionDep,
search_on: Literal["spells", "description"] = "spell",
keyword: Optional[Union[str, int]] = None,
max_results: Optional[int] = 10,
) -> SpellSearchResults:
"""
Search for spells based on a keyword and return the top `max_results` spells.
**Args:**
- keyword (str, optional): The keyword to search for. Defaults to None.
- max_results (int, optional): The maximum number of search results to return. Defaults to 10.
- search_on (str, optional): The field to perform the search on. Defaults to "email".
**Returns:**
- SpellSearchResults: Object containing a list of the top `max_results` spells that match the keyword.
"""
if not keyword:
results = await spell.get_all(session)
return SpellSearchResults(results=results)
results = await spell.search_all(
session, field=search_on, search_value=keyword, max_results=max_results
)
if not results:
raise HTTPException(
status_code=404, detail="No spells found matching the search criteria"
)
return SpellSearchResults(results=results)
```
Here we are calling `spell` object which we setup in step 2.
The `SessionDep` (located in `src/api/deps.py`) dependency is used to access the database.
The `response_model` parameter is used to specify the type of data that will be returned from the endpoint. This is used to generate TypeScript types for the frontend.
</Step>
<Step title="Add the endpoint to the router">
To include the new endpoints in the API, it must be added to the API router, located in `src/api/api_v1/api.py`.
```python src/api/api_v1/api.py
from fastapi import APIRouter
from src.api.api_v1.endpoints import users, spells
api_router = APIRouter()
api_router.include_router(users.router, prefix="/users", tags=["users"], responses={404: {"description": "Not found"}})
api_router.include_router(spells.router, prefix="/spells", tags=["spells"], responses={404: {"description": "Not found"}})
```
</Step>
</Steps>
#### Step 4: Create the table in Supabase
<Steps>
<Step title="Create the table">
Create a new table in your Supabase dashboard. It is **imperative** that the table name matches the `table_name` value in the `Spell` class in `src/schemas/spell.py`.
Columns should be added to match the `Spell` class. For example, we have the following `Spell` class:
```python
class Spell(BaseModel):
id: str
name: str
description: str
table_name: ClassVar[str] = "spells"
```
In this example, the table should be named `spells` (table names are case-sensitive) with the columns `id`, `name`, and `description`. \
\
For data types, `id` can be automatically assigned - in this example, it is set to a `uuid`. The `name` and `description` fields are strings, therefore their column's type is set to `text`. The `id` column should be the primary key.
<Tip>You do not need to add the `table_name` column as this is used in the FastAPI code to identify the table to work with.</Tip>
</Step>
<Step title="Seed the table with data">
The data can be seeded using the Supabase dashboard by uploading the `harry-potter-db-seed-spells.csv` file. For a more detailed explanation, please see the [official documentation](https://supabase.com/docs/guides/database/import-data#option-1-csv-import-via-supabase-dashboard)
</Step>
<Step title="Configure table security">
The table security should be configured to allow the FastAPI application to access the data. By default, Supabase will have [RLS (Row Level Security)](https://supabase.com/docs/guides/auth/row-level-security) enabled.\
\
If left un-configured, the database will return an empty array. As this is a simple read-only project, I am turning `RLS` off. However, for true CRUD operations, it should be configured to use authentication which is beyond the scope of this project.
\
To disable `RLS`, in the table settings, select `configure RLS` and then `disable RLS`.\
</Step>
<Step title="Add table connection to .env">
Once the table has been created, you must ensure that the `DB_URL` and `DB_API_KEY` parameters are populated in your `.env` file located in the root of the `API` directory.\
\
These values come from the Supabase dashboard by going to `settings` and then `API`. Copy the Project URL (`DB_URL`) and the Project API Key (`DB_API_KEY`).
<Info>
`DB_USERNAME` and `DB_PASSWORD` should also included in the `.env` as they are configured in the `src/config.py`. If you do not include these you will receive an error from Pydantic.\
\
Their inclusion is a pre-cursor to authentication, but they are not actually used in this project scaffold.
</Info>
</Step>
</Steps>
#### Step 5: Test your new endpoints
You can now test your new endpoints using the FastAPI Swagger UI or by making requests to the API.

View file

@ -1,176 +0,0 @@
---
title: Next.js config
"og:title": "Next.js config and setup"
"description": "Next.js config and setup"
---
## Introduction
The frontend uses the latest version of Next.js and TypeScript; it is configured using the latest [app router](https://nextjs.org/docs/app).
You can run the **frontend only** by running the `pnpm run dev` command from a terminal within the `web` directory. Alternatively, you can run the entire application by running `pnpm run dev` from the **root directory**.
## App structure
The app root directory is structured as follows:
```
web
├── app
├── components
├── lib
├── public
├── .env
├── .env.example
├── .eslintrc.js
├── components.json
├── next.config.js
├── package.json
├── postcss.config.js
├── README.md
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
```
#### App
| Item | Description |
| ----------- | ---------------------------------------------------------------------------------------------------- |
| globals.css | Tailwind global config. Uses a generated theme from [ShadCN UI themes](https://ui.shadcn.com/themes) |
| layout.tsx | App layout |
| page.tsx | Landing page |
#### Components
| Item | Description |
| ------- | ----------------------------------------------------------------------------------------------------------------------------- |
| layouts | The dashboard layout is stored which wraps the application `{children}` in `root/app/layout.tsx` |
| theme | The `ThemeProvider` component(s) that wrap the application. |
| ui | Where `ShadCN` components are stored upon being generated. Configured by the `components.json` located in the `frontend root` |
| \*.tsx | Components used within the application |
#### Lib
| Item | Description |
| ----------- | ---------------------------------------------------------------------------------------------------------------- |
| api | The output generated by the `openapi` npm package. Configured in the `package.json` task named `generate-client` |
| config | Stores site config. Currently contains the setup for sidebar navigation |
| twConfig.ts | Tailwind colour config exported to an accessible Object. Used for assigning theme-aware colours in charts |
| utils.ts | ShadCN utils |
## TypeScript from FastAPI
As FastAPI is based on the OpenAPI specification, you get automatic compatibility with many tools, including the automatic API docs (provided by Swagger UI).
One particular advantage that is not necessarily obvious is that you can generate clients (sometimes called SDKs ) for your API, for many different programming languages.
<Note>For a more detailed explanation, see the [official FastAPI documentation](https://fastapi.tiangolo.com/advanced/generate-clients/#generate-a-typescript-client-with-the-preprocessed-openapi)</Note>
## Why generate TypeScript?
Generating a TypeScript client for your FastAPI backend is a great way to ensure that your frontend and backend are always in sync. It is also a way to provide type hinting while writing your frontend code without needing a permanent reference to the API, or re-creating the schema using something like Zod.
<Frame caption="Type hinting from the generated API client"><img src="/_images/type-hinting.png" /></Frame>
### How to generate TypeScript
<Steps>
<Step title="Configure and run generate-client task">
There are two generate-client tasks configured in the `package.json`:
```JSON
"scripts": {
...
"generate-client": "openapi --input https://next-fast-turbo.vercel.app/openapi.json --output ./lib/api/client --client axios --useOptions --useUnionTypes",
"generate-client:dev": "openapi --input http://127.0.0.0:8000/openapi.json --output ./lib/api/client --client axios --useOptions --useUnionTypes"
...
},
```
The `generate-client` task is set to run off the production OpenAPI schema.\
\
The `generate-client:dev` task is set to use the localhost OpenAPI schema. This is useful for development, as it will use the latest schema from the backend.
Ensure your production API URL is configured, or that your local API URL is correct, and then run the relevant task.
</Step>
<Step title="Update your root layout">
A key file to be aware of is the `OpenAPI.ts` which is generated in the `lib/api/client/core` directory. This file has the main configuration for the API connection as below:
```tsx lib/api/client/core/OpenAPI.ts
export const OpenAPI: OpenAPIConfig = {
BASE: "http://127.0.0.0:8000",
VERSION: "0.1.0",
WITH_CREDENTIALS: false,
CREDENTIALS: "include",
TOKEN: undefined,
USERNAME: undefined,
PASSWORD: undefined,
HEADERS: undefined,
ENCODE_PATH: undefined,
};
```
The problem with this is that we would want to differentiate between which API is being used. When the TypeScript is generated, the given OpenAPI URL is used as the base URL for the API.
This is not ideal for production, as you would want to use the production API URL, and vice versa in development.
You can override this by adding the following to your root `layout.tsx` file:
```tsx layout.tsx
import { OpenAPI } from "@/lib/api/client";
if (process.env.NODE_ENV === "production") {
OpenAPI.BASE = "https://next-fast-turbo.vercel.app"
}
```
</Step>
</Steps>
## Using the generated API
Once you have generated your TypeScript interface to your API, you can use it in your frontend code.
In its simplest form, you can import the generated API and use it as below:
```tsx
import { UsersService } from "@/lib/api/client";
const response = await UsersService.usersSearchUsers({
keyword: "keyword",
searchOn: "searchOn",
maxResults: "maxResults",
});
```
You can see this code being used in the `components/search-users.tsx` file, as part of the form's onSubmit function:
```tsx
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
try {
const maxResults = data.searchResults ? parseInt(data.searchResults) : 10;
const response = await UsersService.usersSearchUsers({
keyword: data.keyword,
searchOn: data.searchOn,
maxResults: maxResults,
});
setSearchResults(response);
setError(null);
} catch (error) {
setSearchResults({ results: [] });
setError(error);
}
};
```
This code also imports the `UserSearchResults` type which is used to set the state of the search results:
```tsx
import { UsersService, UserSearchResults } from "@/lib/api/client";
const [searchResults, setSearchResults] = React.useState<UserSearchResults>({
results: [],
});
```
## Important considerations
- If you make changes to your API, you will need to re-generate the TypeScript interface.
- Re-generating this interface will overwrite the existing files. If you want to modify anything (e.g. `api/client/core/OpenAPI.ts`) and have it persist, do this outside of the generated files. You can see this in action in the `root/app/layout.tsx` file.

View file

@ -1,53 +0,0 @@
---
title: Turbo config
"og:title": "Configuring Turbo for FastAPI, Next.js and Turborepo"
description: Configuring Turbo for FastAPI, Next.js and Turborepo.
---
## Introduction
This project was first created using the `create-turbo` npm package:
<CodeGroup>
```bash npm
npx create-turbo@latest
```
```bash yarn
yarn dlx create-turbo@latest
```
```bash pnpm
pnpm dlx create-turbo@latest
```
</CodeGroup>
<Note>It is recommended that you read through the [official documentation](https://turbo.build/repo/docs/getting-started/create-new) to understand the breakdown of the monorepo.</Note>
## Changes to the official Turbo starter
The official Turbo starter is a great starting point for a monorepo, but it has some configurations I did not need. As I will not be sharing code between the backend and frontend (nor the docs), I have **removed** the `packages/ui` directory.
If you want to **add a new package** to the project, please reference the [official documentation](https://turbo.build/repo/docs/handbook/sharing-code/internal-packages).
For quick reference:
1. Create a new folder in `packages/<folder>`
2. Add a `package.json`, with `name` and `types` pointing at `src/index.ts[x]`
3. Add `src/index.ts[x]`, with at least one named export
4. Install your packages from `root`
## Turbo.json
The currently configured `turbo.json` does not have many changes from the official starter. The only change is the addition of the `globalEnv` key.
```json
{
"globalEnv": [
"NODE_ENV"
],
}
```
Without adding these environment variables to the `globalEnv`, you will receive an eslint error when referencing variables in your `.env`, however, your code will still work.
<Warning>
There are different ways that people solve this. While it's possible to use\
`"globalDependencies": ["**/.env"]`, that did not work in this project.\
</Warning>

View file

@ -1,131 +0,0 @@
---
title: Deploying the monorepo
description: Deploying your monorepo to Vercel/Mintlify
"og:title": Deploying your monorepo to Vercel/Mintlify
---
## Introduction
Deploying your monorepo to Vercel is relatively straightforward, but there are a handful of things to be aware of.
The frontend deployment is relatively straightforward, but the backend deployment can be a little more complex and need a bit more attention.
<Note>For a more detailed guide on deploying a monorepo to Vercel, see the [official documentation](https://vercel.com/docs/monorepos/turborepo).</Note>
## Prerequisites
Before continuing, ensure you have:
- A [Vercel](https://vercel.com/) account. It is typically easiest to sign up with your GitHub account, as we'll also be deploying directly from the GitHub repository.
- Published your repo to GitHub. If you haven't done this yet, you can follow the instructions [here](https://docs.github.com/en/get-started/quickstart/create-a-repo).
- Optional. If you're planning to deploy your documentation site to Mintlify, you'll need to have a [Mintlify](https://mintlify.com/) account.
## Frontend Deployment
<Steps>
<Step title="Add new project in Vercel">
Go to your [Vercel dashboard](https://vercel.com/dashboard) and click the `Add New...` button, and select `Project`.
</Step>
<Step title="Import your monorepo">
Select the `Import Git Repository` option and select your repository from the list of repositories.
</Step>
<Step title="Configure Project">
Vercel will automatically detect that you have a monorepo, and should have correctly populated all the required fields.
The project root directory should be automatically detected, but if it isn't, you can manually set it to the folder where your frontend is stored. In this project, that is `apps/web`.
<Frame>
<img src="/_images/deploy-web-configure-project.png" />
</Frame>
</Step>
<Step title="Deploy">
Click the `Deploy` button to deploy your project.
</Step>
<Step title="Add an Ignored Build Step">
We don't want to continuously rebuild our frontend every time we push changes to the backend or documentation. To prevent this, we can add an ignored build step.\
\
From the newly deployed frontend dashboard, navigate to `Settings`, then to `Git`. Scroll down to the `Ignored Build Step` section and set the behaviour to
**Only build Turborepo app if there are changes**.
<Frame>
<img src="/_images/deploy-web-ignored-build-step.png" />
</Frame>
<Note>For more information, see the [official documentation](https://vercel.com/docs/projects/overview#ignored-build-step).</Note>
</Step>
</Steps>
## Backend deployment
<Steps>
<Step title="Add new project in Vercel">
Go to your [Vercel dashboard](https://vercel.com/dashboard) and click the `Add New...` button, and select `Project`.
</Step>
<Step title="Import your monorepo">
Select the `Import Git Repository` option and select your repository from the list of repositories.
</Step>
<Step title="Configure Project">
Vercel will automatically detect that you have a monorepo, but for the backend we need to change the default configuration.
- Configure your project name
- Change the `Framework Preset` to `Other` (there is currently no Python option)
- Change the `Root Directory` to the folder where your backend is stored. In this project, that is `apps/api`
- Add your Environment Variables
<Frame>
<img src="/_images/deploy-api-configure-project.png" />
</Frame>
</Step>
<Step title="Deploy">
Click the `Deploy` button to deploy your project. Once built, the API should be running at the URL provided.
<Frame>
<img src="/_images/deploy-api-running.png" />
</Frame>
</Step>
<Step title="Add an Ignored Build Step">
We don't want to continuously rebuild our frontend every time we push changes to the backend or documentation. To prevent this, we can add an ignored build step.\
\
From the newly deployed frontend dashboard, navigate to `Settings`, then to `Git`. Scroll down to the `Ignored Build Step` section and set the behaviour to
**Only build Turborepo app if there are changes**.
<Frame>
<img src="/_images/deploy-web-ignored-build-step.png" />
</Frame>
<Note>For more information, see the [official documentation](https://vercel.com/docs/projects/overview#ignored-build-step).</Note>
</Step>
</Steps>
## Documentation deployment
<Note>For a more detailed explanation, visit the [offical documentation](https://mintlify.com/docs/quickstart)</Note>
Connecting to an existing repository with Mintlify is a little bit tricky, as their walkthrough currently seems to only support creating a new repository. However, it is possible to connect an existing repository by following the steps below.
<Steps>
<Step title="Sign in to Mintlify">
Sign in to [Mintlify](https://mintlify.com/). You will further be prompted to **Sign in with GitHub**. Follow the onscreen instructions.
</Step>
<Step title="Select Monorepo">
Configure your deployment to be pointed to your `docs` directory. Ensure **set up as a monorepo** is selected.
<Frame>
<img src="/_images/deploy-docs-select-repo.png" />
</Frame>
</Step>
<Step title="Install Mintlify app to GitHub">
From the Mintlify dashboard, click **Things to Do** and then click **Enable automatic updates**.
<Frame>
<img src="/_images/deploy-docs-enable-auto-update.png" />
</Frame>
</Step>
</Steps>

View file

@ -1,70 +0,0 @@
---
title: Introduction
"og:title": "Getting started with Next-Fast-Turbo"
description: A starter project for FastAPI, Next.js and Turborepo.
---
## What is Next-Fast-Turbo?
Next-Fast-Turbo is designed as a personal starter kit for Next.js and FastAPI projects. It is a monorepo that includes a Next.js frontend and a FastAPI backend. The project is designed to be deployed to Vercel, but can be deployed to any platform that supports monorepos.
This documentation is not written to be a tutorial, but instead to be a reference for the project. It is designed to be a living document that can be updated as the project evolves.
## Features
**Frontend**\
The Next.js application comes with a fully built frontend that includes:
- A responsive layout
- A dashboard/sidebar design
- Pre-configured connection to the backend API
- Autogenerated TypeScript types based off the FastAPI OpenAPI schema
- A variety of design components, mostly from ShadCN UI (including chart examples)
**Backend**\
The FastAPI application comes with a fully built backend that includes:
- Example endpoints
- Pre-configured schema/crud operations
- Easily extensible to add more endpoints
**Documentation**\
Built using Mintlify, a fully responsive and configured documentation site that features:
- A fully built documentation site
- A variety of Mintlify components
- A fully configured mint.json
## Tech stack
Next-Fast-Turbo is fully open-source built using the following technologies:
**Frontend**
- [Next.js](https://nextjs.org/) - Framework for building React applications
- [Tailwind CSS](https://tailwindcss.com/) - CSS framework
- [ShadCN UI](https://ui.shadcn.com/) - UI kit
**Backend**
- [FastAPI](https://fastapi.tiangolo.com/) - Python backend API
**Documentation**
- [Mintlify](https://mintlify.io/) - Documentation
**Global**
- [Vercel](https://vercel.com/) - hosting
- [Turbo](https://turbo.build/repo) - monorepo
## Getting started
<CardGroup cols={2}>
<Card title="Local development" icon="code" href="local-development">
Install the application locally
</Card>
<Card title="Deployment" icon="code-branch" href="deployment/vercel">
Deploy the monorepo to Vercel
</Card>
</CardGroup>

View file

@ -1,308 +0,0 @@
---
title: "Local Development"
"og:title": "How to setup local development"
description: "A guide on how to run the codebase locally."
---
## Introduction
Next-Fast-Turbo 's codebase is set up in a monorepo (via [Turborepo](https://turbo.build/repo)) and is fully open-source.
Here's the monorepo structure:
```
apps
├── api
├── docs
├── web
packages
├── eslint-config
├── typescript-config
```
The `apps` directory contains the code for:
- `web`: The frontend of the Next-Fast-Turbo's application
- `api`: Next-Fast-Turbo's FastAPI backend - written in Python
- `docs`: Next-Fast-Turbo's documentation site
The `packages` directory contains the code for:
- `eslint-config`: ESLint configurations for Next-Fast-Turbo's codebase. Boilerplate code included as part of the [create Turbo](https://turbo.build/repo/docs/getting-started/create-new) command
- `typescript-config`: TypeScript configurations for Next-Fast-Turbo's codebase. Boilerplate code included as part of the [create Turbo](https://turbo.build/repo/docs/getting-started/create-new) command
## Running Next-Fast-Turbo
### Step 1: Local setup
<Steps>
<Step title="Clone the repo">
Clone the [Next-Fast-Turbo repo](https://github.com/cording12/next-fast-turbo.git).
```bash
git clone https://github.com/cording12/next-fast-turbo.git app-name
```
</Step>
<Step title="Install dependencies">
Change to the root directory of the cloned repository and install the dependencies using the following command:
```bash
cd app-name
pnpm install
```
</Step>
<Step title="Open code-workspace">
It is recommended to use the pre-configured Workspace stored in the `.vscode` folder at the project root.
Navigate to `app-name/.vscode/` and double click `next-fast-turbo.code-workspace` to open in VS Code, or, in VS Code navigate to **File** and then **Open Workspace from File**.
<Tip>You can rename this to match your project name. The extension, `code-workspace`, must stay the same, but it can be changed to `app-name.code-workspace`</Tip>
</Step>
</Steps>
### Step 2: Python setup
<Note>
In a monorepo, VS Code sometimes uses the wrong Python interpreter, leading to **module not found** errors. You can open the `api` folder in its own VS Code window, but using
the pre-configured Workspace is recommended.
</Note>
While working on the Python backend, ensure that your terminal is activated in the correct folder. From the root, run the following command to change to the `api` directory:
```bash
cd apps/api
```
<Steps>
<Step title="Create a virtual environment">
Create a virtual environment in the `api` directory:
<CodeGroup>
```bash Poetry
poetry install
```
```bash Pip
python -m venv .venv
```
</CodeGroup>
<Tip>
If you're using Poetry, you could receive an error noting incorrect format of the `poetry.lock` file. This is a version mismatch between the version installed and the version used to generate the lock file. You can fix this by deleting the `poetry.lock` file
and running `poetry install` again.
</Tip>
</Step>
<Step title="Install dependencies (if not using Poetry)">
Run the following command to install the Python dependencies:
```bash
pip install -r requirements.txt
```
</Step>
<Step title="Configure .env file">
Create a `.env` file in the `api` directory and add the following environment variables:
```env
DB_URL=supabase_url
DB_API_KEY=supabase_api_key
DB_EMAIl=email_address
DB_PASSWORD=password
```
These can be placeholder values for now, but you'll need to replace them with your actual Supabase credentials (covered in step 3).
</Step>
</Steps>
### Step 3: Creating tables in Supabase
Next-Fast-Turbo uses [Supabase](https://supabase.com/) as the database for the backend. You'll need to create a new project in Supabase and then create the required tables. To get this example running, you need to only create two tables in Supabase.
<Steps>
<Step title="Create an account and new project">
Visit [Supabase](https://supabase.com/) and register an account. Once you're logged in, create a new project and give it a name.
<Frame>
<img src="/_images/supabase-project-create.png" />
</Frame>
</Step>
<Step title="Add credentials to `.env`">
While your project is building, copy the `Project API Key` and `URL` values and add these to the `.env` file in the `api` directory, as described in step 3 of the [Python setup](#step-2-python-setup).
<Frame>
<img src="/_images/supabase-credentials.png" />
</Frame>
</Step>
<Step title="Create tables">
The tables are seeded with the two `.csv` files located in the `api` root, but the tables must be created before seeding.
From the dashboard, visit the `Table Editor` and click the `New table` button.
<Frame>
<img src="/_images/supabase-new-table.png" />
</Frame>
Create the `users` and `spells` tables with columns that match their respective CSV columns. Below is how they are both configured:
<Tabs>
<Tab title="Users table">
<Frame>
<img src="/_images/supabase-create-user-table.png" />
</Frame>
</Tab>
<Tab title="Spells table">
<Frame>
<img src="/_images/supabase-create-spells-table.png" />
</Frame>
</Tab>
</Tabs>
<Warning>
RLS is set to disabled on these tables. Authentication with Supabase was not in the scope for this project, but you will want to configure this yourself for anything more than this simple example.
You can read more about [RLS](https://supabase.com/docs/guides/auth/row-level-security) in the Supabase documentation.
</Warning>
</Step>
<Step title="Upload CSV seed data">
Once the tables are created, you can seed them with the data from the `.csv` files. From the `Table Editor`, click the `Insert` button and select the relevant `.csv` file to upload.
<Frame>
<img src="/_images/supabase-upload-csv.png" />
</Frame>
</Step>
</Steps>
### Step 4: Configure Turbo remote caching (optional)
Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel.
<Steps>
<Step title="Login via Turborepo CLI">
From the project root, run the command:
```bash
npx turbo login
```
This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
</Step>
<Step title="Link your Turborepo to your Remote Cache">
Link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
```bash
npx turbo link
```
</Step>
</Steps>
### Step 5: Running everything
To make the most of Turbo's monorepo structure, you can run the frontend, backend and documentation site simultaneously. From the root, run the following command:
```bash root
pnpm run dev
```
<Tip>You can still run each separately by running the task directly from the relevant `package.json` or by running the `pnpm run dev` command from a terminal activated in the desired target location</Tip>
## Working with a monorepo in VS Code
For a better development experience, you can use VS Code Workspaces for the monorepo. This will allow you to run tasks and debug the codebase from a single window, while keeping things more organised.
Furthermore, VS Code doesn't handle Python virtual environments particularly well when working within a monorepo. Running the `dev` command from the project root can make VS Code use your global Python installation,
instead of the `.venv` created in the `api` root. By using a Workspace, this alleviates the problem.
<Note>For a more detailed guide on setting up a monorepo in VS Code, check out the [official Multi-root Workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces) documentation</Note>
### Step 1: Open the monorepo
In the `/.vscode/` directory, you'll find a `next-fast-turbo.code-workspace` file. Open this file in VS Code to open the monorepo Workspace.
<Tabs>
<Tab title="No code-workspace">
<Frame caption="Frontend (web) folder open without using the Workspace">
<img src="/_images/no-workspace.png" />
</Frame>
</Tab>
<Tab title="With a code-workspace">
<Frame caption="Frontend (web) folder open with a Workspace">
<img src="/_images/workspace.png" />
</Frame>
</Tab>
</Tabs>
### Step 2: Running tasks
VS Code will try to autodetect tasks from gulp, grunt, npm, and TypeScript project files across all folders in a workspace as well as search for tasks defined in tasks.json files. The location of tasks is indicated by a folder name suffix
<Frame caption="Workspace tasks">
<img src="/_images/tasks.png" />
</Frame>
From the above example, you can see there are several configured tasks with the relevant folder name after the task name.
### Step 3: Debugging
With multi-root workspaces, VS Code searches across all folders for `launch.json` debug configuration files and displays them with the folder name as a suffix.
Additionally VS Code will also display launch configurations defined in the workspace configuration file.
<Frame caption="Run and debug panel">
<img src="/_images/run-and-debug.png" />
</Frame>
You can still create [launch configurations](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) for each individual package in the monorepo and they will populate in the dropdown list automatically.
#### Workspace launch configurations
If you want to create a Workspace level configuration with [compound launch](https://code.visualstudio.com/docs/editor/debugging#_compound-launch-configurations), you can edit the `next-fast-turbo.code-workspace` file and add the configurations you wish to launch.
<Tip>You can also edit the Workspace configuration file via the Command Palette\
(Windows: Ctrl + Shift + P) and searching for `open workspace config`</Tip>
A compound launch configuration can reference the individual launch configurations by name as long as the names are unique within the workspace, for example:
```json
{
"launch": {
"version": "0.2.0",
"configurations": [],
"compounds": [
{
"name": "Launch Frontend and Backend",
"configurations": ["Next.js: Chrome", "Python: FastAPI"]
}
]
}
}
```
For a more detailed explanation, check out the [official documentation](https://code.visualstudio.com/docs/editor/multi-root-workspaces#_workspace-launch-configurations)
### Optional: Extensions
<AccordionGroup>
<Accordion title="Python Envy">
Helps VS Code identify the correct Python virtual environment when installed in the working directory. This is especially useful when working with Python in a monorepo, as it can be difficult for VS Code to manage multiple virtual environments.
[Python Envy](https://marketplace.visualstudio.com/items?itemName=teticio.python-envy)
</Accordion>
<Accordion title="Workspace Terminals (recommended)">
<Frame caption="Workspace Terminals in VS Code">
<img src="/_images/terminals.png" />
</Frame>
Terminal management in a monorepo can become cumbersome. This extension automatically creates a terminal in each of your monorepo's directories and names them accordingly. This will allow you to run commands and tasks from a terminal that's already set up in the correct directory.
[Workspace Terminals](https://marketplace.visualstudio.com/items?itemName=joshx.workspace-terminals)
</Accordion>
</AccordionGroup>
## Next Steps
<CardGroup cols={2}>
<Card title="Turbo config" icon="code" href="configuration/turbo">
Configuring Turbo for your monorepo
</Card>
<Card title="FastAPI config" icon="database" href="configuration/fastapi">
Configuring FastAPI
</Card>
<Card title="Frontend config" icon="computer" href="configuration/nextjs">
Configuring Next.js
</Card>
<Card title="Documentation config" icon="book" href="configuration/docs">
Configuring Mintlify for documentation
</Card>
</CardGroup>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

View file

@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:8px;}</style></defs><circle class="cls-1" cx="50" cy="50" r="49.9"/><path class="cls-2" d="M61.77,69.61,81.38,50,61.77,30.39"/><path class="cls-2" d="M38.23,69.61,18.62,50,38.23,30.39"/></svg>

Before

Width:  |  Height:  |  Size: 403 B

View file

@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:#020000;}.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:8px;}</style></defs><circle class="cls-1" cx="50" cy="50" r="49.9"/><path class="cls-2" d="M61.77,69.61,81.38,50,61.77,30.39"/><path class="cls-2" d="M38.23,69.61,18.62,50,38.23,30.39"/></svg>

Before

Width:  |  Height:  |  Size: 406 B

View file

@ -1,96 +0,0 @@
{
"$schema": "https://mintlify.com/schema.json",
"name": "Next-Fast-Turbo Docs",
"favicon": "/logos/logo.svg",
"logo": {
"light": "/logos/logo.svg",
"dark": "/logos/logo-light.svg"
},
"colors": {
"primary": "#7c3aed",
"light": "#af87ff",
"dark": "#1e293b",
"background": {
"dark": "#030712",
"light": "#ffffff"
}
},
"modeToggle": {
"default": "dark"
},
"topbarCtaButton": {
"name": "Dashboard",
"url": "https://next-fast-turbo-web.vercel.app/"
},
"tabs": [
{
"name": "API Reference",
"url": "api"
}
],
"anchors": [
{
"name": "Documentation",
"icon": "book-open-cover",
"url": "documentation"
},
{
"name": "API Reference",
"icon": "rectangle-terminal",
"url": "api"
}
],
"navigation": [
{
"group": "Getting Started",
"pages": [
"documentation/introduction",
"documentation/local-development"
]
},
{
"group": "Configuration",
"pages": [
"documentation/configuration/turbo",
"documentation/configuration/fastapi",
"documentation/configuration/nextjs",
"documentation/configuration/docs"
]
},
{
"group": "Deployment",
"pages": [
"documentation/deployment/deployment"
]
},
{
"group": "Users",
"pages": [
"api/users/get-user",
"api/users/get-all-users",
"api/users/search-users",
"api/users/create-user"
]
},
{
"group": "Spells",
"pages": [
"api/spells/get-spell",
"api/spells/get-all-spells",
"api/spells/search-spells"
]
}
],
"api": {
"maintainOrder": true,
"baseUrl": "https://next-fast-turbo-api.vercel.app"
},
"openapi": "https://next-fast-turbo-api.vercel.app/openapi.json",
"feedback": {
"thumbsRating": true
},
"footerSocials": {
"github": "https://github.com/cording12/next-fast-turbo",
"linkedin": "https://www.linkedin.com/in/jon-cording/"
}
}

View file

@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;

View file

@ -1,545 +0,0 @@
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI App",
"description": "A simple FastAPI app",
"version": "0.1.0"
},
"servers": [
{
"url": "https://next-fast-turbo-api.vercel.app"
}
],
"paths": {
"/api/v1/users/get/": {
"get": {
"tags": [
"users"
],
"summary": "Get User",
"description": "Returns a user from a user_id.\n\n**Returns:**\n- User: User object.",
"operationId": "users-get_user",
"parameters": [
{
"name": "user_id",
"in": "query",
"required": true,
"schema": {
"type": "string",
"title": "User Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"404": {
"description": "Not found"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/users/get-all/": {
"get": {
"tags": [
"users"
],
"summary": "Get All Users",
"description": "Returns a list of all users.\n\n**Returns:**\n- list[User]: List of all users.",
"operationId": "users-get_all_users",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/User"
},
"type": "array",
"title": "Response Users-Get All Users"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
},
"/api/v1/users/search/": {
"get": {
"tags": [
"users"
],
"summary": "Search Users",
"description": "Search for users based on a keyword and return the top `max_results` items.\n\n**Args:**\n- keyword (str, optional): The keyword to search for. Defaults to None.\n- max_results (int, optional): The maximum number of search results to return. Defaults to 10.\n- search_on (str, optional): The field to perform the search on. Defaults to \"email\".\n\n**Returns:**\n- UserSearchResults: Object containing a list of the top `max_results` items that match the keyword.",
"operationId": "users-search_users",
"parameters": [
{
"name": "search_on",
"in": "query",
"required": false,
"schema": {
"enum": [
"id",
"email",
"forename",
"surname"
],
"type": "string",
"default": "email",
"title": "Search On"
}
},
{
"name": "keyword",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Keyword"
}
},
{
"name": "max_results",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": 10,
"title": "Max Results"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSearchResults"
}
}
}
},
"404": {
"description": "Not found"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/users/create": {
"post": {
"tags": [
"users"
],
"summary": "Create User",
"description": "Craete a new user.\n\n**Args:**\n- user_in (UserCreate): JSON of the user to create. Forename, surname and email. Email must be unique.\n\n**Returns:**\n- User: User object",
"operationId": "users-create_user",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserCreate"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"404": {
"description": "Not found"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/spells/get/": {
"get": {
"tags": [
"spells"
],
"summary": "Get Spell",
"description": "Returns a spell from a spell_id.\n\n**Returns:**\n- spell: spell object.",
"operationId": "spells-get_spell",
"parameters": [
{
"name": "spell_id",
"in": "query",
"required": true,
"schema": {
"type": "string",
"title": "Spell Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Spell"
}
}
}
},
"404": {
"description": "Not found"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/v1/spells/get-all/": {
"get": {
"tags": [
"spells"
],
"summary": "Get All Spells",
"description": "Returns a list of all spells.\n\n**Returns:**\n- list[spell]: List of all spells.",
"operationId": "spells-get_all_spells",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/Spell"
},
"type": "array",
"title": "Response Spells-Get All Spells"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
},
"/api/v1/spells/search/": {
"get": {
"tags": [
"spells"
],
"summary": "Search Spells",
"description": "Search for spells based on a keyword and return the top `max_results` items.\n\n**Args:**\n- keyword (str, optional): The keyword to search for. Defaults to None.\n- max_results (int, optional): The maximum number of search results to return. Defaults to 10.\n- search_on (str, optional): The field to perform the search on. Defaults to \"email\".\n\n**Returns:**\n- spellSearchResults: Object containing a list of the top `max_results` items that match the keyword.",
"operationId": "spells-search_spells",
"parameters": [
{
"name": "search_on",
"in": "query",
"required": false,
"schema": {
"enum": [
"id",
"spells",
"description"
],
"type": "string",
"default": "spells",
"title": "Search On"
}
},
{
"name": "keyword",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Keyword"
}
},
{
"name": "max_results",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": 10,
"title": "Max Results"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SpellSearchResults"
}
}
}
},
"404": {
"description": "Not found"
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"Spell": {
"properties": {
"id": {
"type": "string",
"title": "Id"
},
"name": {
"type": "string",
"title": "Name"
},
"description": {
"type": "string",
"title": "Description"
}
},
"type": "object",
"required": [
"id",
"name",
"description"
],
"title": "Spell"
},
"SpellSearchResults": {
"properties": {
"results": {
"items": {
"$ref": "#/components/schemas/Spell"
},
"type": "array",
"title": "Results"
}
},
"type": "object",
"required": [
"results"
],
"title": "SpellSearchResults"
},
"User": {
"properties": {
"id": {
"type": "string",
"title": "Id"
},
"forename": {
"type": "string",
"title": "Forename"
},
"surname": {
"type": "string",
"title": "Surname"
},
"email": {
"type": "string",
"format": "email",
"title": "Email"
}
},
"type": "object",
"required": [
"id",
"forename",
"surname",
"email"
],
"title": "User"
},
"UserCreate": {
"properties": {
"forename": {
"type": "string",
"title": "Forename"
},
"surname": {
"type": "string",
"title": "Surname"
},
"email": {
"type": "string",
"format": "email",
"title": "Email"
}
},
"type": "object",
"required": [
"forename",
"surname",
"email"
],
"title": "UserCreate"
},
"UserSearchResults": {
"properties": {
"results": {
"items": {
"$ref": "#/components/schemas/User"
},
"type": "array",
"title": "Results"
}
},
"type": "object",
"required": [
"results"
],
"title": "UserSearchResults"
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
}
}
}

View file

@ -1,13 +1,27 @@
{
"name": "docs",
"version": "0.1.0",
"scripts": {
"dev": "mintlify dev --port 3001",
"generate-api": "npx @mintlify/scraping@latest openapi-file https://next-fast-turbo-api.vercel.app/openapi.json --outDir ./api/",
"generate-api:dev": "npx @mintlify/scraping@latest openapi-file https://127.0.0.1:8000/openapi.json --outDir ./api/"
},
"dependencies": {
"@mintlify/scraping": "^3.0.88",
"mintlify": "^4.0.127"
}
}
"name": "docs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbo --port 3001",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@repo/ui": "workspace:*",
"react": "19.0.0-rc-f994737d14-20240522",
"react-dom": "19.0.0-rc-f994737d14-20240522",
"next": "15.0.0-rc.0"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "15.0.0-rc.0"
}
}

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.5 13.5V6.5V5.41421C14.5 5.149 14.3946 4.89464 14.2071 4.70711L9.79289 0.292893C9.60536 0.105357 9.351 0 9.08579 0H8H3H1.5V1.5V13.5C1.5 14.8807 2.61929 16 4 16H12C13.3807 16 14.5 14.8807 14.5 13.5ZM13 13.5V6.5H9.5H8V5V1.5H3V13.5C3 14.0523 3.44772 14.5 4 14.5H12C12.5523 14.5 13 14.0523 13 13.5ZM9.5 5V2.12132L12.3787 5H9.5ZM5.13 5.00062H4.505V6.25062H5.13H6H6.625V5.00062H6H5.13ZM4.505 8H5.13H11H11.625V9.25H11H5.13H4.505V8ZM5.13 11H4.505V12.25H5.13H11H11.625V11H11H5.13Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 645 B

View file

@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_868_525)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.268 14.0934C11.9051 13.4838 13.2303 12.2333 13.9384 10.6469C13.1192 10.7941 12.2138 10.9111 11.2469 10.9925C11.0336 12.2005 10.695 13.2621 10.268 14.0934ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM8.48347 14.4823C8.32384 14.494 8.16262 14.5 8 14.5C7.83738 14.5 7.67616 14.494 7.51654 14.4823C7.5132 14.4791 7.50984 14.4759 7.50647 14.4726C7.2415 14.2165 6.94578 13.7854 6.67032 13.1558C6.41594 12.5744 6.19979 11.8714 6.04101 11.0778C6.67605 11.1088 7.33104 11.125 8 11.125C8.66896 11.125 9.32395 11.1088 9.95899 11.0778C9.80021 11.8714 9.58406 12.5744 9.32968 13.1558C9.05422 13.7854 8.7585 14.2165 8.49353 14.4726C8.49016 14.4759 8.4868 14.4791 8.48347 14.4823ZM11.4187 9.72246C12.5137 9.62096 13.5116 9.47245 14.3724 9.28806C14.4561 8.87172 14.5 8.44099 14.5 8C14.5 7.55901 14.4561 7.12828 14.3724 6.71194C13.5116 6.52755 12.5137 6.37904 11.4187 6.27753C11.4719 6.83232 11.5 7.40867 11.5 8C11.5 8.59133 11.4719 9.16768 11.4187 9.72246ZM10.1525 6.18401C10.2157 6.75982 10.25 7.36805 10.25 8C10.25 8.63195 10.2157 9.24018 10.1525 9.81598C9.46123 9.85455 8.7409 9.875 8 9.875C7.25909 9.875 6.53877 9.85455 5.84749 9.81598C5.7843 9.24018 5.75 8.63195 5.75 8C5.75 7.36805 5.7843 6.75982 5.84749 6.18401C6.53877 6.14545 7.25909 6.125 8 6.125C8.74091 6.125 9.46123 6.14545 10.1525 6.18401ZM11.2469 5.00748C12.2138 5.08891 13.1191 5.20593 13.9384 5.35306C13.2303 3.7667 11.9051 2.51622 10.268 1.90662C10.695 2.73788 11.0336 3.79953 11.2469 5.00748ZM8.48347 1.51771C8.4868 1.52089 8.49016 1.52411 8.49353 1.52737C8.7585 1.78353 9.05422 2.21456 9.32968 2.84417C9.58406 3.42562 9.80021 4.12856 9.95899 4.92219C9.32395 4.89118 8.66896 4.875 8 4.875C7.33104 4.875 6.67605 4.89118 6.04101 4.92219C6.19978 4.12856 6.41594 3.42562 6.67032 2.84417C6.94578 2.21456 7.2415 1.78353 7.50647 1.52737C7.50984 1.52411 7.51319 1.52089 7.51653 1.51771C7.67615 1.50597 7.83738 1.5 8 1.5C8.16262 1.5 8.32384 1.50597 8.48347 1.51771ZM5.73202 1.90663C4.0949 2.51622 2.76975 3.7667 2.06159 5.35306C2.88085 5.20593 3.78617 5.08891 4.75309 5.00748C4.96639 3.79953 5.30497 2.73788 5.73202 1.90663ZM4.58133 6.27753C3.48633 6.37904 2.48837 6.52755 1.62761 6.71194C1.54392 7.12828 1.5 7.55901 1.5 8C1.5 8.44099 1.54392 8.87172 1.62761 9.28806C2.48837 9.47245 3.48633 9.62096 4.58133 9.72246C4.52807 9.16768 4.5 8.59133 4.5 8C4.5 7.40867 4.52807 6.83232 4.58133 6.27753ZM4.75309 10.9925C3.78617 10.9111 2.88085 10.7941 2.06159 10.6469C2.76975 12.2333 4.0949 13.4838 5.73202 14.0934C5.30497 13.2621 4.96639 12.2005 4.75309 10.9925Z" fill="#666666"/>
</g>
<defs>
<clipPath id="clip0_868_525">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,10 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_977_547)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5 3L18.5 17H2.5L10.5 3Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_977_547">
<rect width="16" height="16" fill="white" transform="translate(2.5 2)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 367 B

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5H14.5V12.5C14.5 13.0523 14.0523 13.5 13.5 13.5H2.5C1.94772 13.5 1.5 13.0523 1.5 12.5V2.5ZM0 1H1.5H14.5H16V2.5V12.5C16 13.8807 14.8807 15 13.5 15H2.5C1.11929 15 0 13.8807 0 12.5V2.5V1ZM3.75 5.5C4.16421 5.5 4.5 5.16421 4.5 4.75C4.5 4.33579 4.16421 4 3.75 4C3.33579 4 3 4.33579 3 4.75C3 5.16421 3.33579 5.5 3.75 5.5ZM7 4.75C7 5.16421 6.66421 5.5 6.25 5.5C5.83579 5.5 5.5 5.16421 5.5 4.75C5.5 4.33579 5.83579 4 6.25 4C6.66421 4 7 4.33579 7 4.75ZM8.75 5.5C9.16421 5.5 9.5 5.16421 9.5 4.75C9.5 4.33579 9.16421 4 8.75 4C8.33579 4 8 4.33579 8 4.75C8 5.16421 8.33579 5.5 8.75 5.5Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 750 B

18
apps/docs/tsconfig.json Normal file
View file

@ -0,0 +1,18 @@
{
"extends": "@repo/typescript-config/nextjs.json",
"compilerOptions": {
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"next.config.mjs",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": ["node_modules"]
}

36
apps/web/.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# env files (can opt-in for commiting if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -1,17 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: Chrome",
"type": "node-terminal",
"request": "launch",
"command": "pnpm run dev",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome",
"webRoot": "${workspaceFolder}"
}
}
]
}

36
apps/web/README.md Normal file
View file

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
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.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

BIN
apps/web/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

View file

@ -1,13 +1,39 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "../components/theme/theme.css";
:root {
--background: #ffffff;
--foreground: #171717;
}
@layer base {
* {
@apply border-border;
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
body {
@apply bg-background text-foreground;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: var(--foreground);
background: var(--background);
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}
}

View file

@ -1,48 +1,30 @@
import "./globals.css";
import type { Metadata } from "next";
import { Inter as FontSans } from "next/font/google";
import { DashboardLayout } from "@/components/layouts/dashboard";
import { cn } from "@/lib/utils";
import { ThemeProvider } from "@/components/theme/theme-provider";
import { OpenAPI } from "@/lib/api/client";
import { TailwindIndicator } from "@/components/tailwind-indicator";
import localFont from "next/font/local";
import "./globals.css";
export const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
});
if (process.env.NODE_ENV === "production") {
OpenAPI.BASE = "https://next-fast-turbo.vercel.app";
}
console.log("Using OpenAPI.base", OpenAPI.BASE);
export const metadata: Metadata = {
title: "Next-Fast-Turbo",
description: "A Next.js, FastAPI and Turbo project scaffol",
icons: {
icon: ["/favicon.png"],
},
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
}: Readonly<{
children: React.ReactNode;
}): JSX.Element {
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={cn(fontSans.variable, "bg-background font-sans")}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<DashboardLayout>{children}</DashboardLayout>
<TailwindIndicator />
</ThemeProvider>
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
{children}
</body>
</html>
);

View file

@ -0,0 +1,188 @@
.page {
--gray-rgb: 0, 0, 0;
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
--button-primary-hover: #383838;
--button-secondary-hover: #f2f2f2;
display: grid;
grid-template-rows: 20px 1fr 20px;
align-items: center;
justify-items: center;
min-height: 100svh;
padding: 80px;
gap: 64px;
font-synthesis: none;
}
@media (prefers-color-scheme: dark) {
.page {
--gray-rgb: 255, 255, 255;
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
--button-primary-hover: #ccc;
--button-secondary-hover: #1a1a1a;
}
}
.main {
display: flex;
flex-direction: column;
gap: 32px;
grid-row-start: 2;
}
.main ol {
font-family: var(--font-geist-mono);
padding-left: 0;
margin: 0;
font-size: 14px;
line-height: 24px;
letter-spacing: -0.01em;
list-style-position: inside;
}
.main li:not(:last-of-type) {
margin-bottom: 8px;
}
.main code {
font-family: inherit;
background: var(--gray-alpha-100);
padding: 2px 4px;
border-radius: 4px;
font-weight: 600;
}
.ctas {
display: flex;
gap: 16px;
}
.ctas a {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: none;
font-family: var(--font-geist-sans);
border: 1px solid transparent;
transition: background 0.2s, color 0.2s, border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
}
a.primary {
background: var(--foreground);
color: var(--background);
gap: 8px;
}
a.secondary {
border-color: var(--gray-alpha-200);
min-width: 180px;
}
button.secondary {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: none;
font-family: var(--font-geist-sans);
border: 1px solid transparent;
transition: background 0.2s, color 0.2s, border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
background: transparent;
border-color: var(--gray-alpha-200);
min-width: 180px;
}
.footer {
font-family: var(--font-geist-sans);
grid-row-start: 3;
display: flex;
gap: 24px;
}
.footer a {
display: flex;
align-items: center;
gap: 8px;
}
.footer img {
flex-shrink: 0;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
a.primary:hover {
background: var(--button-primary-hover);
border-color: transparent;
}
a.secondary:hover {
background: var(--button-secondary-hover);
border-color: transparent;
}
.footer a:hover {
text-decoration: underline;
text-underline-offset: 4px;
}
}
@media (max-width: 600px) {
.page {
padding: 32px;
padding-bottom: 80px;
}
.main {
align-items: center;
}
.main ol {
text-align: center;
}
.ctas {
flex-direction: column;
}
.ctas a {
font-size: 14px;
height: 40px;
padding: 0 16px;
}
a.secondary {
min-width: auto;
}
.footer {
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
}
@media (prefers-color-scheme: dark) {
.logo {
filter: invert();
}
}

View file

@ -1,11 +1,99 @@
import { CardsStats } from "./placeholder-stats";
import SearchUsers from "@/components/search-users";
import Image from "next/image";
import { Button } from "@repo/ui/button";
import styles from "./page.module.css";
export default async function Page() {
export default function Home() {
return (
<div className="flex flex-col gap-4">
<CardsStats />
<SearchUsers />
<div className={styles.page}>
<main className={styles.main}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol>
<li>
Get started by editing <code>app/page.tsx</code>
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className={styles.ctas}>
<a
className={styles.primary}
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className={styles.logo}
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
className={styles.secondary}
>
Read our docs
</a>
</div>
<Button appName="web" className={styles.secondary}>
Open alert
</Button>
</main>
<footer className={styles.footer}>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file-text.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
);
}

View file

@ -1,17 +0,0 @@
// Example cards from ShadCN: https://github.com/shadcn-ui/ui/tree/0fae3fd93ae749aca708bdfbbbeddc5d576bfb2e/apps/www/registry/default/example/cards
import { FlexWrapper } from "@/components/flex-wrapper";
import { DemoRevenue } from "@/components/demo-revenue";
import { DemoSubscriptions } from "@/components/demo-subscriptions";
import { DemoExercise } from "@/components/demo-exercise";
import { DemoGoal } from "@/components/demo-goal";
export function CardsStats() {
return (
<FlexWrapper columns="4">
<DemoRevenue />
<DemoSubscriptions />
<DemoExercise />
<DemoGoal />
</FlexWrapper>
);
}

Some files were not shown because too many files have changed in this diff Show more