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
# SQLite
DATABASE_URL=postgresql://max@localhost/brass
SQLX_OFFLINE=true
# 64 byte long openssl rand -base64 64
SECRET_KEY="changeInProdOrHandAb11111111111111111111111111111111111111111111"
HOSTNAME="localhost"
ADDRESS="127.0.0.1"
PORT="8080"
SERVER_ADDRESS="127.0.0.1"
SERVER_PORT="8080"
SMTP_SERVER="localhost"
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]
name = "brass"
version = "0.1.0"
edition = "2021"
license = "AGPL-3.0"
[workspace]
members = [ "cli", "config", "macros", "web", ]
resolver = "2"
default-members = ["web"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sqlx = { version = "0.6", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
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]
[profile.dev.package.rinja_derive]
opt-level = 3
[profile.dev.package.sqlx-macros]
opt-level = 3

View File

@ -29,7 +29,7 @@ rc_reload=NO
```
```
```ini
# Postgres
# DATABASE_URL=postgres://postgres@localhost/my_database
# SQLite
@ -46,3 +46,11 @@ SMTP_PORT="25"
# SMTP_PASSWORD=""
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
- use of applicationError
- 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,
vehicleId INTEGER REFERENCES vehicle (id) ON DELETE CASCADE,
startTime TIME NOT NULL,
endTime TIME NOT NULL,
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 std::{
fs::{self, copy},
@ -5,9 +6,15 @@ use std::{
};
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");
NpmBuild::new("./static").change_detection().install()?.run("build-bulma")?;
NpmBuild::new("./static").install()?.run("build-bulma")?;
let dist_path = Path::new("./static/dist");
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 askama_actix::TemplateToResponse;
use rinja::Template;
use sqlx::PgPool;
use crate::{
@ -24,7 +24,7 @@ async fn get(
area: Some(area_in_db),
};
return Ok(template.to_response());
return Ok(HttpResponse::Ok().body(template.render()?))
} else {
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 delete;
use askama::Template;
use rinja::Template;
use serde::Deserialize;
use crate::models::{Area, Role, User};
#[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 {
user: User,
area: Option<Area>,

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use rinja::Template;
use serde::Deserialize;
use sqlx::PgPool;
@ -61,5 +61,5 @@ pub async fn delete(
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::{
filters,

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use rinja::Template;
use serde::Deserialize;
use sqlx::PgPool;
@ -106,5 +106,5 @@ pub async fn post(
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 askama_actix::TemplateToResponse;
use actix_web::{web, HttpResponse, Responder};
use chrono::NaiveDate;
use rinja::Template;
use serde::Deserialize;
use crate::endpoints::availability::NewOrEditAvailabilityTemplate;
use crate::models::User;
use crate::utils::ApplicationError;
#[derive(Deserialize)]
struct AvailabilityNewQuery {
@ -17,7 +18,7 @@ struct AvailabilityNewQuery {
pub async fn get(
user: web::ReqData<User>,
query: web::Query<AvailabilityNewQuery>,
) -> impl Responder {
) -> Result<impl Responder, ApplicationError> {
let template = NewOrEditAvailabilityTemplate {
user: user.into_inner(),
date: query.date,
@ -28,5 +29,5 @@ pub async fn get(
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 askama::Template;
use askama_actix::TemplateToResponse;
use chrono::{NaiveDate, Utc};
use rinja::Template;
use serde::Deserialize;
use sqlx::PgPool;
@ -30,18 +29,18 @@ async fn get(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
query: web::Query<CalendarQuery>,
) -> impl Responder {
) -> Result<impl Responder, ApplicationError> {
let date = match query.date {
Some(given_date) => given_date,
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 {
Some(id) => {
if !areas.iter().any(|a| a.id == id) {
return HttpResponse::BadRequest().finish();
return Ok(HttpResponse::BadRequest().finish());
}
Some(id)
@ -54,16 +53,14 @@ async fn get(
date,
query.area.unwrap_or(user.area_id),
)
.await
.unwrap();
.await?;
let availabillities = Availabillity::read_by_date_and_area_including_user(
pool.get_ref(),
date,
query.area.unwrap_or(user.area_id),
)
.await
.unwrap();
.await?;
let template = CalendarTemplate {
user: user.into_inner(),
@ -74,5 +71,5 @@ async fn get(
availabillities,
};
template.to_response()
Ok(HttpResponse::Ok().body(template.render()?))
}

View File

@ -1,5 +1,5 @@
use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use rinja::Template;
use serde::Deserialize;
use sqlx::PgPool;
@ -50,5 +50,5 @@ pub async fn get(
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