Merge branch 'feature/gerust_test'

This commit is contained in:
Max Hohlfeld 2024-12-19 21:28:25 +01:00
commit 954bba25d5
7139 changed files with 838821 additions and 1115 deletions

4
.cargo/config.toml Normal file
View File

@ -0,0 +1,4 @@
[alias]
db = ["run", "--package", "cli", "--bin", "db", "--"]
w = ["watch", "--exec", "run"]
t = ["nextest", "run"]

5
.env
View File

@ -2,11 +2,12 @@
# DATABASE_URL=postgres://postgres@localhost/my_database # DATABASE_URL=postgres://postgres@localhost/my_database
# SQLite # SQLite
DATABASE_URL=postgresql://max@localhost/brass DATABASE_URL=postgresql://max@localhost/brass
SQLX_OFFLINE=true
# 64 byte long openssl rand -base64 64 # 64 byte long openssl rand -base64 64
SECRET_KEY="changeInProdOrHandAb11111111111111111111111111111111111111111111" SECRET_KEY="changeInProdOrHandAb11111111111111111111111111111111111111111111"
HOSTNAME="localhost" HOSTNAME="localhost"
ADDRESS="127.0.0.1" SERVER_ADDRESS="127.0.0.1"
PORT="8080" SERVER_PORT="8080"
SMTP_SERVER="localhost" SMTP_SERVER="localhost"
SMTP_PORT="1025" SMTP_PORT="1025"

16
.env.test Normal file
View File

@ -0,0 +1,16 @@
# Postgres
# DATABASE_URL=postgres://postgres@localhost/my_database
# SQLite
DATABASE_URL=postgresql://max@localhost/brass_test
SQLX_OFFLINE=true
# 64 byte long openssl rand -base64 64
SECRET_KEY="changeInProdOrHandAb11111111111111111111111111111111111111111111"
HOSTNAME="localhost"
SERVER_ADDRESS="127.0.0.1"
SERVER_PORT="8080"
SMTP_SERVER="localhost"
SMTP_PORT="1025"
# SMTP_LOGIN=""
# SMTP_PASSWORD=""
SMTP_TLSTYPE="none"

View File

@ -0,0 +1,52 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM availabillity WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "date",
"type_info": "Date"
},
{
"ordinal": 3,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "endtime",
"type_info": "Time"
},
{
"ordinal": 5,
"name": "comment",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
true,
true,
true
]
},
"hash": "0342272c9798389c37e229018e30bed399723ca0dba70438ba519d3ee7dad01b"
}

View File

@ -0,0 +1,28 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM area WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false
]
},
"hash": "0a5318ab6466385e805f83e13cc0797146a27d523ad5ffab1828806fb2935af9"
}

View File

@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM registration WHERE token = $1 AND expires > NOW();",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "token",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "expires",
"type_info": "Timestamp"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "0b0974fa0c51fe0011990fc3d9b0c4863a313f6cb7f86fe4ea02f6e8c594b4a4"
}

View File

@ -0,0 +1,50 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO user_ (name, email, password, salt, role, function, areaId)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n RETURNING id;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Text",
"Text",
"Text",
{
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
},
{
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
},
"Int4"
]
},
"nullable": [
false
]
},
"hash": "0eafc423ff404eadb5300ad47e5b81d5f3be1fb0c1723600b6f9bbe73b1e8155"
}

View File

@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM vehicleAssignement WHERE vehicleAssignement.eventId = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "vehicleid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 3,
"name": "endtime",
"type_info": "Time"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "126a17ad3fe8937dfbf70e9a8c2bd7aee56eb35fdff0d0e47037b9af3e08b34f"
}

View File

@ -0,0 +1,42 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO passwordReset (token, userId, expires) VALUES ($1, $2, $3) RETURNING *;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "token",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "expires",
"type_info": "Timestamp"
}
],
"parameters": {
"Left": [
"Text",
"Int4",
"Timestamp"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "149c662f824b02f0b530c84ea291a3bef0f7d6698ad6b916851060270bb9e67e"
}

View File

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE area SET name = $1 WHERE id = $2;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int4"
]
},
"nullable": []
},
"hash": "17f79edd7138f29f4279aeb2e72be1d1cea46d8aca559788420f532be0426723"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM location WHERE id = $1;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": []
},
"hash": "1c996712f62a1005990733cd9eee7a94bdcf2ef01b559304aea1d642fab7ae22"
}

View File

@ -0,0 +1,102 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id,\n name,\n email,\n password,\n salt,\n role AS \"role: Role\",\n function AS \"function: Function\",\n areaId,\n locked,\n lastLogin,\n receiveNotifications\n FROM user_;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "email",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "password",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "salt",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "role: Role",
"type_info": {
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
}
},
{
"ordinal": 6,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 7,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 8,
"name": "locked",
"type_info": "Bool"
},
{
"ordinal": 9,
"name": "lastlogin",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "receivenotifications",
"type_info": "Bool"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
true,
true,
false,
false,
false,
false,
true,
false
]
},
"hash": "1d5bf64843b684258fcce0e606ea01a2037890ac736b7344e0008eae2b2a7ef6"
}

View File

@ -0,0 +1,29 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO assignment (eventId, availabillityId, function, startTime, endTime)\n VALUES ($1, $2, $3, $4, $5);\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Int4",
{
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
},
"Time",
"Time"
]
},
"nullable": []
},
"hash": "246f9e96dfc631c1ed57bc520f0e94a3788d8bf123ecc9992f380b34aedf972d"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM user_ WHERE id = $1;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": []
},
"hash": "24fecf0d262d800b26cf90db0e12fea535a7b630000db10ea89419bff998f58d"
}

View File

@ -0,0 +1,32 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM location",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "areaid",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false
]
},
"hash": "2d9f2d0728983dfac09f6649da74aa5659072539a8f222b8ae202786ce958c37"
}

View File

@ -0,0 +1,114 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n user_.id AS userId,\n user_.name,\n user_.email,\n user_.password,\n user_.salt,\n user_.role AS \"role: Role\",\n user_.function AS \"function: Function\",\n user_.areaId,\n user_.locked,\n user_.lastLogin,\n user_.receiveNotifications,\n area.id,\n area.name AS areaName\n FROM user_\n JOIN area ON user_.areaId = area.id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "email",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "password",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "salt",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "role: Role",
"type_info": {
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
}
},
{
"ordinal": 6,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 7,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 8,
"name": "locked",
"type_info": "Bool"
},
{
"ordinal": 9,
"name": "lastlogin",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "receivenotifications",
"type_info": "Bool"
},
{
"ordinal": 11,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 12,
"name": "areaname",
"type_info": "Text"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
true,
true,
false,
false,
false,
false,
true,
false,
false,
false
]
},
"hash": "2defbc675e894d30483e057d78344490b06c3cd5b3b3ab474ef755b67d4b6ea0"
}

View File

@ -0,0 +1,104 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id,\n name,\n email,\n password,\n salt,\n role AS \"role: Role\",\n function AS \"function: Function\",\n areaId,\n locked,\n lastLogin,\n receiveNotifications\n FROM user_\n WHERE id = $1;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "email",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "password",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "salt",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "role: Role",
"type_info": {
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
}
},
{
"ordinal": 6,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 7,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 8,
"name": "locked",
"type_info": "Bool"
},
{
"ordinal": 9,
"name": "lastlogin",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "receivenotifications",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
true,
true,
false,
false,
false,
false,
true,
false
]
},
"hash": "3338639844455180fdc70262cb982042ee2ff5cc8ab0fd6101bf5d4182f58530"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE user_ SET lastLogin = NOW() WHERE id = $1;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": []
},
"hash": "36afabfbbe056a63386f94edf2d49beb2d63130f8ea1bf4a840d7729bcb6f9cc"
}

