diff --git a/API/appsettings.example.json b/API/appsettings.example.json index 10f68b8..1c01677 100644 --- a/API/appsettings.example.json +++ b/API/appsettings.example.json @@ -1,9 +1,18 @@ { + "JwtSettings": { + "Issuer": "Flutter-SkanTravels", + "Audience": "Mercantec-Elever", + "Key": "DenHerMåAldrigVæreOffentligKunIDetteDemoProjekt" + }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "ConnectionStrings": { + "DefaultConnection": "Data Source=database.sqlite3" + }, + "AccessKey": "", + "SecretKey": "" } diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart index 3e3b8a2..fbb82bb 100644 --- a/Mobile/lib/models.dart +++ b/Mobile/lib/models.dart @@ -34,8 +34,9 @@ class Review { String title; String content; int rating; + Image? image; - Review(this.id, this.userId, this.lat, this.lng, this.place_name, this.place_description, this.title, this.content, this.rating); + Review(this.id, this.userId, this.lat, this.lng, this.place_name, this.place_description, this.title, this.content, this.rating, this.image); factory Review.fromJson(Map json) { return Review( @@ -48,6 +49,7 @@ class Review { json['title'], json['content'], json['rating'], + json['image'] != null ? Image.fromJson(json['image']) : null, ); } } diff --git a/Mobile/lib/reviewlist.dart b/Mobile/lib/reviewlist.dart index 9577349..4dc2d1b 100644 --- a/Mobile/lib/reviewlist.dart +++ b/Mobile/lib/reviewlist.dart @@ -1,7 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:mobile/base/sidemenu.dart'; -import 'models.dart'; +import 'models.dart' as models; import 'api.dart' as api; class ReviewListPage extends StatefulWidget { @@ -14,14 +14,14 @@ class ReviewListPage extends StatefulWidget { class _ReviewListState extends State { @override Widget build(BuildContext context) { - final arg = ModalRoute.of(context)!.settings.arguments as ReviewList; + final arg = ModalRoute.of(context)!.settings.arguments as models.ReviewList; final reviews = arg.reviews; final place = arg.place; return SideMenu( selectedIndex: -1, body: Scaffold( - backgroundColor: Color(0xFFF9F9F9), + backgroundColor: const Color(0xFFF9F9F9), body: SingleChildScrollView(child: Container( decoration: const BoxDecoration(color: Color(0xFFF9F9F9)), width: MediaQuery.of(context).size.width, @@ -46,7 +46,7 @@ class _ReviewListState extends State { children: [ const Padding( padding: EdgeInsets.only(top: 3), - child: Icon(Icons.radio, color: Colors.purple, size: 36), + child: Icon(Icons.rate_review, color: Colors.purple, size: 36), ), const SizedBox(width: 20), Expanded( @@ -56,6 +56,8 @@ class _ReviewListState extends State { Text(review.title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), Text(review.content), const SizedBox(height: 10), + if (review.image != null) Image.network(review.image!.imageUrl, height: 200,), + if (review.image != null) const SizedBox(height: 15), Row(children: [ for (var i = 0; i < review.rating; i++) const Icon(Icons.star, color: Colors.yellow), for (var i = review.rating; i < 5; i++) const Icon(Icons.star_border), @@ -75,7 +77,7 @@ class _ReviewListState extends State { return; } - final review = await Navigator.pushNamed(context, '/create-review', arguments: place) as Review?; + final review = await Navigator.pushNamed(context, '/create-review', arguments: place) as models.Review?; if (review != null) reviews.add(review); }, backgroundColor: Colors.blue, diff --git a/Mobile/pubspec.lock b/Mobile/pubspec.lock index a246c0b..f388662 100644 --- a/Mobile/pubspec.lock +++ b/Mobile/pubspec.lock @@ -473,7 +473,7 @@ packages: source: hosted version: "1.0.6" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" diff --git a/rust-backend/Cargo.lock b/rust-backend/Cargo.lock index 1f3a795..bea8299 100644 --- a/rust-backend/Cargo.lock +++ b/rust-backend/Cargo.lock @@ -2262,7 +2262,6 @@ dependencies = [ "actix-utils", "actix-web", "aws-config", - "aws-credential-types", "aws-sdk-s3", "base64 0.22.1", "dotenvy", diff --git a/rust-backend/Cargo.toml b/rust-backend/Cargo.toml index 606ac55..1df9461 100644 --- a/rust-backend/Cargo.toml +++ b/rust-backend/Cargo.toml @@ -19,4 +19,3 @@ tokio = { version = "1", features = ["full"] } refinery = { version = "0.8.14", features = ["rusqlite"] } rusqlite = { version = "0.31", features = ["bundled"] } reqwest = { version = "0.11.16", features = ["blocking", "json"] } -aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] } diff --git a/rust-backend/src/main.rs b/rust-backend/src/main.rs index 612bceb..004ddbe 100644 --- a/rust-backend/src/main.rs +++ b/rust-backend/src/main.rs @@ -8,10 +8,7 @@ use models::{Favorite, Review, Image}; use serde::Deserialize; use actix_web::web::Bytes; use aws_sdk_s3::primitives::ByteStream; -use aws_sdk_s3::config::Region; -use rusqlite::types::Null; use env_logger; -use aws_sdk_s3::config::Credentials; mod embedded { use refinery::embed_migrations; @@ -117,23 +114,43 @@ async fn delete_favorite(auth: AuthorizedUser, data:web::Data, path: we } #[get("/reviews")] -async fn reviews(data: web::Data) -> impl Responder { +async fn get_reviews(data: web::Data) -> impl Responder { let db = data.database.lock().unwrap(); - match get_reviews(db) { + match fetch_reviews(db) { Some(reviews) => HttpResponse::Ok().insert_header(("Content-Type", "application/json; charset=utf-8")).json(reviews), None => HttpResponse::InternalServerError().finish(), } } -fn get_reviews(db: MutexGuard<'_, rusqlite::Connection>) -> Option> { - Some( - db.prepare("SELECT * FROM reviews").ok()? - .query_map([], |row| Review::from_row(row)) - .ok()? - .map(|rev| rev.unwrap()) - .collect() - ) +fn fetch_reviews(db: MutexGuard<'_, rusqlite::Connection>) -> Option> { + let reviews: Vec = db.prepare("SELECT * FROM reviews").ok()? + .query_map([], |row| Review::from_row(row)) + .ok()? + .map(|rev| rev.unwrap()) + .collect(); + + let review_ids = reviews.clone() + .into_iter() + .map(|r| r.id.to_string()) + .collect::>() + .join(","); + + let images: Vec = db.prepare(&format!("SELECT * FROM images WHERE id IN ({})", review_ids)).ok()? + .query_map([], |row| Image::from_row(row)) + .ok()? + .map(|img| img.unwrap()) + .collect(); + + Some(reviews.into_iter().map(|r| { + let mut review = r.clone(); + + if review.image_id.is_some() { + review.image = images.clone().into_iter().find(|img| img.id == review.image_id.unwrap()); + } + + return review; + }).collect()) } #[derive(Deserialize)] @@ -201,6 +218,8 @@ async fn create_review(auth: AuthorizedUser, data: web::Data, input: we title: input.title.clone(), content: input.content.clone(), rating: input.rating.clone(), + image_id: input.image_id, + image: None, }), Err(_) => HttpResponse::InternalServerError().finish(), } @@ -241,8 +260,6 @@ async fn create_image(auth: AuthorizedUser, data: web::Data, bytes: Byt let db = data.database.lock().unwrap(); let config = aws_config::load_from_env().await; - println!("{:?}", config); - let s3_config = aws_sdk_s3::config::Builder::from(&config) .force_path_style(true) .build(); @@ -264,7 +281,7 @@ async fn create_image(auth: AuthorizedUser, data: web::Data, bytes: Byt return HttpResponse::InternalServerError().finish(); } - let image_url = format!("{}/{}/{}", bucket_url, bucket_name, query.file_name); + let image_url = format!("{}/{}", bucket_url, query.file_name); match db.execute( "INSERT INTO images (user_id, image_url) VALUES (:user_id, :image_url)", @@ -311,12 +328,13 @@ async fn main() -> std::io::Result<()> { .app_data(web::Data::new(AppData { database: conn.clone(), })) + .app_data(web::PayloadConfig::new(8_388_608)) .service(healthcheck) .service(authorized) .service(favorites) .service(create_favorite) .service(delete_favorite) - .service(reviews) + .service(get_reviews) .service(create_review) .service(delete_review) .service(create_image) diff --git a/rust-backend/src/models.rs b/rust-backend/src/models.rs index 7b3b1e0..6b5f7a6 100644 --- a/rust-backend/src/models.rs +++ b/rust-backend/src/models.rs @@ -25,7 +25,7 @@ impl Favorite { } } -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub struct Review { pub id: i64, pub user_id: String, @@ -36,6 +36,8 @@ pub struct Review { pub title: String, pub content: String, pub rating: i64, + pub image_id: Option, + pub image: Option, } impl Review { @@ -50,11 +52,13 @@ impl Review { title: row.get("title")?, content: row.get("content")?, rating: row.get("rating")?, + image_id: row.get("image_id").ok(), + image: None, }) } } -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub struct Image { pub id: i64, pub user_id: String,