Added ui
4
.gitignore
vendored
@@ -3,5 +3,5 @@ target/
|
|||||||
.idea/
|
.idea/
|
||||||
**/Cargo.lock
|
**/Cargo.lock
|
||||||
|
|
||||||
logs/
|
.next/
|
||||||
app/
|
node_modules/
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
RUST_LOG=warn,bot=info
|
RUST_LOG=warn,bot=info
|
||||||
COMPOSE_PROJECT_NAME=siren
|
|
||||||
|
|
||||||
SERVICE_HOST=localhost
|
SERVICE_HOST=localhost
|
||||||
SERVICE_PORT=5000
|
SERVICE_PORT=5000
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
|
name: siren
|
||||||
services:
|
services:
|
||||||
bot:
|
bot:
|
||||||
image: siren-bot:${BOT_VERSION:-latest}
|
image: siren-bot:${BOT_VERSION:-latest}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
RUST_LOG=warn,service=info
|
RUST_LOG=warn,service=info
|
||||||
COMPOSE_PROJECT_NAME=siren
|
|
||||||
|
|
||||||
DATABASE_USER=siren
|
DATABASE_USER=siren
|
||||||
DATABASE_PASSWORD=
|
DATABASE_PASSWORD=
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
SIREN_VERSION=0.2.4
|
SIREN_VERSION=0.2.5
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "service"
|
name = "service"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Ben Sherriff <hello@bensherriff.com>"]
|
authors = ["Ben Sherriff <hello@bensherriff.com>"]
|
||||||
repository = "https://github.com/bensherriff/siren"
|
repository = "https://github.com/bensherriff/siren"
|
||||||
@@ -14,6 +14,7 @@ path = "src/lib.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.4.0"
|
actix-web = "4.4.0"
|
||||||
actix-rt = "2.9.0"
|
actix-rt = "2.9.0"
|
||||||
|
actix-cors = "0.6.4"
|
||||||
actix-web-httpauth = "0.8.1"
|
actix-web-httpauth = "0.8.1"
|
||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"saving_throw": [
|
"saving_throw": [
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "round"
|
"unit": "round"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "sphere",
|
"type": "sphere",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "round"
|
"unit": "round"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -133,12 +133,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 120,
|
"value": 120,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "round"
|
"unit": "round"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -176,17 +176,17 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "cube",
|
"type": "cube",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "hour"
|
"unit": "hour"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -231,17 +231,17 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "cube",
|
"type": "cube",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "concentration",
|
"type": "concentration",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -275,12 +275,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 120,
|
"value": 120,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "concentration",
|
"type": "concentration",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -303,7 +303,7 @@
|
|||||||
],
|
],
|
||||||
"description": {
|
"description": {
|
||||||
"entries": [
|
"entries": [
|
||||||
"You create up to four torch-amountd lights within range, making them appear as torches, lanterns, or glowing orbs that hover in the air for the duration. You can also combine the four lights into one glowing vaguely humanoid form of Medium amount. Whichever form you choose, each light sheds dim light in a 10-foot radius.",
|
"You create up to four torch-valued lights within range, making them appear as torches, lanterns, or glowing orbs that hover in the air for the duration. You can also combine the four lights into one glowing vaguely humanoid form of Medium value. Whichever form you choose, each light sheds dim light in a 10-foot radius.",
|
||||||
"As a bonus action on your turn, you can move the lights up to 60 feet to a new spot within range. A light must be within 20 feet of another light created by this spell, and a light winks out if it exceeds the spell's range."
|
"As a bonus action on your turn, you can move the lights up to 60 feet to a new spot within range. A light must be within 20 feet of another light created by this spell, and a light winks out if it exceeds the spell's range."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -314,12 +314,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -358,12 +358,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 120,
|
"value": 120,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -398,7 +398,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": true,
|
"ritual": true,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -412,7 +412,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 8,
|
"value": 8,
|
||||||
"unit": "hours"
|
"unit": "hours"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -434,12 +434,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 120,
|
"value": 120,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -474,7 +474,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -484,12 +484,12 @@
|
|||||||
"verbal": false,
|
"verbal": false,
|
||||||
"somatic": true,
|
"somatic": true,
|
||||||
"material": true,
|
"material": true,
|
||||||
"materials_needed": "a small amount of makeup applied to the face as this spell is cast"
|
"materials_needed": "a small value of makeup applied to the face as this spell is cast"
|
||||||
},
|
},
|
||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "concentration",
|
"type": "concentration",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -510,12 +510,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"saving_throw": [
|
"saving_throw": [
|
||||||
@@ -552,7 +552,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -560,7 +560,7 @@
|
|||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "sphere",
|
"type": "sphere",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -596,7 +596,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -610,7 +610,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "concentration",
|
"type": "concentration",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -631,12 +631,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -674,12 +674,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"saving_throw": [
|
"saving_throw": [
|
||||||
@@ -716,7 +716,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -724,7 +724,7 @@
|
|||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "sphere",
|
"type": "sphere",
|
||||||
"amount": 20,
|
"value": 20,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"saving_throw": [
|
"saving_throw": [
|
||||||
@@ -739,7 +739,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "hour"
|
"unit": "hour"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -761,12 +761,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "self",
|
"type": "self",
|
||||||
"amount": 15,
|
"value": 15,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"saving_throw": [
|
"saving_throw": [
|
||||||
@@ -803,12 +803,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -819,7 +819,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -842,7 +842,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "bonus"
|
"unit": "bonus"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -860,7 +860,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -881,7 +881,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -916,12 +916,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 120,
|
"value": 120,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -933,7 +933,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "round"
|
"unit": "round"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -955,12 +955,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"saving_throw": [
|
"saving_throw": [
|
||||||
@@ -996,12 +996,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1013,7 +1013,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1037,17 +1037,17 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "cube",
|
"type": "cube",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1061,7 +1061,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "hour"
|
"unit": "hour"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1090,12 +1090,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 10,
|
"value": 10,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"saving_throw": [
|
"saving_throw": [
|
||||||
@@ -1134,11 +1134,11 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"unit": "action",
|
"unit": "action",
|
||||||
"amount": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 10,
|
"value": 10,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1149,7 +1149,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "hour"
|
"unit": "hour"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1183,7 +1183,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"unit": "action",
|
"unit": "action",
|
||||||
"amount": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "self"
|
"type": "self"
|
||||||
@@ -1220,7 +1220,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"unit": "action",
|
"unit": "action",
|
||||||
"amount": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "self"
|
"type": "self"
|
||||||
@@ -1237,7 +1237,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 10,
|
"value": 10,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1261,11 +1261,11 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"unit": "action",
|
"unit": "action",
|
||||||
"amount": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -1301,7 +1301,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"unit": "action",
|
"unit": "action",
|
||||||
"amount": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "touch"
|
"type": "touch"
|
||||||
@@ -1315,7 +1315,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "concentration",
|
"type": "concentration",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "round"
|
"unit": "round"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1336,12 +1336,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"saving_throw": [
|
"saving_throw": [
|
||||||
@@ -1378,7 +1378,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -1389,7 +1389,7 @@
|
|||||||
],
|
],
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1419,17 +1419,17 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"area": {
|
"area": {
|
||||||
"type": "cube",
|
"type": "cube",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1443,7 +1443,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "hour"
|
"unit": "hour"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1473,7 +1473,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "bonus"
|
"unit": "bonus"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -1492,7 +1492,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1513,7 +1513,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -1551,7 +1551,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
@@ -1584,7 +1584,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -1595,7 +1595,7 @@
|
|||||||
],
|
],
|
||||||
"range": {
|
"range": {
|
||||||
"type": "self",
|
"type": "self",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1625,12 +1625,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1641,7 +1641,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "timed",
|
"type": "timed",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "minutes"
|
"unit": "minutes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1674,7 +1674,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -1683,7 +1683,7 @@
|
|||||||
"attack_type": "melee",
|
"attack_type": "melee",
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1715,7 +1715,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -1726,7 +1726,7 @@
|
|||||||
],
|
],
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1756,7 +1756,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -1767,7 +1767,7 @@
|
|||||||
],
|
],
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1797,12 +1797,12 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 30,
|
"value": 30,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1813,7 +1813,7 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"type": "concentration",
|
"type": "concentration",
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "round"
|
"unit": "round"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1834,7 +1834,7 @@
|
|||||||
"level": 0,
|
"level": 0,
|
||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"amount": 1,
|
"value": 1,
|
||||||
"unit": "action"
|
"unit": "action"
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
@@ -1845,7 +1845,7 @@
|
|||||||
],
|
],
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 60,
|
"value": 60,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1877,7 +1877,7 @@
|
|||||||
"ritual": false,
|
"ritual": false,
|
||||||
"casting_time": {
|
"casting_time": {
|
||||||
"unit": "action",
|
"unit": "action",
|
||||||
"amount": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
"damage_inflict": [
|
"damage_inflict": [
|
||||||
"radiant"
|
"radiant"
|
||||||
@@ -1887,7 +1887,7 @@
|
|||||||
],
|
],
|
||||||
"range": {
|
"range": {
|
||||||
"type": "point",
|
"type": "point",
|
||||||
"amount": 5,
|
"value": 5,
|
||||||
"unit": "feet"
|
"unit": "feet"
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
|
name: siren
|
||||||
services:
|
services:
|
||||||
service:
|
service:
|
||||||
image: siren-service:${SIREN_VERSION:-latest}
|
image: siren-service:${SIREN_VERSION:-latest}
|
||||||
@@ -14,7 +15,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: 5432
|
DATABASE_PORT: 5432
|
||||||
SERVICE_HOST: siren
|
SERVICE_HOST: service
|
||||||
SERVICE_PORT: 5000
|
SERVICE_PORT: 5000
|
||||||
ports:
|
ports:
|
||||||
- ${SERVICE_PORT:-5000}:5000
|
- ${SERVICE_PORT:-5000}:5000
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::db::{schema::spells::{self}, classes::AbilityType, conditions::Condit
|
|||||||
|
|
||||||
use super::{SchoolType, CastingTime, CastingType, SpellAttackType, SpellDamageType, Range, Area, Components, Duration, Source, Description, DurationType};
|
use super::{SchoolType, CastingTime, CastingType, SpellAttackType, SpellDamageType, Range, Area, Components, Duration, Source, Description, DurationType};
|
||||||
|
|
||||||
#[derive(Queryable, QueryableByName)]
|
#[derive(Queryable, QueryableByName, Serialize, Deserialize)]
|
||||||
#[diesel(table_name = spells)]
|
#[diesel(table_name = spells)]
|
||||||
pub struct QuerySpell {
|
pub struct QuerySpell {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
@@ -191,6 +191,7 @@ impl InsertSpell {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Spell {
|
pub struct Spell {
|
||||||
|
pub id: Option<i32>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub school: SchoolType,
|
pub school: SchoolType,
|
||||||
pub level: i32,
|
pub level: i32,
|
||||||
@@ -226,17 +227,18 @@ impl From<QuerySpell> for Spell {
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("Failed to parse spell: {}", err);
|
log::error!("Failed to parse spell: {}", err);
|
||||||
Self {
|
Self {
|
||||||
|
id: None,
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
school: SchoolType::Abjuration,
|
school: SchoolType::Abjuration,
|
||||||
level: 0,
|
level: 0,
|
||||||
ritual: false,
|
ritual: false,
|
||||||
casting_time: CastingTime { amount: 0, casting_type: CastingType::Action },
|
casting_time: CastingTime { value: 0, casting_type: CastingType::Action },
|
||||||
saving_throw: None,
|
saving_throw: None,
|
||||||
attack_type: None,
|
attack_type: None,
|
||||||
damage_inflict: None,
|
damage_inflict: None,
|
||||||
damage_resist: None,
|
damage_resist: None,
|
||||||
conditions: None,
|
conditions: None,
|
||||||
range: Range { range_type: "".to_string(), amount: None, unit: None },
|
range: Range { range_type: "".to_string(), value: None, unit: None },
|
||||||
area: None,
|
area: None,
|
||||||
components: Components { verbal: false, somatic: false, material: false, materials_needed: None, materials_cost: None, materials_consumed: None },
|
components: Components { verbal: false, somatic: false, material: false, materials_needed: None, materials_cost: None, materials_consumed: None },
|
||||||
durations: vec![],
|
durations: vec![],
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
|
|||||||
None => None
|
None => None
|
||||||
};
|
};
|
||||||
// Limit must be between 1 and 100
|
// Limit must be between 1 and 100
|
||||||
let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(20), 1), 100);
|
let limit = std::cmp::min(std::cmp::max(params.limit.unwrap_or(100), 1), 100);
|
||||||
let total_count = QuerySpell::get_count(&filters).unwrap();
|
let total_count = QuerySpell::get_count(&filters).unwrap();
|
||||||
let max_page = std::cmp::max((total_count as f64 / limit as f64).ceil() as i32, 1);
|
let max_page = std::cmp::max((total_count as f64 / limit as f64).ceil() as i32, 1);
|
||||||
// Page must be between 1 and max_page
|
// Page must be between 1 and max_page
|
||||||
@@ -82,8 +82,11 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
|
|||||||
match web::block(move || QuerySpell::get_all(&filters, limit, page)).await.unwrap() {
|
match web::block(move || QuerySpell::get_all(&filters, limit, page)).await.unwrap() {
|
||||||
Ok(spells) => {
|
Ok(spells) => {
|
||||||
let mut response: Vec<Spell> = Vec::new();
|
let mut response: Vec<Spell> = Vec::new();
|
||||||
for spell in spells {
|
for query_spell in spells {
|
||||||
response.push(Spell::from(spell));
|
let id = query_spell.id;
|
||||||
|
let mut spell = Spell::from(query_spell);
|
||||||
|
spell.id = Some(id);
|
||||||
|
response.push(spell);
|
||||||
}
|
}
|
||||||
HttpResponse::Ok().json(GetResponse {
|
HttpResponse::Ok().json(GetResponse {
|
||||||
data: response,
|
data: response,
|
||||||
@@ -112,10 +115,15 @@ async fn get_by_id(id: web::Path<String>) -> HttpResponse {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
match web::block(move || QuerySpell::get_by_id(id)).await.unwrap() {
|
match web::block(move || QuerySpell::get_by_id(id)).await.unwrap() {
|
||||||
Ok(spell) => HttpResponse::Ok().json(GetResponse {
|
Ok(query_spell) => {
|
||||||
data: Spell::from(spell),
|
let id = query_spell.id;
|
||||||
metadata: None
|
let mut spell = Spell::from(query_spell);
|
||||||
}),
|
spell.id = Some(id);
|
||||||
|
HttpResponse::Ok().json(GetResponse {
|
||||||
|
data: spell,
|
||||||
|
metadata: None
|
||||||
|
})
|
||||||
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("{:?}", err.message);
|
error!("{:?}", err.message);
|
||||||
ResponseError::error_response(&err)
|
ResponseError::error_response(&err)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl FromStr for SchoolType {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct CastingTime {
|
pub struct CastingTime {
|
||||||
pub amount: i32,
|
pub value: i32,
|
||||||
#[serde(rename = "unit")]
|
#[serde(rename = "unit")]
|
||||||
pub casting_type: CastingType
|
pub casting_type: CastingType
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ pub struct Range {
|
|||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub range_type: String,
|
pub range_type: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub amount: Option<i32>,
|
pub value: Option<i32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub unit: Option<String>
|
pub unit: Option<String>
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ pub struct Area {
|
|||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub area_type: AreaType,
|
pub area_type: AreaType,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub amount: Option<i32>,
|
pub value: Option<i32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub unit: Option<String>
|
pub unit: Option<String>
|
||||||
}
|
}
|
||||||
@@ -270,7 +270,7 @@ pub struct Duration {
|
|||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub duration_type: DurationType,
|
pub duration_type: DurationType,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub amount: Option<i32>,
|
pub value: Option<i32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub unit: Option<String>
|
pub unit: Option<String>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ extern crate diesel_migrations;
|
|||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
use actix_cors::Cors;
|
||||||
use actix_web::{HttpServer, App};
|
use actix_web::{HttpServer, App};
|
||||||
|
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
@@ -22,9 +23,15 @@ async fn main() -> std::io::Result<()> {
|
|||||||
let port = env::var("SERVICE_PORT").unwrap_or("5000".to_string());
|
let port = env::var("SERVICE_PORT").unwrap_or("5000".to_string());
|
||||||
|
|
||||||
match HttpServer::new(|| {
|
match HttpServer::new(|| {
|
||||||
|
let cors = Cors::default()
|
||||||
|
.allow_any_origin()
|
||||||
|
.allow_any_method()
|
||||||
|
.allow_any_header()
|
||||||
|
.max_age(3600);
|
||||||
App::new()
|
App::new()
|
||||||
.configure(db::messages::init_routes)
|
.configure(db::messages::init_routes)
|
||||||
.configure(db::spells::init_routes)
|
.configure(db::spells::init_routes)
|
||||||
|
.wrap(cors)
|
||||||
})
|
})
|
||||||
.bind(format!("{}:{}", host, port)) {
|
.bind(format!("{}:{}", host, port)) {
|
||||||
Ok(b) => {
|
Ok(b) => {
|
||||||
|
|||||||
5
ui/.env.TEMPLATE
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
SERVICE_HOST=service
|
||||||
|
SERVICE_PORT=5000
|
||||||
|
|
||||||
|
UI_PORT=8080
|
||||||
|
NODE_ENV=development
|
||||||
17
ui/.eslintrc.json
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint/eslint-plugin"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/interface-name-prefix": "off",
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
ui/.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"jsxSingleQuote": true,
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
||||||
39
ui/Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Base
|
||||||
|
FROM node:18-alpine AS base
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
FROM base as deps
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Dev
|
||||||
|
FROM base AS dev
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Builder
|
||||||
|
FROM base AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Runner
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
|
||||||
|
COPY --from=builder /app/next.config.js ./
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/package.json ./package.json
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV PORT 3000
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
26
ui/Makefile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!make
|
||||||
|
|
||||||
|
SHELL := /bin/bash
|
||||||
|
|
||||||
|
.PHONY: help build start stop lint
|
||||||
|
|
||||||
|
help: ## This info
|
||||||
|
@echo
|
||||||
|
@cat Makefile | grep -E '^[a-zA-Z\/_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
@echo
|
||||||
|
|
||||||
|
build: ## Install the dependencies and build
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
up: ## Start the dev instance
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
down: ## Stop the dev instance
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
lint: ## Run the linter
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
clean: ## Remove node modules
|
||||||
|
docker compose down && \
|
||||||
|
docker image rm siren-ui
|
||||||
26
ui/docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
name: siren
|
||||||
|
services:
|
||||||
|
ui:
|
||||||
|
container_name: siren-ui
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=${NODE_ENV:-development}
|
||||||
|
ports:
|
||||||
|
- ${UI_PORT:-8080}:3000
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
target: dev
|
||||||
|
command: "npm run dev"
|
||||||
|
volumes:
|
||||||
|
- ./src:/app/src
|
||||||
|
- ./public:/app/public
|
||||||
|
- ./styles:/app/styles
|
||||||
|
networks:
|
||||||
|
- siren-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
siren-frontend: {}
|
||||||
5
ui/next-env.d.ts
vendored
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||||
22
ui/next.config.js
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
swcMinify: true,
|
||||||
|
eslint: {
|
||||||
|
ignoreDuringBuilds: true
|
||||||
|
},
|
||||||
|
webpackDevMiddleware: (config) => {
|
||||||
|
config.watchOptions = {
|
||||||
|
poll: 1000,
|
||||||
|
aggregateTimeout: 300
|
||||||
|
};
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
publicRuntimeConfig: {
|
||||||
|
// remove private variables from processEnv
|
||||||
|
processEnv: Object.fromEntries(Object.entries(process.env).filter(([key]) => key.includes('NEXT_PUBLIC_')))
|
||||||
|
},
|
||||||
|
output: 'standalone'
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
5425
ui/package-lock.json
generated
Normal file
42
ui/package.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "siren-ui",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mantine/core": "^7.1.2",
|
||||||
|
"@mantine/hooks": "^7.1.2",
|
||||||
|
"@mantine/modals": "^7.1.2",
|
||||||
|
"@mantine/notifications": "^7.1.2",
|
||||||
|
"axios": "^1.5.1",
|
||||||
|
"next": "^13.5.4",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-icons": "^4.11.0",
|
||||||
|
"react-leaflet": "^4.2.1",
|
||||||
|
"recharts": "^2.8.0",
|
||||||
|
"recoil": "^0.7.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "20.8.2",
|
||||||
|
"@types/react": "18.2.24",
|
||||||
|
"@types/react-dom": "18.2.8",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||||
|
"@typescript-eslint/parser": "^6.7.4",
|
||||||
|
"autoprefixer": "^10.4.16",
|
||||||
|
"eslint": "8.50.0",
|
||||||
|
"eslint-config-next": "13.5.4",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
"postcss": "^8.4.31",
|
||||||
|
"postcss-import": "^15.1.0",
|
||||||
|
"postcss-preset-mantine": "^1.8.0",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"typescript": "5.2.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
ui/postcss.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
'postcss-preset-mantine': {},
|
||||||
|
'postcss-import': {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
BIN
ui/public/favicon.ico
Executable file
|
After Width: | Height: | Size: 25 KiB |
BIN
ui/public/layers-2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
ui/public/layers.png
Normal file
|
After Width: | Height: | Size: 696 B |
BIN
ui/public/marker-icon-2x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
ui/public/marker-icon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
ui/public/marker-shadow.png
Normal file
|
After Width: | Height: | Size: 618 B |
4
ui/public/vercel.svg
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
25
ui/src/api/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
const serviceHost = process.env.SERVICE_HOST || 'http://localhost';
|
||||||
|
const servicePort = process.env.SERVICE_PORT || 5000;
|
||||||
|
|
||||||
|
export async function getRequest(endpoint: string, params: any): Promise<AxiosResponse<any, any> | undefined> {
|
||||||
|
const response = await axios
|
||||||
|
.get(`${serviceHost}:${servicePort}/${endpoint}`, { params })
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
return response || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postRequest(endpoint: string, body: any): Promise<AxiosResponse<any, any> | undefined> {
|
||||||
|
const response = await axios
|
||||||
|
.post(`${serviceHost}:${servicePort}/${endpoint}`, { body })
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
return response || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Metadata {
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
pages: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
37
ui/src/api/spells.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { getRequest } from '.';
|
||||||
|
import { GetSpellsResponse } from './spells.types';
|
||||||
|
|
||||||
|
interface GetSpellsParams {
|
||||||
|
name?: string;
|
||||||
|
schools?: string[];
|
||||||
|
levels?: number[];
|
||||||
|
ritual?: boolean;
|
||||||
|
concentration?: boolean;
|
||||||
|
classes?: string[];
|
||||||
|
damage_inflict?: string[];
|
||||||
|
damage_resist?: string[];
|
||||||
|
conditions?: string[];
|
||||||
|
saving_throw?: string[];
|
||||||
|
attack_type?: string[];
|
||||||
|
limit?: number;
|
||||||
|
page?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSpells(params?: GetSpellsParams): Promise<GetSpellsResponse> {
|
||||||
|
const response = await getRequest('spells', {
|
||||||
|
name: params?.name,
|
||||||
|
schools: params?.schools?.join(','),
|
||||||
|
levels: params?.levels?.join(','),
|
||||||
|
ritual: params?.ritual,
|
||||||
|
concentration: params?.concentration,
|
||||||
|
classes: params?.classes?.join(','),
|
||||||
|
damage_inflict: params?.damage_inflict?.join(','),
|
||||||
|
damage_resist: params?.damage_resist?.join(','),
|
||||||
|
conditions: params?.conditions?.join(','),
|
||||||
|
saving_throw: params?.saving_throw?.join(','),
|
||||||
|
attack_type: params?.attack_type?.join(','),
|
||||||
|
limit: params?.limit,
|
||||||
|
page: params?.page
|
||||||
|
});
|
||||||
|
return response?.data || { data: [] };
|
||||||
|
}
|
||||||
82
ui/src/api/spells.types.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { Metadata } from '.';
|
||||||
|
|
||||||
|
export interface Spell {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
school: string;
|
||||||
|
level: number;
|
||||||
|
ritual: boolean;
|
||||||
|
casting_time: CastingTime;
|
||||||
|
saving_throw?: string[];
|
||||||
|
attack_type?: string;
|
||||||
|
damage_inflict?: string[];
|
||||||
|
damage_resist?: string[];
|
||||||
|
conditions?: string[];
|
||||||
|
range: Range;
|
||||||
|
area?: Area;
|
||||||
|
components: Components;
|
||||||
|
durations: Duration[];
|
||||||
|
classes: string[];
|
||||||
|
sources: Source[];
|
||||||
|
tags?: string[];
|
||||||
|
description?: Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CastingTime {
|
||||||
|
value: number;
|
||||||
|
unit: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Range {
|
||||||
|
type: string;
|
||||||
|
value?: number;
|
||||||
|
unit?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Area {
|
||||||
|
type: string;
|
||||||
|
value?: number;
|
||||||
|
unit?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Components {
|
||||||
|
verbal: boolean;
|
||||||
|
somatic: boolean;
|
||||||
|
material: boolean;
|
||||||
|
materials_needed?: string;
|
||||||
|
materials_cost?: number;
|
||||||
|
materials_consumed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Duration {
|
||||||
|
type: string;
|
||||||
|
value?: number;
|
||||||
|
unit?: string;
|
||||||
|
// concentration: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Source {
|
||||||
|
source: string;
|
||||||
|
page?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Description {
|
||||||
|
entries: EntryType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntryType = string | Entry;
|
||||||
|
|
||||||
|
export interface Entry {
|
||||||
|
type: string;
|
||||||
|
items: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetSpellResponse {
|
||||||
|
data: Spell;
|
||||||
|
metadata: Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetSpellsResponse {
|
||||||
|
data: Spell[];
|
||||||
|
metadata: Metadata;
|
||||||
|
}
|
||||||
5
ui/src/app/backgrounds/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
0
ui/src/app/bot/page.tsx
Normal file
5
ui/src/app/classes/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
5
ui/src/app/feats/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
5
ui/src/app/items/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
40
ui/src/app/layout.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import RecoilRootWrapper from '@app/recoil-root-wrapper';
|
||||||
|
import Topbar from '@/components/Topbar';
|
||||||
|
import { Inter } from 'next/font/google';
|
||||||
|
import { Box, MantineProvider } from '@mantine/core';
|
||||||
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
|
import { Notifications } from '@mantine/notifications';
|
||||||
|
import 'styles/globals.css';
|
||||||
|
import '@mantine/core/styles.css';
|
||||||
|
import '@mantine/notifications/styles.css';
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: 'Siren',
|
||||||
|
description: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang='en' className='h-full bg-white'>
|
||||||
|
<head>
|
||||||
|
<title>Siren</title>
|
||||||
|
</head>
|
||||||
|
<body className={`${inter.className} wrapper h-full`}>
|
||||||
|
<RecoilRootWrapper>
|
||||||
|
<MantineProvider>
|
||||||
|
<Notifications />
|
||||||
|
<ModalsProvider>
|
||||||
|
<Topbar />
|
||||||
|
<Box p='xl' pt='sm' className='h-full'>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</ModalsProvider>
|
||||||
|
</MantineProvider>
|
||||||
|
</RecoilRootWrapper>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
ui/src/app/options/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
5
ui/src/app/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
5
ui/src/app/races/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
8
ui/src/app/recoil-root-wrapper.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { RecoilRoot } from 'recoil';
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function RecoilRootWrapper({ children }: { children: ReactNode }) {
|
||||||
|
return <RecoilRoot>{children}</RecoilRoot>;
|
||||||
|
}
|
||||||
91
ui/src/app/spells/page.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { getSpells } from '@/api/spells';
|
||||||
|
import { Spell } from '@/api/spells.types';
|
||||||
|
import SpellModal from '@/components/SpellModal';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import './spells.css';
|
||||||
|
import { Box, TextInput } from '@mantine/core';
|
||||||
|
import { AiOutlineVerticalAlignTop } from 'react-icons/ai';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [cantrips, setCantrips] = useState<Spell[]>([]);
|
||||||
|
const [level1, setLevel1] = useState<Spell[]>([]);
|
||||||
|
const [level2, setLevel2] = useState<Spell[]>([]);
|
||||||
|
const [level3, setLevel3] = useState<Spell[]>([]);
|
||||||
|
const [level4, setLevel4] = useState<Spell[]>([]);
|
||||||
|
const [level5, setLevel5] = useState<Spell[]>([]);
|
||||||
|
const [level6, setLevel6] = useState<Spell[]>([]);
|
||||||
|
const [level7, setLevel7] = useState<Spell[]>([]);
|
||||||
|
const [level8, setLevel8] = useState<Spell[]>([]);
|
||||||
|
const [level9, setLevel9] = useState<Spell[]>([]);
|
||||||
|
const [activeSpell, setActiveSpell] = useState<Spell | undefined>(undefined);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [searchName, setSearchName] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getSpells({ levels: [0] }).then((s) => setCantrips(s.data));
|
||||||
|
getSpells({ levels: [1] }).then((s) => setLevel1(s.data));
|
||||||
|
getSpells({ levels: [2] }).then((s) => setLevel2(s.data));
|
||||||
|
getSpells({ levels: [3] }).then((s) => setLevel3(s.data));
|
||||||
|
getSpells({ levels: [4] }).then((s) => setLevel4(s.data));
|
||||||
|
getSpells({ levels: [5] }).then((s) => setLevel5(s.data));
|
||||||
|
getSpells({ levels: [6] }).then((s) => setLevel6(s.data));
|
||||||
|
getSpells({ levels: [7] }).then((s) => setLevel7(s.data));
|
||||||
|
getSpells({ levels: [8] }).then((s) => setLevel8(s.data));
|
||||||
|
getSpells({ levels: [9] }).then((s) => setLevel9(s.data));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box style={{ width: '60%', margin: 'auto' }}>
|
||||||
|
<h1>Spells</h1>
|
||||||
|
<TextInput
|
||||||
|
label='Search by name'
|
||||||
|
placeholder='Acid Splash...'
|
||||||
|
onChange={(e) => setSearchName(e.target.value)}
|
||||||
|
style={{ width: '25%' }}
|
||||||
|
/>
|
||||||
|
<SpellSection
|
||||||
|
title='Cantrips'
|
||||||
|
spells={cantrips.filter((s) => s.name.toLowerCase().includes(searchName.toLowerCase()))}
|
||||||
|
onClick={(spell) => {
|
||||||
|
setActiveSpell(spell);
|
||||||
|
setIsOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
|
{activeSpell && <SpellModal spell={activeSpell} isOpen={isOpen} onClose={() => setIsOpen(false)} />}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SpellSection({ title, spells, onClick }: { title: string; spells: Spell[]; onClick: (spell: Spell) => void }) {
|
||||||
|
const isBrowser = () => typeof window !== 'undefined'; //The approach recommended by Next.js
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
|
if (!isBrowser()) return;
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<h2>{title}</h2>
|
||||||
|
<ul>
|
||||||
|
{spells.map((spell) => (
|
||||||
|
<li
|
||||||
|
key={spell.id}
|
||||||
|
className='link spell-item'
|
||||||
|
style={{ width: 'fit-content' }}
|
||||||
|
onClick={() => onClick(spell)}
|
||||||
|
>
|
||||||
|
{spell.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', color: 'gray' }} onClick={scrollToTop}>
|
||||||
|
<span style={{ paddingRight: '0.2em' }}>Back to top</span>
|
||||||
|
<AiOutlineVerticalAlignTop />
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
ui/src/app/spells/spells.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.spell-item {
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spell-item:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
153
ui/src/components/SpellModal.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Spell } from '@/api/spells.types';
|
||||||
|
import { levelText, rollDice } from '@/js/spells';
|
||||||
|
import { capitalize } from '@/js/utils';
|
||||||
|
import { Grid, Modal } from '@mantine/core';
|
||||||
|
import { notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
|
interface SpellModalProps {
|
||||||
|
spell: Spell;
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps) {
|
||||||
|
return (
|
||||||
|
<Modal opened={isOpen} onClose={onClose} withCloseButton={false} size={'50%'} className='modal'>
|
||||||
|
<h1 style={{ padding: '0', margin: '0' }}>{spell.name}</h1>
|
||||||
|
<Grid gutter={1}>
|
||||||
|
<Grid.Col span={4} style={{ paddingBottom: '1rem' }}>
|
||||||
|
<span style={{ fontWeight: 'bold' }}>
|
||||||
|
{capitalize(spell.school)} {levelText(spell)}
|
||||||
|
</span>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={8} style={{ paddingBottom: '1rem' }}>
|
||||||
|
<div style={{ float: 'right' }}>
|
||||||
|
<span style={{ float: 'right' }}>
|
||||||
|
{spell.components.verbal && spell.components.somatic ? 'V, ' : 'V '}
|
||||||
|
{spell.components.somatic && spell.components.material ? 'S, ' : 'S '}
|
||||||
|
{spell.components.material && spell.components.materials_needed ? 'M*' : 'M'}
|
||||||
|
</span>
|
||||||
|
{spell.components.materials_needed && (
|
||||||
|
<span style={{ fontSize: '0.8em', color: 'gray' }}>
|
||||||
|
<br />*{capitalize(spell.components.materials_needed)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Sources:</span>
|
||||||
|
{spell.sources.map((s) => (
|
||||||
|
<span style={{ paddingRight: '0.6em' }}>
|
||||||
|
{s.source}
|
||||||
|
{s.page ? `.${s.page}` : ''}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<span style={{ fontWeight: 'bold', marginRight: '1em' }}>Classes:</span>
|
||||||
|
<span style={{ overflowWrap: 'break-word' }}>
|
||||||
|
{spell.classes.map((c) => (
|
||||||
|
<span style={{ paddingRight: '0.6em', display: 'inline-block' }} className='link'>
|
||||||
|
{capitalize(c)}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Casting Time:</span>
|
||||||
|
<span style={{ paddingRight: '0.6em' }}>
|
||||||
|
{spell.casting_time.value} {capitalize(spell.casting_time.unit)}
|
||||||
|
</span>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Range:</span>
|
||||||
|
<span style={{ paddingRight: '0.6em' }}>
|
||||||
|
{spell.range.type != 'point' && capitalize(spell.range.type)} {spell.range.value}{' '}
|
||||||
|
{capitalize(spell.range.unit)}
|
||||||
|
</span>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Duration:</span>
|
||||||
|
<span style={{ paddingRight: '0.6em' }}>
|
||||||
|
{spell.durations.map((d) => (
|
||||||
|
<span>
|
||||||
|
{capitalize(d.type)} {d.value} {capitalize(d.unit)}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={12}>
|
||||||
|
<SpellDescription spell={spell} />
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SpellDescription({ spell }: { spell: Spell }) {
|
||||||
|
function parseText(text: string) {
|
||||||
|
const regex = /{@(.*?) (.*?)}/g;
|
||||||
|
const matches = text.matchAll(regex);
|
||||||
|
const result = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
for (const match of matches) {
|
||||||
|
const [full, type, name] = match;
|
||||||
|
result.push(text.slice(lastIndex, match.index));
|
||||||
|
if (match.index !== undefined) {
|
||||||
|
result.push(
|
||||||
|
<span onClick={() => handleLink(type, name)} className='link'>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
lastIndex = match.index + full.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(text.slice(lastIndex));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLink(type: string, name: string) {
|
||||||
|
if (type == 'spell') {
|
||||||
|
console.log(`Link to spell: ${name}`);
|
||||||
|
} else if (type == 'dice' || type == 'damage') {
|
||||||
|
const rolls = rollDice(name);
|
||||||
|
notifications.show({
|
||||||
|
title: `Rolling ${name}`,
|
||||||
|
message: `${rolls.join(' + ')} = ${rolls.reduce((a, b) => a + b, 0)}`,
|
||||||
|
color: 'blue',
|
||||||
|
autoClose: 5000,
|
||||||
|
withCloseButton: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(`Unknown link type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{spell.description && (
|
||||||
|
<>
|
||||||
|
{spell.description.entries.map((e) =>
|
||||||
|
typeof e === 'string' ? (
|
||||||
|
<p>{parseText(e)}</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{e.type == 'list' ? (
|
||||||
|
<ul>
|
||||||
|
{e.items.map((text) => (
|
||||||
|
<li>{parseText(text)}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
ui/src/components/Topbar/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import './topbar.css';
|
||||||
|
|
||||||
|
const headerItems = [
|
||||||
|
{
|
||||||
|
name: 'Races',
|
||||||
|
link: '/races'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Classes',
|
||||||
|
link: '/classes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Feats',
|
||||||
|
link: '/feats'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Options & Features',
|
||||||
|
link: '/options'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Backgrounds',
|
||||||
|
link: '/backgrounds'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Items',
|
||||||
|
link: '/items'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Spells',
|
||||||
|
link: '/spells'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Topbar() {
|
||||||
|
const pathName = usePathname();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className='navbar'>
|
||||||
|
<div className='left'>
|
||||||
|
<Link href={'/'} className='title'>
|
||||||
|
Siren
|
||||||
|
</Link>
|
||||||
|
<div className='header-items'>
|
||||||
|
{headerItems.map((item) => (
|
||||||
|
<Link className={`header-item ${pathName == item.link && 'active'}`} href={item.link} key={item.name}>
|
||||||
|
{item.name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
ui/src/components/Topbar/topbar.css
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.navbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: black;
|
||||||
|
border-bottom: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .left {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .title {
|
||||||
|
padding-left: 2em;
|
||||||
|
padding-right: 2em;
|
||||||
|
margin: auto;
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .left .search {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .avatar {
|
||||||
|
padding-right: 2em;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-items {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-items .header-item {
|
||||||
|
padding-left: 2em;
|
||||||
|
padding-right: 2em;
|
||||||
|
margin: auto;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-items .header-item:hover {
|
||||||
|
border-bottom: 2px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-items .active {
|
||||||
|
border-bottom: 2px solid #5f5f5f;
|
||||||
|
}
|
||||||
23
ui/src/js/spells.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Spell } from '@/api/spells.types';
|
||||||
|
|
||||||
|
export function levelText(spell: Spell) {
|
||||||
|
if (spell.level === 0) {
|
||||||
|
return 'Cantrip';
|
||||||
|
} else {
|
||||||
|
return `Level ${spell.level}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rollDice(dice: string): number[] {
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let [count, sides] = dice.split('d');
|
||||||
|
const rolls = [];
|
||||||
|
if (isNaN(parseInt(count))) {
|
||||||
|
count = '1';
|
||||||
|
}
|
||||||
|
for (let i = 0; i < parseInt(count); i++) {
|
||||||
|
rolls.push(Math.floor(Math.random() * parseInt(sides)) + 1);
|
||||||
|
}
|
||||||
|
console.log(rolls);
|
||||||
|
return rolls;
|
||||||
|
}
|
||||||
5
ui/src/js/theme.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createTheme } from '@mantine/core';
|
||||||
|
|
||||||
|
export const theme = createTheme({});
|
||||||
6
ui/src/js/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export function capitalize(str: string | undefined): string {
|
||||||
|
if (!str || str.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
}
|
||||||
38
ui/styles/globals.css
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||||
|
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper > nav {
|
||||||
|
flex: 0 0 56px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
list-style-type: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #297bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
color: #1c59bb;
|
||||||
|
}
|
||||||
45
ui/tsconfig.json
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"@api/*": ["src/api"],
|
||||||
|
"@app/*": ["./src/app/*"],
|
||||||
|
"@components/*": ["src/components/*"],
|
||||||
|
"@lib/*": ["src/components/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||