View File

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE vehicle SET radiocallname = $1, station = $2 WHERE id = $3;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Int4"
]
},
"nullable": []
},
"hash": "39415450c15de43079d87443b71cf9bacb8e852fa434448d3cc4c52bad0bcf01"
}

View File

@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM passwordReset WHERE token = $1 AND expires > NOW();",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "token",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "expires",
"type_info": "Timestamp"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "39e46937dc06fb71beffbe990027946ce741335fefad69f5d92a2adb434b118e"
}

View File

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM vehicleassignement WHERE eventId = $1 AND vehicleId = $2;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Int4"
]
},
"nullable": []
},
"hash": "48e3cab60736bf95958b063013090c64160aff1bad2f0db851db2e421711a156"
}

View File

@ -0,0 +1,18 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO availabillity (userId, date, startTime, endTime, comment)\n VALUES ($1, $2, $3, $4, $5);\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Date",
"Time",
"Time",
"Text"
]
},
"nullable": []
},
"hash": "4a3837ecb09fdc97cf5706bcd84be918cd3fa3861d2a19461b5612bceecb1278"
}

View File

@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO vehicleassignement (eventId, vehicleId, startTime, endTime) VALUES ($1, $2, $3, $4);",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Int4",
"Time",
"Time"
]
},
"nullable": []
},
"hash": "57969291aa7b29162b3a39c00f37f260e027d55063f031c8fd0a2efbe7e3b414"
}

View File

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO event (date, startTime, endTime, name, locationId, voluntaryWachhabender, amountOfPosten, clothing, note)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Date",
"Time",
"Time",
"Text",
"Int4",
"Bool",
"Int2",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "57d4a852f0845c761990cc1483bfc3e6f2e8b130427b7cae9e2376b45625195f"
}

View File

@ -0,0 +1,58 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n assignment.eventId,\n assignment.availabillityId,\n assignment.function AS \"function: Function\",\n assignment.startTime,\n assignment.endTime\n FROM assignment\n WHERE\n assignment.eventId = $1 AND\n assignment.availabillityId = $2;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "availabillityid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 3,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "endtime",
"type_info": "Time"
}
],
"parameters": {
"Left": [
"Int4",
"Int4"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "593d533114e3efc94c6dec2a01803f569365bbb59b95ab5ca798bcc6d7d50c06"
}

View File

@ -0,0 +1,32 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM vehicle;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "radiocallname",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "station",
"type_info": "Text"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false
]
},
"hash": "5b87f4da0924338da1a30d7b74711d8073f6d62cf30a42381484846f0917bc33"
}

View File

@ -0,0 +1,100 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n event.id AS eventId,\n event.date,\n event.startTime,\n event.endTime,\n event.name,\n event.locationId,\n event.voluntaryWachhabender,\n event.amountOfPosten,\n event.clothing,\n event.canceled,\n event.note,\n location.id,\n location.name AS locationName,\n location.areaId AS locationAreaId\n FROM event\n JOIN location ON event.locationId = location.id\n WHERE event.id = $1;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "date",
"type_info": "Date"
},
{
"ordinal": 2,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 3,
"name": "endtime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "locationid",
"type_info": "Int4"
},
{
"ordinal": 6,
"name": "voluntarywachhabender",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "amountofposten",
"type_info": "Int2"
},
{
"ordinal": 8,
"name": "clothing",
"type_info": "Text"
},
{
"ordinal": 9,
"name": "canceled",
"type_info": "Bool"
},
{
"ordinal": 10,
"name": "note",
"type_info": "Text"
},
{
"ordinal": 11,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 12,
"name": "locationname",
"type_info": "Text"
},
{
"ordinal": 13,
"name": "locationareaid",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
false
]
},
"hash": "5c5c88811fd870d2f68b76fe71afd2ee1e72623c94be406e369af8a4a04591e0"
}

View File

@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE availabillity SET startTime = $1, endTime = $2, comment = $3 WHERE id = $4",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Time",
"Time",
"Text",
"Int4"
]
},
"nullable": []
},
"hash": "609e14be64f588bf2981061955c83ee6b32085e10ec77d39cb64fc6e3d9de989"
}

View File

@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM vehicle WHERE id = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "radiocallname",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "station",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "68d2b1ed0dc56056ec85ca38ba033ffcb6a480ca13c2beea0a543067840c64a7"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM passwordReset WHERE token = $1;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "73eba57512af51a7a7d5ea9b6b375dba701bf829b8cf8e37388c1de1c302f486"
}

View File

@ -0,0 +1,104 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id,\n name,\n email,\n password,\n salt,\n role AS \"role: Role\",\n function AS \"function: Function\",\n areaId,\n locked,\n lastLogin,\n receiveNotifications\n FROM user_\n WHERE email = $1 AND locked = FALSE AND password IS NOT NULL AND salt IS NOT NULL;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "email",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "password",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "salt",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "role: Role",
"type_info": {
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
}
},
{
"ordinal": 6,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 7,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 8,
"name": "locked",
"type_info": "Bool"
},
{
"ordinal": 9,
"name": "lastlogin",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "receivenotifications",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
true,
true,
false,
false,
false,
false,
true,
false
]
},
"hash": "76bf18e05733214925ddd0cbe090a69f839c140377f043ca8181cd9a0af5e70e"
}

View File

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM assignment WHERE assignment.eventId = $1 AND assignment.availabillityId = $2;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Int4"
]
},
"nullable": []
},
"hash": "7d2e4fcde5bee6c9dcd85e35fa5f0dd4ae6b638b85f0e3a54afc85a46de6ae3a"
}

View File

@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM area ORDER by id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false
]
},
"hash": "7f6c89117e8d4249e032235d03d264c3d5d47bd119c563237486cf47e402ae2e"
}

View File

@ -0,0 +1,104 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id,\n name,\n email,\n password,\n salt,\n role AS \"role: Role\",\n function AS \"function: Function\",\n areaId,\n locked,\n lastLogin,\n receiveNotifications\n FROM user_\n WHERE areaId = $1;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "email",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "password",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "salt",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "role: Role",
"type_info": {
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
}
},
{
"ordinal": 6,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 7,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 8,
"name": "locked",
"type_info": "Bool"
},
{
"ordinal": 9,
"name": "lastlogin",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "receivenotifications",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
true,
true,
false,
false,
false,
false,
true,
false
]
},
"hash": "82a9b85a96c255c1bc0728ac6d3b4debcbbe0dbdf4b75e585d8e0f44fbd7a982"
}

View File

@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM vehicleAssignement WHERE vehicleAssignement.vehicleId = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "vehicleid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 3,
"name": "endtime",
"type_info": "Time"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "8897efc635987bca2c2d1a85e001c909404ad2356fc63872fdae5a0cf76e7099"
}

View File

