updating api formats
This commit is contained in:
206
weather-service/Cargo.lock
generated
206
weather-service/Cargo.lock
generated
@@ -45,7 +45,7 @@ dependencies = [
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"ahash",
|
||||
"base64",
|
||||
"base64 0.21.4",
|
||||
"bitflags 2.4.0",
|
||||
"brotli",
|
||||
"bytes",
|
||||
@@ -73,6 +73,22 @@ dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-identity"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1224c9f9593dc27c9077b233ce04adedc1d7febcfc35ee9f53ea3c24df180bec"
|
||||
dependencies = [
|
||||
"actix-service",
|
||||
"actix-session",
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"futures-core",
|
||||
"serde",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-macros"
|
||||
version = "0.2.4"
|
||||
@@ -135,6 +151,23 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-session"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da8b818ae1f11049a4d218975345fe8e56ce5a5f92c11f972abcff5ff80e87"
|
||||
dependencies = [
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"derive_more",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-utils"
|
||||
version = "3.0.1"
|
||||
@@ -212,6 +245,41 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
@@ -263,6 +331,23 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@@ -284,6 +369,12 @@ dependencies = [
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.4"
|
||||
@@ -390,6 +481,16 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@@ -402,7 +503,14 @@ version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"base64 0.20.0",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
@@ -448,9 +556,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.8"
|
||||
@@ -526,6 +644,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -689,6 +808,16 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.0"
|
||||
@@ -732,6 +861,24 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.9"
|
||||
@@ -862,6 +1009,15 @@ dependencies = [
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.8.0"
|
||||
@@ -1068,6 +1224,12 @@ version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.57"
|
||||
@@ -1165,6 +1327,18 @@ version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postgis_diesel"
|
||||
version = "2.2.1"
|
||||
@@ -1304,7 +1478,7 @@ version = "0.11.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.21.4",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
@@ -1485,6 +1659,17 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
@@ -1529,6 +1714,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -1763,6 +1954,16 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
@@ -1882,6 +2083,7 @@ name = "weather-service"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-cors",
|
||||
"actix-identity",
|
||||
"actix-rt",
|
||||
"actix-web",
|
||||
"chrono",
|
||||
|
||||
@@ -8,6 +8,8 @@ edition = "2021"
|
||||
[dependencies]
|
||||
actix-web = "4.4.0"
|
||||
actix-rt = "2.9.0"
|
||||
actix-cors = "0.6.4"
|
||||
actix-identity = "0.5.0"
|
||||
chrono = { version = "0.4.30", features = ["serde"] }
|
||||
dotenv = "0.15.0"
|
||||
diesel = { version = "2.0", features = ["postgres", "r2d2", "uuid", "chrono"] }
|
||||
@@ -23,5 +25,4 @@ serde = {version = "1.0.188", features = ["derive"]}
|
||||
serde_json = "1.0.105"
|
||||
tokio = { version = "1.32.0", features = ["macros", "rt"] }
|
||||
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||
log = "0.4.20"
|
||||
actix-cors = "0.6.4"
|
||||
log = "0.4.20"
|
||||
@@ -27,7 +27,7 @@ clean:
|
||||
rm -rf target
|
||||
|
||||
clean-db: ## Remove database and Cargo packages
|
||||
docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "DROP DATABASE IF EXISTS \"${DATABASE_NAME}\";"' || true
|
||||
docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "DROP DATABASE IF EXISTS \"${DATABASE_NAME}\";"'
|
||||
docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "CREATE DATABASE \"${DATABASE_NAME}\";"' || true
|
||||
|
||||
|
||||
56
weather-service/README.md
Normal file
56
weather-service/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
## REST API
|
||||
The REST API for the weather service is described below.
|
||||
|
||||
### Import Airports
|
||||
---
|
||||
#### Request
|
||||
`GET /import`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Get Airports
|
||||
---
|
||||
#### Request
|
||||
`GET /airports`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Get Airport
|
||||
---
|
||||
#### Request
|
||||
`GET /airports/{icao}`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Create Airport
|
||||
---
|
||||
#### Request
|
||||
`CREATE /airports`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Update Airport
|
||||
---
|
||||
#### Request
|
||||
`PUT /airports/{icao}`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
|
||||
### Delete Airport
|
||||
---
|
||||
#### Request
|
||||
`DELETE /airports/{icao}`
|
||||
|
||||
#### Response
|
||||
```
|
||||
```
|
||||
1
weather-service/migrations/000002_create_users/down.sql
Normal file
1
weather-service/migrations/000002_create_users/down.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE users;
|
||||
11
weather-service/migrations/000002_create_users/up.sql
Normal file
11
weather-service/migrations/000002_create_users/up.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
||||
email TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
favorites TEXT[]
|
||||
);
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::db;
|
||||
use crate::error_handler::CustomError;
|
||||
use crate::error_handler::ServiceError;
|
||||
use crate::schema::airports;
|
||||
use diesel::prelude::*;
|
||||
use log::trace;
|
||||
// use log::trace;
|
||||
use postgis_diesel::types::*;
|
||||
use postgis_diesel::functions::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, AsChangeset, Insertable)]
|
||||
#[diesel(table_name = airports)]
|
||||
pub struct Airport {
|
||||
pub struct InsertAirport {
|
||||
pub icao: String,
|
||||
pub category: String,
|
||||
pub full_name: String,
|
||||
@@ -26,7 +26,7 @@ pub struct Airport {
|
||||
|
||||
#[derive(Serialize, Deserialize, Queryable, QueryableByName)]
|
||||
#[diesel(table_name = airports)]
|
||||
pub struct Airports {
|
||||
pub struct QueryAirport {
|
||||
pub icao: String,
|
||||
pub id: i32,
|
||||
pub category: String,
|
||||
@@ -42,13 +42,13 @@ pub struct Airports {
|
||||
pub point: Point
|
||||
}
|
||||
|
||||
impl Airports {
|
||||
pub fn get_all(bounds: Option<Polygon<Point>>, category: Option<String>, filter: Option<String>, limit: i32, page: i32) -> Result<Vec<Self>, CustomError> {
|
||||
impl QueryAirport {
|
||||
pub fn get_all(bounds: Option<Polygon<Point>>, category: Option<String>, filter: Option<String>, limit: i32, page: i32) -> Result<Vec<Self>, ServiceError> {
|
||||
let mut conn = db::connection()?;
|
||||
let mut query = airports::table
|
||||
.limit(limit as i64)
|
||||
.into_boxed();
|
||||
query = query.filter(airports::id.gt(page * limit));
|
||||
query = query.filter(airports::id.gt(std::cmp::max(1, page - 1) * limit));
|
||||
|
||||
if let Some(bounds) = bounds {
|
||||
query = query.filter(st_contains(bounds, airports::point));
|
||||
@@ -62,28 +62,28 @@ impl Airports {
|
||||
.or(airports::full_name.ilike(format!("%{}%", filter)))
|
||||
)
|
||||
}
|
||||
let debug = diesel::debug_query::<diesel::pg::Pg, _>(&query);
|
||||
trace!("{}", debug);
|
||||
let airports: Vec<Airports> = query.order(airports::category.asc()).load::<Airports>(&mut conn)?;
|
||||
// let debug = diesel::debug_query::<diesel::pg::Pg, _>(&query);
|
||||
// trace!("{}", debug);
|
||||
let airports: Vec<QueryAirport> = query.order(airports::category.asc()).load::<QueryAirport>(&mut conn)?;
|
||||
Ok(airports)
|
||||
}
|
||||
|
||||
pub fn find(icao: String) -> Result<Self, CustomError> {
|
||||
pub fn find(icao: String) -> Result<Self, ServiceError> {
|
||||
let mut conn = db::connection()?;
|
||||
let airport = airports::table.filter(airports::icao.eq(icao)).first(&mut conn)?;
|
||||
Ok(airport)
|
||||
}
|
||||
|
||||
pub fn create(airport: Airport) -> Result<Self, CustomError> {
|
||||
pub fn create(airport: InsertAirport) -> Result<Self, ServiceError> {
|
||||
let mut conn = db::connection()?;
|
||||
let airport = Airport::from(airport);
|
||||
let airport = InsertAirport::from(airport);
|
||||
let airport = diesel::insert_into(airports::table)
|
||||
.values(airport)
|
||||
.get_result(&mut conn)?;
|
||||
Ok(airport)
|
||||
}
|
||||
|
||||
pub fn update(id: i32, airport: Airport) -> Result<Self, CustomError> {
|
||||
pub fn update(id: i32, airport: InsertAirport) -> Result<Self, ServiceError> {
|
||||
let mut conn = db::connection()?;
|
||||
let airport = diesel::update(airports::table)
|
||||
.filter(airports::id.eq(id))
|
||||
@@ -92,7 +92,7 @@ pub fn update(id: i32, airport: Airport) -> Result<Self, CustomError> {
|
||||
Ok(airport)
|
||||
}
|
||||
|
||||
pub fn delete(id: i32) -> Result<usize, CustomError> {
|
||||
pub fn delete(id: i32) -> Result<usize, ServiceError> {
|
||||
let mut conn = db::connection()?;
|
||||
let res = diesel::delete(airports::table.filter(airports::id.eq(id))).execute(&mut conn)?;
|
||||
Ok(res)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{airports::{Airport, Airports}, db::{self, Metadata}};
|
||||
use crate::{airports::{InsertAirport, QueryAirport}, db::{self, Metadata}};
|
||||
use actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest};
|
||||
use log::{error, warn};
|
||||
use postgis_diesel::types::{Polygon, Point};
|
||||
@@ -21,7 +21,7 @@ async fn import() -> HttpResponse {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AirportsResponse {
|
||||
pub data: Vec<Airports>,
|
||||
pub data: Vec<QueryAirport>,
|
||||
pub meta: Metadata
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
|
||||
None => None
|
||||
};
|
||||
|
||||
match web::block(move || Airports::get_all(polygon, category, filter, params.limit, params.page)).await.unwrap() {
|
||||
match web::block(move || QueryAirport::get_all(polygon, category, filter, params.limit, params.page)).await.unwrap() {
|
||||
Ok(a) => HttpResponse::Ok().json(AirportsResponse {
|
||||
data: a,
|
||||
meta: Metadata { page: 0, limit: 0, pages: 0, total: 0 }
|
||||
@@ -96,13 +96,13 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AirportResponse {
|
||||
pub data: Airports,
|
||||
pub data: QueryAirport,
|
||||
pub meta: Metadata
|
||||
}
|
||||
|
||||
#[get("/airports/{icao}")]
|
||||
async fn get(icao: web::Path<String>) -> HttpResponse {
|
||||
match Airports::find(icao.into_inner()) {
|
||||
match QueryAirport::find(icao.into_inner()) {
|
||||
Ok(a) => HttpResponse::Ok().json(AirportResponse {
|
||||
data: a,
|
||||
meta: Metadata { page: 0, limit: 0, pages: 0, total: 0 }
|
||||
@@ -115,8 +115,8 @@ async fn get(icao: web::Path<String>) -> HttpResponse {
|
||||
}
|
||||
|
||||
#[post("/airports")]
|
||||
async fn create(airport: web::Json<Airport>) -> HttpResponse {
|
||||
match Airports::create(airport.into_inner()) {
|
||||
async fn create(airport: web::Json<InsertAirport>) -> HttpResponse {
|
||||
match QueryAirport::create(airport.into_inner()) {
|
||||
Ok(a) => HttpResponse::Created().json(a),
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
@@ -125,9 +125,9 @@ async fn create(airport: web::Json<Airport>) -> HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[put("/airports/{id}")]
|
||||
async fn update(id: web::Path<i32>, airport: web::Json<Airport>) -> HttpResponse {
|
||||
match Airports::update(id.into_inner(), airport.into_inner()) {
|
||||
#[put("/airports/{icao}")]
|
||||
async fn update(icao: web::Path<i32>, airport: web::Json<InsertAirport>) -> HttpResponse {
|
||||
match QueryAirport::update(icao.into_inner(), airport.into_inner()) {
|
||||
Ok(a) => HttpResponse::Ok().json(a),
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
@@ -136,9 +136,9 @@ async fn update(id: web::Path<i32>, airport: web::Json<Airport>) -> HttpResponse
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("/airports/{id}")]
|
||||
async fn delete(id: web::Path<i32>) -> HttpResponse {
|
||||
match Airports::delete(id.into_inner()) {
|
||||
#[delete("/airports/{icao}")]
|
||||
async fn delete(icao: web::Path<i32>) -> HttpResponse {
|
||||
match QueryAirport::delete(icao.into_inner()) {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
|
||||
0
weather-service/src/auth/mod.rs
Normal file
0
weather-service/src/auth/mod.rs
Normal file
@@ -1,4 +1,4 @@
|
||||
use crate::{error_handler::CustomError, airports::{Airport, Airports}};
|
||||
use crate::{error_handler::ServiceError, airports::{InsertAirport, QueryAirport}};
|
||||
use diesel::{r2d2::ConnectionManager, PgConnection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::diesel_migrations::MigrationHarness;
|
||||
@@ -34,18 +34,18 @@ pub fn init() {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn connection() -> Result<DbConnection, CustomError> {
|
||||
pub fn connection() -> Result<DbConnection, ServiceError> {
|
||||
POOL.get()
|
||||
.map_err(|e| CustomError::new(500, format!("Failed getting db connection: {}", e)))
|
||||
.map_err(|e| ServiceError::new(500, format!("Failed getting db connection: {}", e)))
|
||||
}
|
||||
|
||||
pub fn import_data() {
|
||||
let path = "airport-codes.json";
|
||||
debug!("Importing data from {}", path);
|
||||
let contents: String = std::fs::read_to_string(path).expect("Failed to read file");
|
||||
let airports: Vec<Airport> = serde_json::from_str(&contents).expect("JSON was not well formed.");
|
||||
let airports: Vec<InsertAirport> = serde_json::from_str(&contents).expect("JSON was not well formed.");
|
||||
for airport in airports {
|
||||
match Airports::create(airport) {
|
||||
match QueryAirport::create(airport) {
|
||||
Ok(_) => {},
|
||||
Err(err) => error!("Error inserting airport; {}", err)
|
||||
};
|
||||
|
||||
@@ -7,14 +7,14 @@ use serde_json::json;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct CustomError {
|
||||
pub struct ServiceError {
|
||||
pub error_status_code: u16,
|
||||
pub error_message: String,
|
||||
}
|
||||
|
||||
impl CustomError {
|
||||
pub fn new(error_status_code: u16, error_message: String) -> CustomError {
|
||||
CustomError {
|
||||
impl ServiceError {
|
||||
pub fn new(error_status_code: u16, error_message: String) -> ServiceError {
|
||||
ServiceError {
|
||||
error_status_code,
|
||||
error_message,
|
||||
}
|
||||
@@ -32,25 +32,25 @@ impl CustomError {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CustomError {
|
||||
impl fmt::Display for ServiceError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(self.error_message.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DieselError> for CustomError {
|
||||
fn from(error: DieselError) -> CustomError {
|
||||
impl From<DieselError> for ServiceError {
|
||||
fn from(error: DieselError) -> ServiceError {
|
||||
match error {
|
||||
DieselError::DatabaseError(_, err) => CustomError::new(409, err.message().to_string()),
|
||||
DieselError::DatabaseError(_, err) => ServiceError::new(409, err.message().to_string()),
|
||||
DieselError::NotFound => {
|
||||
CustomError::new(404, "The airport record was not found".to_string())
|
||||
ServiceError::new(404, "The record was not found".to_string())
|
||||
}
|
||||
err => CustomError::new(500, format!("Unknown Diesel error: {}", err)),
|
||||
err => ServiceError::new(500, format!("Unknown Diesel error: {}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for CustomError {
|
||||
impl ResponseError for ServiceError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let status_code = match StatusCode::from_u16(self.error_status_code) {
|
||||
Ok(status_code) => status_code,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
extern crate actix_web;
|
||||
extern crate diesel;
|
||||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
@@ -8,12 +9,13 @@ use dotenv::dotenv;
|
||||
use env_logger::Env;
|
||||
use listenfd::ListenFd;
|
||||
use log::debug;
|
||||
use std::env;
|
||||
|
||||
mod airports;
|
||||
mod auth;
|
||||
mod db;
|
||||
mod error_handler;
|
||||
mod metars;
|
||||
mod users;
|
||||
mod schema;
|
||||
|
||||
#[actix_rt::main]
|
||||
@@ -34,6 +36,7 @@ async fn main() -> std::io::Result<()> {
|
||||
App::new()
|
||||
.configure(airports::init_routes)
|
||||
.configure(metars::init_routes)
|
||||
.configure(users::init_routes)
|
||||
.wrap(cors)
|
||||
.wrap(Logger::default())
|
||||
});
|
||||
@@ -41,8 +44,8 @@ async fn main() -> std::io::Result<()> {
|
||||
server = match listenfd.take_tcp_listener(0)? {
|
||||
Some(listener) => server.listen(listener)?,
|
||||
None => {
|
||||
let host = env::var("HOST").expect("Please set host in .env");
|
||||
let port = env::var("PORT").expect("Please set port in .env");
|
||||
let host = std::env::var("HOST").expect("Please set host in .env");
|
||||
let port = std::env::var("PORT").expect("Please set port in .env");
|
||||
debug!("Binding server to {}:{}", host, port);
|
||||
server.bind(format!("{}:{}", host, port))?
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{error_handler::CustomError, db};
|
||||
use crate::{error_handler::ServiceError, db};
|
||||
use crate::schema::metars;
|
||||
use diesel::{prelude::*, sql_query};
|
||||
use log::{warn, trace};
|
||||
@@ -15,7 +15,7 @@ pub struct QualityControlFlags {
|
||||
|
||||
#[derive(Serialize, Deserialize, AsChangeset, Insertable)]
|
||||
#[diesel(table_name = metars)]
|
||||
pub struct Metar {
|
||||
pub struct InsertMetar {
|
||||
pub raw_text: String,
|
||||
pub station_id: String,
|
||||
pub observation_time: String,
|
||||
@@ -42,10 +42,10 @@ pub struct Metar {
|
||||
pub elevation_m: i32
|
||||
}
|
||||
|
||||
impl Metar {
|
||||
pub fn parse(input: String) -> Result<Vec<Self>, CustomError> {
|
||||
impl InsertMetar {
|
||||
pub fn parse(input: String) -> Result<Vec<Self>, ServiceError> {
|
||||
if input.is_empty() {
|
||||
return Err(CustomError::new(500, "Input is empty".to_string()))
|
||||
return Err(ServiceError::new(500, "Input is empty".to_string()))
|
||||
}
|
||||
|
||||
let mut reader = Reader::from_str(&input);
|
||||
@@ -109,7 +109,7 @@ impl Metar {
|
||||
|
||||
#[derive(Serialize, Deserialize, Queryable, QueryableByName)]
|
||||
#[diesel(table_name = metars)]
|
||||
pub struct Metars {
|
||||
pub struct QueryMetar {
|
||||
pub id: i32,
|
||||
pub raw_text: String,
|
||||
pub station_id: String,
|
||||
@@ -137,8 +137,8 @@ pub struct Metars {
|
||||
pub elevation_m: i32
|
||||
}
|
||||
|
||||
impl Metars {
|
||||
pub async fn get_all(icaos: String) -> Result<Vec<Self>, CustomError> {
|
||||
impl QueryMetar {
|
||||
pub async fn get_all(icaos: String) -> Result<Vec<Self>, ServiceError> {
|
||||
if icaos.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
@@ -146,12 +146,12 @@ impl Metars {
|
||||
let station_query: Vec<String> = station_icaos.iter().map(|icao| format!("'{}'", icao.to_string())).collect();
|
||||
|
||||
let mut conn = db::connection()?;
|
||||
let mut db_metars: Vec<Metars> = match sql_query(format!("SELECT DISTINCT ON (station_id) * FROM metars WHERE station_id IN ({}) ORDER BY station_id, observation_time DESC", station_query.join(","))).load(&mut conn) {
|
||||
let mut db_metars: Vec<QueryMetar> = match sql_query(format!("SELECT DISTINCT ON (station_id) * FROM metars WHERE station_id IN ({}) ORDER BY station_id, observation_time DESC", station_query.join(","))).load(&mut conn) {
|
||||
Ok(m) => m,
|
||||
Err(err) => return Err(CustomError { error_status_code: 500, error_message: format!("{}", err) })
|
||||
Err(err) => return Err(ServiceError { error_status_code: 500, error_message: format!("{}", err) })
|
||||
};
|
||||
|
||||
fn get_missing_metar_icaos(db_metars: &Vec<Metars>, station_icaos: Vec<&str>) -> Vec<String> {
|
||||
fn get_missing_metar_icaos(db_metars: &Vec<QueryMetar>, station_icaos: Vec<&str>) -> Vec<String> {
|
||||
let mut missing_metar_icaos: Vec<String> = vec![];
|
||||
let current_time = chrono::Local::now().naive_local().timestamp();
|
||||
let db_metars_set: HashSet<&str> = db_metars.iter().map(|icao| icao.station_id.as_str()).collect();
|
||||
@@ -182,10 +182,10 @@ impl Metars {
|
||||
trace!("Retrieving missing METAR data for {:?}", missing_icaos);
|
||||
let missing_icaos_string: Vec<String> = missing_icaos.iter().map(|icao| format!("'{}'", icao.to_string())).collect();
|
||||
let url = format!("https://beta.aviationweather.gov/cgi-bin/data/metar.php?ids={}&format=xml", missing_icaos_string.join(","));
|
||||
let metars: Vec<Metar> = match reqwest::get(url).await {
|
||||
let metars: Vec<InsertMetar> = match reqwest::get(url).await {
|
||||
Ok(r) => match r.text().await {
|
||||
Ok(r) => {
|
||||
match Metar::parse(r) {
|
||||
match InsertMetar::parse(r) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use crate::{error_handler::CustomError, db::Metadata};
|
||||
use crate::metars::Metars;
|
||||
use crate::{error_handler::ServiceError, db::Metadata};
|
||||
use crate::metars::QueryMetar;
|
||||
use actix_web::{get, web, HttpResponse, Responder};
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MetarsResponse {
|
||||
pub data: Vec<Metars>,
|
||||
pub data: Vec<QueryMetar>,
|
||||
pub meta: Metadata
|
||||
}
|
||||
|
||||
#[get("metars/{ids}")]
|
||||
async fn get_all(ids: web::Path<String>) -> impl Responder {
|
||||
let airports = match web::block(|| Ok::<_, CustomError>(async {Metars::get_all(ids.into_inner()).await}))
|
||||
let airports = match web::block(|| Ok::<_, ServiceError>(async {QueryMetar::get_all(ids.into_inner()).await}))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
diesel::table! {
|
||||
use postgis_diesel::sql_types::*;
|
||||
use diesel::sql_types::*;
|
||||
use postgis_diesel::sql_types::*;
|
||||
airports (icao) {
|
||||
icao -> Text,
|
||||
id -> Integer,
|
||||
@@ -43,3 +43,15 @@ diesel::table! {
|
||||
elevation_m -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::users::PgUserType;
|
||||
users (id) {
|
||||
id -> Uuid,
|
||||
first_name -> Text,
|
||||
last_name -> Text,
|
||||
user_type -> PgUserType,
|
||||
favorites -> Array<Text>
|
||||
}
|
||||
}
|
||||
|
||||
7
weather-service/src/users/mod.rs
Normal file
7
weather-service/src/users/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod model;
|
||||
mod routes;
|
||||
mod user_type;
|
||||
|
||||
pub use user_type::PgUserType;
|
||||
pub use model::*;
|
||||
pub use routes::init_routes;
|
||||
50
weather-service/src/users/model.rs
Normal file
50
weather-service/src/users/model.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::{future::Future, pin::Pin, sync::RwLock};
|
||||
|
||||
use actix_web::{dev::Payload, error::ErrorUnauthorized, web, Error, FromRequest, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use diesel::{query_builder::AsChangeset, prelude::Insertable};
|
||||
use log::warn;
|
||||
use crate::schema::users;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::user_type::UserType;
|
||||
|
||||
#[derive(Serialize, Deserialize, AsChangeset, Insertable)]
|
||||
#[diesel(table_name = users)]
|
||||
pub struct InsertUser {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
user_type: UserType,
|
||||
favorites: Vec<String>
|
||||
}
|
||||
|
||||
// impl FromRequest for InsertUser {
|
||||
// type Config = ();
|
||||
// type Error = Error;
|
||||
// type Future = Pin<Box<dyn Future<Output = Result<Self, Error>>>>;
|
||||
|
||||
// fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
|
||||
// let fut = Identity::from_request(req, pl);
|
||||
// let sessions: Option<&web::Data<RwLock<Sessions>>> = req.app_data();
|
||||
// if sessions.is_none() {
|
||||
// warn!("sessions is empty(none)!");
|
||||
// return Box::pin(async { Err(ErrorUnauthorized("unauthorized")) });
|
||||
// }
|
||||
// let sessions = sessions.unwrap().clone();
|
||||
// Box::pin(async move {
|
||||
// if let Some(identity) = fut.await?.identity() {
|
||||
// if let Some(user) = sessions
|
||||
// .read()
|
||||
// .unwrap()
|
||||
// .map
|
||||
// .get(&identity)
|
||||
// .map(|x| x.clone())
|
||||
// {
|
||||
// return Ok(user);
|
||||
// }
|
||||
// };
|
||||
|
||||
// Err(ErrorUnauthorized("unauthorized"))
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
51
weather-service/src/users/routes.rs
Normal file
51
weather-service/src/users/routes.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use actix_web::{get, post, delete, put, web, HttpResponse};
|
||||
|
||||
#[get("users")]
|
||||
async fn get() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[get("users/{id}")]
|
||||
async fn get_all() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[post("users")]
|
||||
async fn create() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[delete("users")]
|
||||
async fn delete() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[put("users")]
|
||||
async fn update() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[get("users/favorites")]
|
||||
async fn get_favorites() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[post("users/favorites")]
|
||||
async fn add_favorite() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[delete("users/favorites")]
|
||||
async fn delete_favorite() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
pub fn init_routes(config: &mut web::ServiceConfig) {
|
||||
config.service(get);
|
||||
config.service(create);
|
||||
config.service(delete);
|
||||
config.service(update);
|
||||
config.service(get_favorites);
|
||||
config.service(add_favorite);
|
||||
config.service(delete_favorite);
|
||||
}
|
||||
35
weather-service/src/users/user_type.rs
Normal file
35
weather-service/src/users/user_type.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::io::Write;
|
||||
|
||||
use diesel::{sql_types::SqlType, deserialize::{FromSqlRow, FromSql, self}, expression::AsExpression, serialize::{ToSql, Output, self, IsNull}, pg::{Pg, PgValue}};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(SqlType)]
|
||||
#[diesel(postgres_type(name = "User_Type"))]
|
||||
pub struct PgUserType;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, FromSqlRow, AsExpression, Eq)]
|
||||
#[diesel(sql_type = PgUserType)]
|
||||
pub enum UserType {
|
||||
Admin,
|
||||
User,
|
||||
}
|
||||
|
||||
impl ToSql<PgUserType, Pg> for UserType {
|
||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
|
||||
match *self {
|
||||
Self::Admin => out.write_all(b"admin")?,
|
||||
Self::User => out.write_all(b"user")?,
|
||||
}
|
||||
Ok(IsNull::No)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql<PgUserType, Pg> for UserType {
|
||||
fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
|
||||
match bytes.as_bytes() {
|
||||
b"admin" => Ok(Self::Admin),
|
||||
b"user" => Ok(Self::User),
|
||||
_ => Err("Unrecognized enum variant".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,15 @@ export default function MapTiles() {
|
||||
position={[airport.point.y, airport.point.x]}
|
||||
icon={icon(airport)}
|
||||
eventHandlers={{
|
||||
click: () => handleOpen(airport)
|
||||
click: () => {
|
||||
mapEvents.eachLayer((l) => {
|
||||
if (l.getTooltip() && l.isTooltipOpen()) {
|
||||
console.log('l', l);
|
||||
l.closeTooltip();
|
||||
}
|
||||
});
|
||||
handleOpen(airport);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{!isOpen && (
|
||||
|
||||
@@ -36,12 +36,16 @@ export default function MetarDialog({ airport, isOpen, onClose }: MetarDialogPro
|
||||
}
|
||||
|
||||
function windColor(metar: Metar | undefined) {
|
||||
if (Number(metar?.wind_speed_kt) <= 9) {
|
||||
return 'bg-green-300';
|
||||
} else if (Number(metar?.wind_speed_kt) > 9) {
|
||||
return 'bg-orange-300';
|
||||
} else if (Number(metar?.wind_speed_kt) > 12) {
|
||||
return 'bg-red-300';
|
||||
if (metar) {
|
||||
if (Number(metar.wind_speed_kt) <= 9) {
|
||||
return 'bg-green-300';
|
||||
} else if (Number(metar.wind_speed_kt) <= 12) {
|
||||
return 'bg-orange-300';
|
||||
} else {
|
||||
return 'bg-red-300';
|
||||
}
|
||||
} else {
|
||||
return 'gb-gray-100';
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -9,8 +9,8 @@ export default function Map({ className = '' }: { className?: string }) {
|
||||
<MapContainer
|
||||
center={[38.7209, -77.5133]}
|
||||
zoom={8}
|
||||
maxZoom={12}
|
||||
minZoom={1}
|
||||
maxZoom={14} // Zoomed in
|
||||
minZoom={3} // Zoomed out
|
||||
id='map-container'
|
||||
style={{ height: '94.5vh' }}
|
||||
className={`${className} overflow-y-hidden overflow-x-hidden`}
|
||||
|
||||
Reference in New Issue
Block a user