Add review images to review list
Co-authored-by: Reimar <mail@reim.ar>
This commit is contained in:
parent
4d39e40648
commit
ebf81906ec
@ -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": ""
|
||||
}
|
||||
|
@ -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<String, dynamic> json) {
|
||||
return Review(
|
||||
@ -48,6 +49,7 @@ class Review {
|
||||
json['title'],
|
||||
json['content'],
|
||||
json['rating'],
|
||||
json['image'] != null ? Image.fromJson(json['image']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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<ReviewListPage> {
|
||||
@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<ReviewListPage> {
|
||||
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<ReviewListPage> {
|
||||
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<ReviewListPage> {
|
||||
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,
|
||||
|
@ -473,7 +473,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
path:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
|
1
rust-backend/Cargo.lock
generated
1
rust-backend/Cargo.lock
generated
@ -2262,7 +2262,6 @@ dependencies = [
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"aws-config",
|
||||
"aws-credential-types",
|
||||
"aws-sdk-s3",
|
||||
"base64 0.22.1",
|
||||
"dotenvy",
|
||||
|
@ -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"] }
|
||||
|
@ -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<AppData>, path: we
|
||||
}
|
||||
|
||||
#[get("/reviews")]
|
||||
async fn reviews(data: web::Data<AppData>) -> impl Responder {
|
||||
async fn get_reviews(data: web::Data<AppData>) -> 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<Vec<Review>> {
|
||||
Some(
|
||||
db.prepare("SELECT * FROM reviews").ok()?
|
||||
fn fetch_reviews(db: MutexGuard<'_, rusqlite::Connection>) -> Option<Vec<Review>> {
|
||||
let reviews: Vec<Review> = db.prepare("SELECT * FROM reviews").ok()?
|
||||
.query_map([], |row| Review::from_row(row))
|
||||
.ok()?
|
||||
.map(|rev| rev.unwrap())
|
||||
.collect()
|
||||
)
|
||||
.collect();
|
||||
|
||||
let review_ids = reviews.clone()
|
||||
.into_iter()
|
||||
.map(|r| r.id.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
let images: Vec<Image> = 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<AppData>, 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<AppData>, 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<AppData>, 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)
|
||||
|
@ -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<i64>,
|
||||
pub image: Option<Image>,
|
||||
}
|
||||
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user