@ -0,0 +1,135 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n availabillity.id,\n availabillity.userId,\n availabillity.date,\n availabillity.startTime,\n availabillity.endTime,\n availabillity.comment,\n user_.name,\n user_.email,\n user_.password,\n user_.salt,\n user_.role AS \"role: Role\",\n user_.function AS \"function: Function\",\n user_.areaId,\n user_.locked,\n user_.lastLogin,\n user_.receiveNotifications\n FROM availabillity\n JOIN user_ ON availabillity.userId = user_.id\n WHERE availabillity.date = $1\n AND user_.areaId = $2;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "date",
"type_info": "Date"
},
{
"ordinal": 3,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "endtime",
"type_info": "Time"
},
{
"ordinal": 5,
"name": "comment",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 7,
"name": "email",
"type_info": "Text"
},
{
"ordinal": 8,
"name": "password",
"type_info": "Text"
},
{
"ordinal": 9,
"name": "salt",
"type_info": "Text"
},
{
"ordinal": 10,
"name": "role: Role",
"type_info": {
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
}
},
{
"ordinal": 11,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 12,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 13,
"name": "locked",
"type_info": "Bool"
},
{
"ordinal": 14,
"name": "lastlogin",
"type_info": "Timestamptz"
},
{
"ordinal": 15,
"name": "receivenotifications",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Date",
"Int4"
]
},
"nullable": [
false,
false,
false,
true,
true,
true,
false,
false,
true,
true,
false,
false,
false,
false,
true,
false
]
},
"hash": "a1288bdb944dc72d5591d8686f533d124d9edacaf62539f7908c797efe44a68d"
}

View File

@ -0,0 +1,48 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO user_ (name, email, role, function, areaId)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING id;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Text",
{
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
},
{
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
},
"Int4"
]
},
"nullable": [
false
]
},
"hash": "ad4419211e4c98292eaa47ab04e0ea0201f1789ac906acc8cf79aacfbecd9f05"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM registration WHERE token = $1;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "b55283e681bed3f5666cefca8acf2504e78ee2c8e094d96b89b68e8e7dddce48"
}

View File

@ -0,0 +1,57 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n assignment.eventId,\n assignment.availabillityId,\n assignment.function AS \"function: Function\",\n assignment.startTime,\n assignment.endTime\n FROM assignment\n WHERE assignment.AvailabillityId = $1;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "availabillityid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 3,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "endtime",
"type_info": "Time"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "b5e41a3f0b8fdda8bd0f8654a526596dbb76ff521e67b459ceace6c7793e99b9"
}

View File

@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO area (name) VALUES ($1) RETURNING id;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "b670053eb906a244d3ce12a7eb36982d6b21ed1e637fbf0eee841031e217c6c5"
}

View File

@ -0,0 +1,42 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO registration (token, userId, expires) VALUES ($1, $2, $3) RETURNING *;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "token",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "expires",
"type_info": "Timestamp"
}
],
"parameters": {
"Left": [
"Text",
"Int4",
"Timestamp"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "b68b7d99cbf993a1cfa4bef1d0eb16caeba5715fced557c2ed017ed9130830e5"
}

View File

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO vehicle (radioCallName, station) VALUES ($1, $2);",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "c28c0fd372fa23b0053ec38ce59b5bf791d8a75ba68937609698bd52c97c8d0d"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM area WHERE id = $1;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": []
},
"hash": "cca2b280d655073557f035932eb7919b7e8263eee1e8026d9143a15f12fa81a5"
}

View File

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO location (name, areaId) VALUES ($1, $2);",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int4"
]
},
"nullable": []
},
"hash": "d1d97d9f6cc8d9777dc4ce38a4b50041db855227c8d465d56e985bad86f0c8d9"
}

View File

@ -0,0 +1,142 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n availabillity.id,\n availabillity.userId,\n availabillity.date,\n availabillity.startTime,\n availabillity.endTime,\n availabillity.comment,\n user_.name,\n user_.email,\n user_.password,\n user_.salt,\n user_.role AS \"role: Role\",\n user_.function AS \"function: Function\",\n user_.areaId,\n user_.locked,\n user_.lastLogin,\n user_.receiveNotifications,\n area.name AS areaName\n FROM availabillity\n JOIN user_ ON availabillity.userId = user_.id\n JOIN area ON user_.areaId = area.id\n WHERE user_.areaId = $1 AND\n availabillity.date >= $2 AND\n availabillity.date <= $3;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "date",
"type_info": "Date"
},
{
"ordinal": 3,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "endtime",
"type_info": "Time"
},
{
"ordinal": 5,
"name": "comment",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 7,
"name": "email",
"type_info": "Text"
},
{
"ordinal": 8,
"name": "password",
"type_info": "Text"
},
{
"ordinal": 9,
"name": "salt",
"type_info": "Text"
},
{
"ordinal": 10,
"name": "role: Role",
"type_info": {
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
}
},
{
"ordinal": 11,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 12,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 13,
"name": "locked",
"type_info": "Bool"
},
{
"ordinal": 14,
"name": "lastlogin",
"type_info": "Timestamptz"
},
{
"ordinal": 15,
"name": "receivenotifications",
"type_info": "Bool"
},
{
"ordinal": 16,
"name": "areaname",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Int4",
"Date",
"Date"
]
},
"nullable": [
false,
false,
false,
true,
true,
true,
false,
false,
true,
true,
false,
false,
false,
false,
true,
false,
false
]
},
"hash": "d3705fa03f98a5b83c65e29fe98d8c6015dfbff58460b420b9634bf2a0e38e4e"
}

View File

@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM location WHERE id = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "areaid",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "d556cb1971f386a7997c2e3dbce191cde2d488c55c05edbfe3746208a782d5f6"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM vehicle WHERE id = $1;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": []
},
"hash": "d79f4733454dfe8df4209fa94b3a3512716812561516c0fd602d8b9d9af4eca6"
}

View File

@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT count(*) FROM assignment WHERE assignment.eventId = $1 AND assignment.function = $2;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int4",
{
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
]
},
"nullable": [
null
]
},
"hash": "de0201654f21edf4852a456006fb7c1c87ff4eb34a3ab141058f31e103746e9c"
}

View File

@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM location WHERE areaId = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "areaid",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "ea9f427b5d5a3e3c5f720d6bab2417cb3b42de0a5bf1d8b48b11a6e6275cc8e4"
}

View File

@ -0,0 +1,57 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n assignment.eventId,\n assignment.availabillityId,\n assignment.function AS \"function: Function\",\n assignment.startTime,\n assignment.endTime\n FROM assignment\n WHERE assignment.eventId = $1;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "availabillityid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 3,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "endtime",
"type_info": "Time"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "ebf8b913aaa5718482a4aba18226cc7da21c8c9f49a7f70433b21b56115514a0"
}

View File

@ -0,0 +1,101 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n event.id AS eventId,\n event.date,\n event.startTime,\n event.endTime,\n event.name,\n event.locationId,\n event.voluntaryWachhabender,\n event.amountOfPosten,\n event.clothing,\n event.canceled,\n event.note,\n location.id,\n location.name AS locationName,\n location.areaId AS locationAreaId\n FROM event\n JOIN location ON event.locationId = location.id\n WHERE date = $1\n AND location.areaId = $2;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "eventid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "date",
"type_info": "Date"
},
{
"ordinal": 2,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 3,
"name": "endtime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 5,
"name": "locationid",
"type_info": "Int4"
},
{
"ordinal": 6,
"name": "voluntarywachhabender",
"type_info": "Bool"
},
{
"ordinal": 7,
"name": "amountofposten",
"type_info": "Int2"
},
{
"ordinal": 8,
"name": "clothing",
"type_info": "Text"
},
{
"ordinal": 9,
"name": "canceled",
"type_info": "Bool"
},
{
"ordinal": 10,
"name": "note",
"type_info": "Text"
},
{
"ordinal": 11,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 12,
"name": "locationname",
"type_info": "Text"
},
{
"ordinal": 13,
"name": "locationareaid",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Date",
"Int4"
]
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
true,
false,
false,
false
]
},
"hash": "efb827de66b93f6087ebfb49360abded5f7d5bef3b22db0fc3de466924b23782"
}

View File

@ -0,0 +1,134 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n availabillity.id,\n availabillity.userId,\n availabillity.date,\n availabillity.startTime,\n availabillity.endTime,\n availabillity.comment,\n user_.name,\n user_.email,\n user_.password,\n user_.salt,\n user_.role AS \"role: Role\",\n user_.function AS \"function: Function\",\n user_.areaId,\n user_.locked,\n user_.lastLogin,\n user_.receiveNotifications\n FROM availabillity\n LEFT JOIN assignment ON availabillity.Id = assignment.availabillityId\n JOIN user_ ON availabillity.userId = user_.id\n WHERE availabillity.id = $1;\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "userid",
"type_info": "Int4"
},
{
"ordinal": 2,
"name": "date",
"type_info": "Date"
},
{
"ordinal": 3,
"name": "starttime",
"type_info": "Time"
},
{
"ordinal": 4,
"name": "endtime",
"type_info": "Time"
},
{
"ordinal": 5,
"name": "comment",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 7,
"name": "email",
"type_info": "Text"
},
{
"ordinal": 8,
"name": "password",
"type_info": "Text"
},
{
"ordinal": 9,
"name": "salt",
"type_info": "Text"
},
{
"ordinal": 10,
"name": "role: Role",
"type_info": {
"Custom": {
"name": "role",
"kind": {
"Enum": [
"staff",
"areamanager",
"admin"
]
}
}
}
},
{
"ordinal": 11,
"name": "function: Function",
"type_info": {
"Custom": {
"name": "function",
"kind": {
"Enum": [
"posten",
"fuehrungsassistent",
"wachhabender"
]
}
}
}
},
{
"ordinal": 12,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 13,
"name": "locked",
"type_info": "Bool"
},
{
"ordinal": 14,
"name": "lastlogin",
"type_info": "Timestamptz"
},
{
"ordinal": 15,
"name": "receivenotifications",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false,
false,
true,
true,
true,
false,
false,
true,
true,
false,
false,
false,
false,
true,
false
]
},
"hash": "f48192661c91b4f48bb46f8ea5d60911ddeafc408d3a897dd4ab1b45fe06dd4d"
}

View File

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE location SET name = $1, areaid = $2 WHERE id = $3;",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int4",
"Int4"
]
},
"nullable": []
},
"hash": "f613f4cbd2d89bc061e41f69fe913e69d658242344be7c46ba4f4df90f8b74b3"
}

View File

@ -0,0 +1,44 @@
{
"db_name": "PostgreSQL",
"query": "SELECT location.id AS locationId, location.name, location.areaId, area.id, area.name AS areaName FROM location JOIN area ON location.areaId = area.id;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "locationid",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "areaid",
"type_info": "Int4"
},
{
"ordinal": 3,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 4,
"name": "areaname",
"type_info": "Text"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "f94d7fe59a2d4b7d246711a796571367172bce9446b9fb1e7ba057917a98d958"
}

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM availabillity WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": []
},
"hash": "fc09093ce791d2d690b2e3f5ff2125a10757b06ff7d60c8843cb7814ea452f13"
}

1424
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,10 @@
[package] [workspace]
name = "brass" members = [ "cli", "config", "macros", "web", ]
version = "0.1.0" resolver = "2"
edition = "2021" default-members = ["web"]
license = "AGPL-3.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [profile.dev.package.rinja_derive]
opt-level = 3
[dependencies]
sqlx = { version = "0.6", features = ["runtime-async-std-rustls", "postgres", "chrono"] } [profile.dev.package.sqlx-macros]
actix-web = { version = "4" }
askama = { version = "0.12.0", features = ["with-actix-web"] }
serde = { version = "1.0.164", features = ["derive"] }
argon2 = { version = "0.5.0", features = [ "std"]}
anyhow = "1.0.71"
dotenv = "0.15.0"
actix-session = { version = "0.7.2", features = ["cookie-session"] }
actix-identity = "0.5.2"
chrono = { version = "0.4.33", features = ["serde", "now"] }
actix-files = "0.6.5"
askama_actix = "0.14.0"
futures-util = "0.3.30"
serde_json = "1.0.114"
pico-args = "0.5.0"
rand = { version = "0.8.5", features = ["getrandom"] }
async-trait = "0.1.79"
lettre = "0.11.7"
quick-xml = { version = "0.31.0", features = ["serde", "serialize"] }
actix-web-static-files = "4.0"
static-files = "0.2.1"
zxcvbn = "3.1.0"
thiserror = "1.0.63"
[build-dependencies]
built = "0.7.4"
static-files = "0.2.1"
[profile.dev.package.askama_derive]
opt-level = 3 opt-level = 3

View File

@ -29,7 +29,7 @@ rc_reload=NO
``` ```
``` ```ini
# Postgres # Postgres
# DATABASE_URL=postgres://postgres@localhost/my_database # DATABASE_URL=postgres://postgres@localhost/my_database
# SQLite # SQLite
@ -46,3 +46,11 @@ SMTP_PORT="25"
# SMTP_PASSWORD="" # SMTP_PASSWORD=""
SMTP_TLSTYPE="none" SMTP_TLSTYPE="none"
``` ```
## drop test databases
```bash
for dbname in $(psql -c "copy (select datname from pg_database where datname like 'brass_test_%') to stdout") ; do
echo "$dbname"
#dropdb -i "$dbname"
done
```

17
cli/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "cli"
version = "0.1.0"
edition = "2021"
[lib]
[[bin]]
name = "db"
path = "src/db.rs"
[dependencies]
clap = { version = "4.5.23", features = ["derive"] }
brass-config = { path = "../config" }
async-std = { version = "1.13.0", features = ["attributes"] }
sqlx = { version = "0.8.2", features = ["runtime-async-std", "postgres"] }
anyhow = "1.0.94"

150
cli/src/db.rs Normal file
View File

@ -0,0 +1,150 @@
use anyhow::Context;
use sqlx::migrate::Migrate;
use sqlx::{migrate::Migrator, Executor};
use std::{
collections::HashMap,
path::{Path, PathBuf},
str::FromStr,
};
use brass_config::{load_config, parse_env, Environment};
use clap::{Parser, Subcommand};
use sqlx::{postgres::PgConnectOptions, Connection, PgConnection};
#[derive(Parser)]
#[command(about = "A CLI tool for managing the projects database.", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Command,
#[arg(short, long, global = true, help = "Choose the environment (development, test, production).", value_parser = parse_env, default_value = "development")]
environment: Environment,
}
#[derive(Subcommand)]
enum Command {
#[command(about = "Create the database and run all migrations")]
Setup,
#[command(about = "Drop and recreate the database and run all migrations")]
Reset,
#[command(about = "Run all pending migrations on database")]
Migrate,
}
#[async_std::main]
async fn main() {
let cli = Cli::parse();
let config = load_config(&cli.environment).expect("Could not load config!");
let db_config =
PgConnectOptions::from_str(&config.database_url).expect("Invalid DATABASE_URL!");
match cli.command {
Command::Setup => {
create_db(&db_config)
.await
.expect("Failed creating database.");
migrate_db(&db_config)
.await
.expect("Failed migrating database.");
}
Command::Reset => {
drop_db(&db_config)
.await
.expect("Failed dropping database.");
create_db(&db_config)
.await
.expect("Failed creating database.");
migrate_db(&db_config)
.await
.expect("Failed migrating database.");
},
Command::Migrate => {
migrate_db(&db_config)
.await
.expect("Failed migrating database.");
}
}
}
async fn drop_db(db_config: &PgConnectOptions) -> anyhow::Result<()> {
let db_name = db_config
.get_database()
.context("Failed to get database name!")?;
let root_db_config = db_config.clone().database("postgres");
let mut connection: PgConnection = Connection::connect_with(&root_db_config)
.await
.context("Connection to database failed!")?;
let query_drop = format!("DROP DATABASE {}", db_name);
connection
.execute(query_drop.as_str())
.await
.context("Failed to drop database!")?;
Ok(())
}
async fn create_db(db_config: &PgConnectOptions) -> anyhow::Result<()> {
let db_name = db_config
.get_database()
.context("Failed to get database name!")?;
let root_db_config = db_config.clone().database("postgres");
let mut connection: PgConnection = Connection::connect_with(&root_db_config)
.await
.context("Connection to database failed!")?;
let query_create = format!("CREATE DATABASE {}", db_name);
connection
.execute(query_create.as_str())
.await
.context("Failed to create database!")?;
Ok(())
}
async fn migrate_db(db_config: &PgConnectOptions) -> anyhow::Result<()> {
let mut connection: PgConnection = Connection::connect_with(db_config)
.await
.context("Connection to database failed!")?;
let migrations_path = PathBuf::from(
std::env::var("CARGO_MANIFEST_DIR").expect("This command needs to be invoked using cargo"),
)
.join("..")
.join("migrations")
.canonicalize()
.unwrap();
let migrator = Migrator::new(Path::new(&migrations_path))
.await
.context("Failed to create migrator!")?;
connection
.ensure_migrations_table()
.await
.context("Failed to ensure migrations table!")?;
let applied_migrations: HashMap<_, _> = connection
.list_applied_migrations()
.await
.context("Failed to list applied migrations!")?
.into_iter()
.map(|m| (m.version, m))
.collect();
for migration in migrator.iter() {
if !applied_migrations.contains_key(&migration.version) {
connection
.apply(migration)
.await
.context("Failed to apply migration {}!")?;
println!("Applied migration {}.", migration.version);
}
}
Ok(())
}

1
cli/src/lib.rs Normal file
View File

@ -0,0 +1 @@
pub mod db;

8
config/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "brass-config"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.94"
dotenvy = "0.15.7"

102
config/src/lib.rs Normal file
View File

@ -0,0 +1,102 @@
use anyhow::anyhow;
use std::env;
use std::net::IpAddr;
#[derive(Clone)]
pub struct Config {
/// the ip the server will bind to, e.g. 127.0.0.1 or ::1
pub server_address: IpAddr,
/// the port the server will bind to, e.g. 3000
pub server_port: u16,
/// the database connection string e.g. "postgresql://user:password@localhost:5432/database"
pub database_url: String,
pub secret_key: String,
pub hostname: String,
pub smtp_server: String,
pub smtp_port: u16,
pub smtp_login: Option<String>,
pub smtp_password: Option<String>,
pub smtp_tlstype: SmtpTlsType,
}
#[derive(Clone)]
pub enum SmtpTlsType {
TLS,
StartTLS,
NoTLS,
}
impl From<String> for SmtpTlsType {
fn from(value: String) -> Self {
match value.as_str() {
"starttls" => SmtpTlsType::StartTLS,
"tls" => SmtpTlsType::TLS,
_ => SmtpTlsType::NoTLS,
}
}
}
#[derive(Clone)]
pub enum Environment {
Development,
Test,
Production,
}
pub fn load_config(env: &Environment) -> Result<Config, anyhow::Error> {
match env {
Environment::Development => {
dotenvy::dotenv().ok();
}
Environment::Test => {
dotenvy::from_filename(".env.test").ok();
}
Environment::Production => {
// do not load a file for prod
}
}
let config = Config {
server_address: env::var("SERVER_ADDRESS")?.parse()?,
server_port: env::var("SERVER_PORT")?.parse()?,
database_url: env::var("DATABASE_URL")?.parse()?,
secret_key: env::var("SECRET_KEY")?,
hostname: env::var("HOSTNAME")?,
smtp_server: env::var("SMTP_SERVER")?,
smtp_port: env::var("SMTP_PORT")?.parse()?,
smtp_login: env::var("SMTP_LOGIN")
.and_then(|x| Ok(Some(x)))
.unwrap_or(None),
smtp_password: env::var("SMTP_PASSWORD")
.and_then(|x| Ok(Some(x)))
.unwrap_or(None),
smtp_tlstype: SmtpTlsType::from(env::var("SMTP_TLSTYPE")?),
};
Ok(config)
}
pub fn get_env() -> Result<Environment, anyhow::Error> {
match env::var("APP_ENVIRONMENT") {
Ok(val) => {
//info!(r#"Setting environment from APP_ENVIRONMENT: "{}""#, val);
parse_env(&val)
}
Err(_) => {
//info!("Defaulting to environment: development");
Ok(Environment::Development)
}
}
}
pub fn parse_env(env: &str) -> Result<Environment, anyhow::Error> {
let env = &env.to_lowercase();
match env.as_str() {
"dev" => Ok(Environment::Development),
"development" => Ok(Environment::Development),
"test" => Ok(Environment::Test),
"prod" => Ok(Environment::Production),
"production" => Ok(Environment::Production),
unknown => Err(anyhow!(r#"Unknown environment: "{}"!"#, unknown)),
}
}

BIN
doc/anforderungen.xls Normal file

Binary file not shown.

View File

@ -10,3 +10,7 @@
- see vehicle.rs - see vehicle.rs
- use of applicationError - use of applicationError
- return Options, where queries are bound to id or search input - return Options, where queries are bound to id or search input
## Testing
### User
- CreatingNewUser_AsAdmin_SendsRegistrationMail

BIN
doc/erd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

12
macros/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "brass-macros"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
proc-macro = true
[dependencies]
quote = "1.0.37"
syn = { version = "2.0.90", features = ["full"] }

35
macros/src/lib.rs Normal file
View File

@ -0,0 +1,35 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn db_test(_: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let test_name = input.sig.ident.clone();
let test_arguments = input.sig.inputs;
let test_block = input.block;
let inner_test_name = syn::Ident::new(
format!("inner_{}", test_name).as_str(),
input.sig.ident.span(),
);
let setup = quote! {
let context = crate::utils::test_helper::setup().await;
};
let teardown = quote! {
crate::utils::test_helper::teardown(context).await;
};
let output = quote!(
#[actix_web::test]
async fn #test_name() {
#setup
async fn #inner_test_name(#test_arguments) #test_block
#inner_test_name(&context).await;
#teardown
}
);
TokenStream::from(output)
}

View File

@ -78,6 +78,8 @@ CREATE TABLE vehicleassignement
( (
eventId INTEGER REFERENCES event (id) ON DELETE CASCADE, eventId INTEGER REFERENCES event (id) ON DELETE CASCADE,
vehicleId INTEGER REFERENCES vehicle (id) ON DELETE CASCADE, vehicleId INTEGER REFERENCES vehicle (id) ON DELETE CASCADE,
startTime TIME NOT NULL,
endTime TIME NOT NULL,
PRIMARY KEY (eventId, vehicleId) PRIMARY KEY (eventId, vehicleId)
); );

View File

@ -1,27 +0,0 @@
use actix_web::{web, HttpResponse, Responder};
use sqlx::PgPool;
use crate::{
endpoints::IdPath,
models::{Area, Role, User},
utils::ApplicationError,
};
#[actix_web::delete("/area/delete/{id}")]
pub async fn delete(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
if Area::read_by_id(pool.get_ref(), path.id).await?.is_none() {
return Ok(HttpResponse::NotFound().finish());
};
Area::delete(pool.get_ref(), path.id).await?;
return Ok(HttpResponse::Ok().finish());
}

View File

@ -1,18 +0,0 @@
use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use crate::{endpoints::area::NewOrEditAreaTemplate, models::{Role, User}};
#[actix_web::get("/area/new")]
async fn get(user: web::ReqData<User>) -> impl Responder {
if user.role != Role::Admin {
return HttpResponse::Unauthorized().finish();
}
let template = NewOrEditAreaTemplate {
user: user.into_inner(),
area: None
};
template.to_response()
}

View File

@ -1,14 +0,0 @@
use actix_web::Responder;
use askama::Template;
use askama_actix::TemplateToResponse;
#[derive(Template)]
#[template(path = "imprint.html")]
struct ImprintTemplate {}
#[actix_web::get("/imprint")]
pub async fn get_imprint() -> impl Responder {
let template = ImprintTemplate {};
template.to_response()
}

View File

@ -1,26 +0,0 @@
use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
use crate::{endpoints::location::LocationTemplate, models::{Area, Role, User}};
#[actix_web::get("/locations/new")]
pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder {
if user.role == Role::AreaManager || user.role == Role::Admin {
let mut areas = None;
if user.role == Role::Admin {
areas = Some(Area::read_all(pool.get_ref()).await.unwrap());
}
let template = LocationTemplate {
user: user.into_inner(),
areas,
location: None
};
return template.to_response();
}
return HttpResponse::Unauthorized().finish();
}

View File

@ -1,57 +0,0 @@
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
use crate::models::{Area, Location, Role, User};
#[derive(Template)]
#[template(path = "location/overview.html")]
pub struct LocationsTemplate {
user: User,
grouped_locations: Vec<(Area, Vec<Location>)>,
}
#[actix_web::get("/locations")]
pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder {
if user.role == Role::AreaManager || user.role == Role::Admin {
let mut locations;
let mut grouped_locations: Vec<(Area, Vec<Location>)>;
if user.role == Role::Admin {
locations = Location::read_all(pool.get_ref()).await.unwrap();
grouped_locations = Area::read_all(pool.get_ref())
.await
.unwrap()
.into_iter()
.map(|a| (a, Vec::new()))
.collect();
} else {
locations = Location::read_by_area(pool.get_ref(), user.area_id)
.await
.unwrap();
let area = Area::read_by_id(pool.get_ref(), user.area_id)
.await
.unwrap().unwrap();
grouped_locations = vec![(area, Vec::new())];
}
for entry in grouped_locations.iter_mut() {
let (mut locations_in_this_area, rest): (Vec<_>, Vec<_>) =
locations.into_iter().partition(|l| l.area_id == entry.0.id);
locations = rest;
entry.1.append(&mut locations_in_this_area);
}
let template = LocationsTemplate {
user: user.into_inner(),
grouped_locations,
};
return template.to_response();
}
return HttpResponse::Unauthorized().finish();
}

View File

@ -1,38 +0,0 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use sqlx::PgPool;
use crate::{
endpoints::location::LocationForm,
models::{Location, Role, User}, utils::ApplicationError,
};
#[actix_web::post("/locations/new")]
pub async fn post(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
form: web::Form<LocationForm>,
) -> Result<impl Responder, ApplicationError> {
if user.role == Role::AreaManager && user.role == Role::Admin {
return Err(ApplicationError::Unauthorized);
}
let mut area_id = user.area_id;
if user.role == Role::Admin && form.area.is_some() {
area_id = form.area.unwrap();
}
// TODO: rework
match Location::create(pool.get_ref(), &form.name, area_id).await {
Ok(_) => {
return Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish())
}
Err(err) => {
println!("{}", err);
return Ok(HttpResponse::InternalServerError().finish());
}
}
}

View File

@ -1,16 +0,0 @@
use actix_web::{web, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use crate::models::User;
#[derive(Template)]
#[template(path = "user/profile_change_password.html")]
struct ProfileChangePasswordTemplate {}
#[actix_web::get("/users/changepassword")]
pub async fn get(_user: web::ReqData<User>) -> impl Responder {
let template = ProfileChangePasswordTemplate {};
template.to_response()
}

View File

@ -1,49 +0,0 @@
use crate::models::{Area, Role, User, Function};
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
#[derive(Template)]
#[template(path = "user/overview.html")]
pub struct UsersTemplate {
user: User,
area: Option<Area>,
users: Vec<User>,
}
#[actix_web::get("/users")]
pub async fn get_overview(user: Identity, pool: web::Data<PgPool>) -> impl Responder {
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
.await
.unwrap();
if current_user.role == Role::AreaManager || current_user.role == Role::Admin {
let mut area = None;
let users;
if current_user.role == Role::AreaManager {
area = Some(
Area::read_by_id(pool.get_ref(), current_user.area_id)
.await
.unwrap().unwrap(),
);
users = User::read_all_by_area(pool.get_ref(), current_user.area_id)
.await
.unwrap();
} else {
users = User::read_all_including_area(pool.get_ref()).await.unwrap();
}
let template = UsersTemplate {
user: current_user,
area,
users,
};
return template.to_response();
}
return HttpResponse::BadRequest().body("Fehler beim Zugriff auf die Nutzerverwaltung!");
}

View File

@ -1,29 +0,0 @@
use actix_web::{web, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
use crate::{
filters,
models::{Area, Role, User},
};
#[derive(Template)]
#[template(path = "user/profile.html")]
struct ProfileTemplate {
user: User,
}
#[actix_web::get("/profile")]
pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder {
let area = Area::read_by_id(pool.get_ref(), user.area_id)
.await
.unwrap();
let mut user = user.into_inner();
user.area = area;
let template = ProfileTemplate { user };
return template.to_response();
}

View File

@ -1,94 +0,0 @@
use actix_identity::Identity;
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use serde::Deserialize;
use sqlx::PgPool;
use crate::{
endpoints::IdPath,
models::{Function, Role, User},
};
#[derive(Deserialize)]
pub struct EditUserForm {
email: String,
name: String,
role: u8,
function: u8,
area: Option<i32>,
}
#[actix_web::post("/users/edit/{id}")]
pub async fn post_edit(
user: Identity,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
form: web::Form<EditUserForm>,
) -> impl Responder {
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
.await
.unwrap();
if current_user.role != Role::AreaManager && current_user.role != Role::Admin {
return HttpResponse::Unauthorized().finish();
}
if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await {
if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id {
return HttpResponse::Unauthorized().finish();
}
let mut changed = false;
let email = if user_in_db.email != form.email {
changed = true;
Some(form.email.as_str())
} else {
None
};
let name = if user_in_db.name != form.name {
changed = true;
Some(form.name.as_str())
} else {
None
};
let role = if user_in_db.role as u8 != form.role {
if let Ok(r) = Role::try_from(form.role) {
changed = true;
Some(r)
} else {
None
}
} else {
None
};
let function = if user_in_db.function as u8 != form.function {
if let Ok(f) = Function::try_from(form.function) {
changed = true;
Some(f)
} else {
None
}
} else {
None
};
let area = if current_user.role == Role::Admin && form.area.is_some() && user_in_db.area_id != form.area.unwrap() {
changed = true;
Some(form.area.unwrap())
} else {
None
};
if changed {
match User::update(pool.get_ref(), path.id, email, name, None, None, role, function, area, None, None).await {
Ok(_) => return HttpResponse::Found().insert_header((LOCATION, "/users")).finish(),
Err(err) => println!("{}", err)
}
}
}
return HttpResponse::BadRequest().body("Fehler beim Bearbeiten des Nutzers");
}

View File

@ -1,66 +0,0 @@
use std::env;
use std::time::Duration;
use actix_identity::IdentityMiddleware;
use actix_session::SessionMiddleware;
use actix_web::cookie::Key;
use actix_web::{web, App, HttpServer};
use actix_web_static_files::ResourceFiles;
use dotenv::dotenv;
use sqlx::postgres::PgPool;
use crate::postgres_session_store::SqlxPostgresqlSessionStore;
use crate::utils::manage_commands::{handle_command, parse_args};
mod auth;
mod endpoints;
mod middleware;
mod models;
mod utils;
mod filters;
mod postgres_session_store;
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
include!(concat!(env!("OUT_DIR"), "/built.rs"));
#[actix_web::main]
async fn main() -> anyhow::Result<()> {
dotenv()?;
let args = parse_args()?;
let pool = PgPool::connect(&env::var("DATABASE_URL")?).await?;
let mailer = utils::email::get_mailer()?;
let secret_key = Key::from(env::var("SECRET_KEY")?.as_bytes());
let store = SqlxPostgresqlSessionStore::from_pool(pool.clone().into());
handle_command(args.command, &pool).await?;
let address = env::var("ADDRESS")?;
let port = env::var("PORT")?.parse()?;
println!("Starting server on http://{address}:{port}.");
HttpServer::new(move || {
let generated = generate();
App::new()
.app_data(web::Data::new(pool.clone()))
.app_data(web::Data::new(mailer.clone()))
.configure(endpoints::init)
.wrap(middleware::RedirectToLogin)
.wrap(middleware::LoadCurrentUser)
.wrap(
IdentityMiddleware::builder()
.visit_deadline(Some(Duration::from_secs(300)))
.build(),
)
.wrap(SessionMiddleware::new(store.clone(), secret_key.clone()))
.service(ResourceFiles::new("/static", generated))
})
.bind((address, port))?
.run()
.await?;
Ok(())
}

View File

@ -1,34 +0,0 @@
use sqlx::{query, PgPool};
use super::Result;
pub struct VehicleAssignement {
event_id: i32,
vehicle_id: i32,
}
impl VehicleAssignement {
pub async fn create(pool: &PgPool, event_id: i32, vehicle_id: i32) -> Result<()> {
query!(
"INSERT INTO vehicleassignement (eventId, vehicleId) VALUES ($1, $2);",
event_id,
vehicle_id
)
.execute(pool)
.await?;
Ok(())
}
pub async fn delete(pool: &PgPool, event_id: i32, vehicle_id: i32) -> Result<()> {
query!(
"DELETE FROM vehicleassignement WHERE eventId = $1 AND vehicleId = $2;",
event_id,
vehicle_id
)
.execute(pool)
.await?;
Ok(())
}
}

3495
web/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

46
web/Cargo.toml Normal file
View File

@ -0,0 +1,46 @@
[package]
name = "brass-web"
version = "0.1.0"
edition = "2021"
license = "AGPL-3.0"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sqlx = { version = "^0.8", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
actix-web = { version = "4" }
serde = { version = "1.0.164", features = ["derive"] }
argon2 = { version = "0.5.0", features = [ "std"]}
anyhow = "1.0.71"
dotenv = "0.15.0"
actix-session = { version = "0.10.1", features = ["cookie-session"] }
actix-identity = "0.8.0"
chrono = { version = "0.4.33", features = ["serde", "now"] }
actix-files = "0.6.5"
futures-util = "0.3.30"
serde_json = "1.0.114"
pico-args = "0.5.0"
rand = { version = "0.8.5", features = ["getrandom"] }
async-trait = "0.1.79"
lettre = "0.11.7"
quick-xml = { version = "0.31.0", features = ["serde", "serialize"] }
actix-web-static-files = "4.0"
static-files = "0.2.1"
zxcvbn = "3.1.0"
thiserror = "1.0.63"
idna = "=1.0.2"
regex = "1.11.1"
brass-macros = { path = "../macros" }
brass-config = { path = "../config" }
actix-http = "3.9.0"
rinja = "0.3.5"
[build-dependencies]
built = "0.7.4"
static-files = "0.2.1"
change-detection = "1.2.0"
[dev-dependencies]
insta = "1.41.1"
fake = "3.0.1"

View File

@ -1,3 +1,4 @@
use change_detection::ChangeDetection;
use static_files::{resource_dir, NpmBuild}; use static_files::{resource_dir, NpmBuild};
use std::{ use std::{
fs::{self, copy}, fs::{self, copy},
@ -5,9 +6,15 @@ use std::{
}; };
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
ChangeDetection::path("static/utils.js")
.path("static/style.scss")
.path("static/brass.jpeg")
.path("static/package.json")
.generate();
built::write_built_file().expect("Failed to acquire build-time information"); built::write_built_file().expect("Failed to acquire build-time information");
NpmBuild::new("./static").change_detection().install()?.run("build-bulma")?; NpmBuild::new("./static").install()?.run("build-bulma")?;
let dist_path = Path::new("./static/dist"); let dist_path = Path::new("./static/dist");
let nm_path = Path::new("./static/node_modules"); let nm_path = Path::new("./static/node_modules");

View File

@ -0,0 +1,56 @@
---
source: web/src/endpoints/area/get_new.rs
expression: body
snapshot_kind: text
---
<section class="section">
<div class="container">
<form method="post" action="/area/new">
<h1 class="title">Neuen Bereich anlegen</h1>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Name</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text" name="name" required placeholder="Leipzig Ost" />
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<button class="button is-success">
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
<span>
Erstellen
</span>
</button>
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/locations">
<svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" />
</svg>
<span>Zurück</span>
</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>

View File

@ -0,0 +1,77 @@
use actix_web::{web, HttpResponse, Responder};
use brass_macros::db_test;
use sqlx::PgPool;
use crate::{
endpoints::IdPath,
models::{Area, Role, User},
utils::ApplicationError,
};
#[cfg(test)]
use crate::utils::test_helper::{test_delete, DbTestContext, RequestConfig, StatusCode};
#[actix_web::delete("/area/delete/{id}")]
pub async fn delete(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
if Area::read_by_id(pool.get_ref(), path.id).await?.is_none() {
return Ok(HttpResponse::NotFound().finish());
};
Area::delete(pool.get_ref(), path.id).await?;
return Ok(HttpResponse::Ok().finish());
}
#[db_test]
async fn works_when_user_is_admin(context: &DbTestContext) {
Area::create(&context.db_pool, "Area to delete")
.await
.unwrap();
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_some());
let app = context.app().await;
let config = RequestConfig {
uri: "/area/delete/2".to_string(),
role: Role::Admin,
function: crate::models::Function::Posten,
user_area: 1,
};
let response = test_delete(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::OK, response.status());
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_none());
}
#[db_test]
async fn does_not_work_when_user_is_not_admin(context: &DbTestContext) {
Area::create(&context.db_pool, "Area to delete")
.await
.unwrap();
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_some());
let app = context.app().await;
let response = test_delete(&context.db_pool, app, &RequestConfig::new("/area/delete/2")).await;
assert_eq!(StatusCode::UNAUTHORIZED, response.status());
assert!(Area::read_by_id(&context.db_pool, 2)
.await
.unwrap()
.is_some());
}

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use sqlx::PgPool; use sqlx::PgPool;
use crate::{ use crate::{
@ -24,7 +24,7 @@ async fn get(
area: Some(area_in_db), area: Some(area_in_db),
}; };
return Ok(template.to_response()); return Ok(HttpResponse::Ok().body(template.render()?))
} else { } else {
return Ok(HttpResponse::NotFound().finish()); return Ok(HttpResponse::NotFound().finish());
} }

View File

@ -0,0 +1,44 @@
use crate::{
endpoints::area::NewOrEditAreaTemplate,
models::{Role, User},
utils::ApplicationError,
};
use actix_web::{web, HttpResponse, Responder};
use brass_macros::db_test;
use rinja::Template;
#[cfg(test)]
use crate::utils::test_helper::{
assert_snapshot, read_body, test_get, DbTestContext, RequestConfig, StatusCode
};
#[actix_web::get("/area/new")]
async fn get(user: web::ReqData<User>) -> Result<impl Responder, ApplicationError> {
if user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
}
let template = NewOrEditAreaTemplate {
user: user.into_inner(),
area: None,
};
Ok(HttpResponse::Ok().body(template.render()?))
}
#[db_test]
async fn produces_template(context: &DbTestContext) {
let app = context.app().await;
let config = RequestConfig {
uri: "/area/new".to_string(),
role: Role::Admin,
function: crate::models::Function::Posten,
user_area: 1,
};
let response = test_get(&context.db_pool, app, &config).await;
assert_eq!(StatusCode::OK, response.status());
let body = read_body(response).await;
assert_snapshot!(body);
}

View File

@ -4,13 +4,14 @@ pub mod get_edit;
pub mod post_edit; pub mod post_edit;
pub mod delete; pub mod delete;
use askama::Template; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use crate::models::{Area, Role, User}; use crate::models::{Area, Role, User};
#[derive(Template)] #[derive(Template)]
#[template(path = "area/new_or_edit.html")] #[cfg_attr(not(test), template(path = "area/new_or_edit.html"))]
#[cfg_attr(test, template(path = "area/new_or_edit.html", block = "content"))]
struct NewOrEditAreaTemplate { struct NewOrEditAreaTemplate {
user: User, user: User,
area: Option<Area>, area: Option<Area>,

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -61,5 +61,5 @@ pub async fn delete(
further_wachhabender_required, further_wachhabender_required,
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,4 +1,4 @@
use askama::Template; use rinja::Template;
use crate::{ use crate::{
filters, filters,

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -106,5 +106,5 @@ pub async fn post(
further_wachhabender_required, further_wachhabender_required,
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,10 +1,11 @@
use actix_web::{web, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use chrono::NaiveDate; use chrono::NaiveDate;
use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use crate::endpoints::availability::NewOrEditAvailabilityTemplate; use crate::endpoints::availability::NewOrEditAvailabilityTemplate;
use crate::models::User; use crate::models::User;
use crate::utils::ApplicationError;
#[derive(Deserialize)] #[derive(Deserialize)]
struct AvailabilityNewQuery { struct AvailabilityNewQuery {
@ -17,7 +18,7 @@ struct AvailabilityNewQuery {
pub async fn get( pub async fn get(
user: web::ReqData<User>, user: web::ReqData<User>,
query: web::Query<AvailabilityNewQuery>, query: web::Query<AvailabilityNewQuery>,
) -> impl Responder { ) -> Result<impl Responder, ApplicationError> {
let template = NewOrEditAvailabilityTemplate { let template = NewOrEditAvailabilityTemplate {
user: user.into_inner(), user: user.into_inner(),
date: query.date, date: query.date,
@ -28,5 +29,5 @@ pub async fn get(
comment: None, comment: None,
}; };
template.to_response() Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,8 +1,7 @@
use crate::filters; use crate::{filters, utils::ApplicationError};
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use chrono::{NaiveDate, Utc}; use chrono::{NaiveDate, Utc};
use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -30,18 +29,18 @@ async fn get(
user: web::ReqData<User>, user: web::ReqData<User>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
query: web::Query<CalendarQuery>, query: web::Query<CalendarQuery>,
) -> impl Responder { ) -> Result<impl Responder, ApplicationError> {
let date = match query.date { let date = match query.date {
Some(given_date) => given_date, Some(given_date) => given_date,
None => Utc::now().date_naive(), None => Utc::now().date_naive(),
}; };
let areas = Area::read_all(pool.get_ref()).await.unwrap(); let areas = Area::read_all(pool.get_ref()).await?;
let selected_area = match query.area { let selected_area = match query.area {
Some(id) => { Some(id) => {
if !areas.iter().any(|a| a.id == id) { if !areas.iter().any(|a| a.id == id) {
return HttpResponse::BadRequest().finish(); return Ok(HttpResponse::BadRequest().finish());
} }
Some(id) Some(id)
@ -54,16 +53,14 @@ async fn get(
date, date,
query.area.unwrap_or(user.area_id), query.area.unwrap_or(user.area_id),
) )
.await .await?;
.unwrap();
let availabillities = Availabillity::read_by_date_and_area_including_user( let availabillities = Availabillity::read_by_date_and_area_including_user(
pool.get_ref(), pool.get_ref(),
date, date,
query.area.unwrap_or(user.area_id), query.area.unwrap_or(user.area_id),
) )
.await .await?;
.unwrap();
let template = CalendarTemplate { let template = CalendarTemplate {
user: user.into_inner(), user: user.into_inner(),
@ -74,5 +71,5 @@ async fn get(
availabillities, availabillities,
}; };
template.to_response() Ok(HttpResponse::Ok().body(template.render()?))
} }

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse; use rinja::Template;
use serde::Deserialize; use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
@ -50,5 +50,5 @@ pub async fn get(
comment: availabillity.comment.as_deref(), comment: availabillity.comment.as_deref(),
}; };
Ok(template.to_response()) Ok(HttpResponse::Ok().body(template.render()?))
} }

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