Compare commits

..

No commits in common. "master" and "openai-implementation" have entirely different histories.

27 changed files with 111 additions and 324 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ API/bin
API/obj API/obj
.idea .idea
/rust-backend/.env.example

View File

@ -1,32 +0,0 @@
using API.Models;
using API.Persistence.Repositories;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
using Newtonsoft.Json.Linq;
namespace API.Application.Users.Queries
{
public class QueryUsersByIds
{
private readonly IUserRepository _repository;
public QueryUsersByIds(IUserRepository repository)
{
_repository = repository;
}
public async Task<ActionResult<List<UserDTO>>> Handle(List<string> ids)
{
List<User> users = await _repository.QueryUsersByIdsAsync(ids);
if (users == null)
{
return new ConflictObjectResult(new { message = "No user on given Id" });
}
return new OkObjectResult(users.Select(user => new { id = user.Id, email = user.Email, username = user.Username, profilePicture = user.ProfilePicture, createdAt = user.CreatedAt }));
}
}
}

View File

@ -22,35 +22,36 @@ namespace API.Controllers
{ {
private readonly QueryAllUsers _queryAllUsers; private readonly QueryAllUsers _queryAllUsers;
private readonly QueryUserById _queryUserById; private readonly QueryUserById _queryUserById;
private readonly QueryUsersByIds _queryUsersByIds;
private readonly CreateUser _createUser; private readonly CreateUser _createUser;
private readonly UpdateUser _updateUser; private readonly UpdateUser _updateUser;
private readonly DeleteUser _deleteUser; private readonly DeleteUser _deleteUser;
private readonly LoginUser _loginUser; private readonly LoginUser _loginUser;
private readonly TokenHelper _tokenHelper; private readonly TokenHelper _tokenHelper;
private readonly IUserRepository _repository; private readonly IUserRepository _repository;
public UsersController( public UsersController(
QueryAllUsers queryAllUsers, QueryAllUsers queryAllUsers,
QueryUserById queryUserById, QueryUserById queryUserById,
QueryUsersByIds queryUsersByIds,
CreateUser createUser, CreateUser createUser,
UpdateUser updateUser, UpdateUser updateUser,
DeleteUser deleteUser, DeleteUser deleteUser,
LoginUser loginUser, LoginUser loginUser,
TokenHelper tokenHelper, TokenHelper tokenHelper,
IUserRepository repository IUserRepository repository
) )
{ {
_queryAllUsers = queryAllUsers; _queryAllUsers = queryAllUsers;
_queryUserById = queryUserById; _queryUserById = queryUserById;
_queryUsersByIds = queryUsersByIds;
_createUser = createUser; _createUser = createUser;
_updateUser = updateUser; _updateUser = updateUser;
_deleteUser = deleteUser; _deleteUser = deleteUser;
_loginUser = loginUser; _loginUser = loginUser;
_tokenHelper = tokenHelper; _tokenHelper = tokenHelper;
_repository = repository; _repository = repository;
} }
[HttpPost("login")] [HttpPost("login")]
@ -72,16 +73,9 @@ namespace API.Controllers
return await _queryUserById.Handle(id); return await _queryUserById.Handle(id);
} }
[HttpGet("UsersByIds")]
public async Task<ActionResult<List<UserDTO>>> GetUsersByIds(string userIds)
{
List<string> ids = userIds.Split(",").ToList();
return await _queryUsersByIds.Handle(ids);
}
[Authorize] [Authorize]
[HttpPut] [HttpPut]
public async Task<IActionResult> PutUser([FromForm] UpdateUserDTO UpdateUserDTO) public async Task<IActionResult> PutUser([FromForm ]UpdateUserDTO UpdateUserDTO)
{ {
return await _updateUser.Handle(UpdateUserDTO); return await _updateUser.Handle(UpdateUserDTO);
} }

View File

@ -8,7 +8,6 @@ namespace API.Persistence.Repositories
Task<bool> DeleteUserAsync(string id); Task<bool> DeleteUserAsync(string id);
Task<List<User>> QueryAllUsersAsync(); Task<List<User>> QueryAllUsersAsync();
Task<User> QueryUserByIdAsync(string id); Task<User> QueryUserByIdAsync(string id);
Task<List<User>> QueryUsersByIdsAsync(List<string> ids);
Task<User> QueryUserByEmailAsync(string email); Task<User> QueryUserByEmailAsync(string email);
Task<bool> UpdateUserAsync(User user); Task<bool> UpdateUserAsync(User user);
Task<User> QueryUserByRefreshTokenAsync(string refreshToken); Task<User> QueryUserByRefreshTokenAsync(string refreshToken);

View File

@ -1,6 +1,5 @@
using API.Models; using API.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages; using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
namespace API.Persistence.Repositories namespace API.Persistence.Repositories
@ -26,18 +25,6 @@ namespace API.Persistence.Repositories
} }
} }
public async Task<List<User>> QueryUsersByIdsAsync(List<string> ids)
{
try
{
return _context.Users.Where(user => ids.Contains(user.Id)).ToList();
}
catch (Exception)
{
return [];
}
}
public async Task<string> CreateUserAsync(User user) public async Task<string> CreateUserAsync(User user)
{ {
try try

View File

@ -43,7 +43,7 @@ namespace API
builder.Services.AddScoped<DeleteUser>(); builder.Services.AddScoped<DeleteUser>();
builder.Services.AddScoped<LoginUser>(); builder.Services.AddScoped<LoginUser>();
builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<QueryUsersByIds>();
IConfiguration Configuration = builder.Configuration; IConfiguration Configuration = builder.Configuration;

View File

@ -8,7 +8,6 @@ Environment=PATH=$PATH:/home/reimar/.dotnet
Environment=DEFAULT_CONNECTION="Data Source=/home/reimar/skantravels/database.sqlite3" Environment=DEFAULT_CONNECTION="Data Source=/home/reimar/skantravels/database.sqlite3"
ExecStartPre=/home/reimar/skantravels/efbundle ExecStartPre=/home/reimar/skantravels/efbundle
ExecStart=/home/reimar/skantravels/API --urls=http://0.0.0.0:5001 ExecStart=/home/reimar/skantravels/API --urls=http://0.0.0.0:5001
WorkingDirectory=/home/reimar/skantravels
Type=simple Type=simple
[Install] [Install]

View File

@ -151,7 +151,7 @@ KC - Kundechef
## Torsdag ## Torsdag
**SM(Alexander):** Samarbejdede med Reimar omkring Refresh-tokens, mest med ide-fasen **SM(Alexander):**
**PO(Reimar):** Begyndt på implementering af refresh tokens, fikset en fejl med en openssl dependency til vores release-flow **PO(Reimar):** Begyndt på implementering af refresh tokens, fikset en fejl med en openssl dependency til vores release-flow
@ -159,7 +159,7 @@ KC - Kundechef
## Fredag ## Fredag
**SM(Alexander):** Holdt kundemøder **SM(Alexander):**
**PO(Reimar):** Holdt kundemøder **PO(Reimar):** Holdt kundemøder
@ -177,7 +177,7 @@ KC - Kundechef
## Tirsdag ## Tirsdag
**PO(Alexander):** Pair-programmede med Reimar om nedenstående features **PO(Alexander):**
**KC(Reimar):** Implementeret reviews i rust backenden, fået vist dem som location pins på kortet **KC(Reimar):** Implementeret reviews i rust backenden, fået vist dem som location pins på kortet
@ -185,7 +185,7 @@ KC - Kundechef
## Onsdag ## Onsdag
**PO(Alexander):** Pair-programmede med Reimar om nedenstående features **PO(Alexander):**
**KC(Reimar):** Implementeret review-liste når man klikker ind på én **KC(Reimar):** Implementeret review-liste når man klikker ind på én
@ -193,7 +193,7 @@ KC - Kundechef
## Torsdag ## Torsdag
**PO(Alexander):** Pair-programmede med Reimar om nedenstående features **PO(Alexander):**
**KC(Reimar):** Lavet create review-side, tilføjet stjerner til review-liste **KC(Reimar):** Lavet create review-side, tilføjet stjerner til review-liste
@ -201,7 +201,7 @@ KC - Kundechef
## Fredag ## Fredag
**PO(Alexander):** Hjalp med kundemøder **PO(Alexander):**
**KC(Reimar):** Holdt kundemøder **KC(Reimar):** Holdt kundemøder
@ -211,59 +211,49 @@ KC - Kundechef
## Mandag ## Mandag
**KC(Alexander):** Aftalte udviklermøde **KC(Alexander):**
**SM(Reimar):** Påbegyndt implementation af AWS image upload i Rust **SM(Reimar):**
**PO(Philip):** Fik billede upload til at virke efter reimar sagde at jeg skulle sende fra flutter som en form i stedet for JSON **PO(Philip):**
## Tirsdag ## Tirsdag
**KC(Alexander):** **KC(Alexander):**
**SM(Reimar):** Færdiggjort image uploads i Rust **SM(Reimar):**
**PO(Philip):** intet med kundegruppen. Fik billede til at vise icon hvis der ikke var billede. og det nye billede blev vist efter man havde savet **PO(Philip):**
## Onsdag ## Onsdag
**KC(Alexander):** Afholdte udviklermøde **KC(Alexander):**
**SM(Reimar):** Tilføjet billeder til reviews, lavet backend til at vise bruger-information på review-liste **SM(Reimar):**
**PO(Philip):** Kiggede på openai. planlage møde med kundegruppe **PO(Philip):**
## Torsdag ## Torsdag
**KC(Alexander):** **KC(Alexander):**
**SM(Reimar):** Holdt møde, fikset min cykel **SM(Reimar):**
**PO(Philip):** fik overblik over hvad vi hver især skulle vise til kundegruppe mødet. openai blev ikke færdig **PO(Philip):**
## Fredag ## Fredag
**KC(Alexander):** **KC(Alexander):**
**SM(Reimar):** Lavet frontend til at vise bruger-info på review-liste, implementeret sletning af reviews, fikset nogle fejl ved image upload **SM(Reimar):**
**PO(Philip):** Møde med kundegruppen hvor vi viste alt funktionallitet, dog ikke at det virkede fuldendt **PO(Philip):**
# Uge 38 ## Fredag
## Mandag **KC(Alexander):**
**SM(Alexander):** **SM(Reimar):**
**PO(Reimar):** **PO(Philip):**
**KC(Philip):** openai blev færdiglavet med guidebook
## Tirsdag
**SM(Alexander):**
**PO(Reimar):**
**KC(Philip):** for overblik over hvad vi skal sige til eksamen

View File

@ -1,3 +1,3 @@
# Case beskrivelse # Case beskrivelse
PDF[Case](https://edumercantec.sharepoint.com/:b:/s/24Q3H4-AppprogrammeringServerogmetodik/EbZ4bfQ_4xhJlUp6dVJMmbEBq7saaqfJJ8RuHLuPnVe6hw?e=yQoxXO) Som kunde skal I beskrive det product som I ønsker! For eksempler se her - [Notion](https://mercantec.notion.site/Casebeskrivelse-og-Kravspec-60eb806216074896ae1b3c7f14d9b2b6?pvs=4)

View File

@ -1,22 +0,0 @@
## Setting up
### C# backend
In the `API` folder, copy `appsettings.example.json` to `appsettings.json` and fill out the values. `AccessKey` and `SecretKey` are for the Cloudflare R2 service.
Run `dotnet ef database update` and then `dotnet run`.
### Rust backend
In the `rust-backend` folder, copy `.env.example` to `.env` and fill out the values. Make sure the JWT secret is the same on both backends.
Rust can be installed from <https://rustup.rs>. After installation, run `rustup default stable` in the terminal.
To start the backend, run `cargo run`.
### Flutter
In the `Mobile` folder, copy `environment.example.json` to `environment.json` and fill out the values. Also do this with `.env.example`, copying it into `.env`.
Run `flutter run --dart-define-from-file environment.json`.

View File

@ -3,7 +3,7 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application <application
android:label="SkanTravels" android:label="mobile"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,4 +0,0 @@
#!/bin/sh
java -version
flutter build apk --split-per-abi --no-shrink --dart-define-from-file environment.prod.json

View File

@ -1,4 +0,0 @@
{
"AUTH_SERVICE_HOST": "https://skantravels.reim.ar",
"APP_SERVICE_HOST": "https://skantravels.reim.ar"
}

View File

@ -20,11 +20,7 @@ import 'services/api.dart' as api;
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env"); await dotenv.load(fileName: ".env");
// Refresh JWT on startup
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
if (prefs.getString("token") != null && prefs.getString("refresh-token") != null) { if (prefs.getString("token") != null && prefs.getString("refresh-token") != null) {
final token = await api.request(null, api.ApiService.auth, "POST", "/RefreshToken", {'refreshToken': prefs.getString("refresh-token")}); final token = await api.request(null, api.ApiService.auth, "POST", "/RefreshToken", {'refreshToken': prefs.getString("refresh-token")});
@ -254,47 +250,38 @@ class _MyHomePageState extends State<MyHomePage> {
}); });
} }
Future<void> _onSearch() async { Future<void> _onSearch() async {
final http.Response response = await http.get( final http.Response response = await http.get(
Uri.parse('https://nominatim.openstreetmap.org/search.php?q=${searchBarInput.text}&format=jsonv2'), Uri.parse('https://nominatim.openstreetmap.org/search.php?q=${searchBarInput.text}&format=jsonv2'),
headers: {'User-Agent': 'SkanTravels/1.0'} headers: {'User-Agent': 'SkanTravels/1.0'}
); );
final dynamic location = jsonDecode(response.body); final dynamic location = jsonDecode(response.body);
// Move the map to the center of the first search result // Move the map to the center of the first search result
_mapController.move( _mapController.move(
LatLng(double.parse(location[0]['lat']), double.parse(location[0]['lon'])), LatLng(double.parse(location[0]['lat']), double.parse(location[0]['lon'])),
8 8
); );
// Extract the bounding box and convert to LatLng // Extract the bounding box and convert to LatLng
final List<dynamic> boundingBox = location[0]['boundingbox']; final List<dynamic> boundingBox = location[0]['boundingbox'];
_getOpenStreetMapData(LatLng(double.parse(boundingBox[0]), double.parse(boundingBox[2])), LatLng(double.parse(boundingBox[1]), double.parse(boundingBox[3]))); _getOpenStreetMapData(LatLng(double.parse(boundingBox[0]), double.parse(boundingBox[2])), LatLng(double.parse(boundingBox[1]), double.parse(boundingBox[3])));
} }
Future<void> _getCurrentLocation() async { Future<void> _getCurrentLocation() async {
LocationPermission? permission; LocationPermission permission = await Geolocator.checkPermission();
try { if(permission != LocationPermission.always || permission != LocationPermission.whileInUse){
permission = await Geolocator.requestPermission();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error retrieving location: $e')));
return;
} }
else{
if (permission != LocationPermission.always && permission != LocationPermission.whileInUse) { await Geolocator.requestPermission();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Location permission denied')));
return;
} }
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high); Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
_mapController.move(LatLng(position.latitude, position.longitude), 10); _mapController.move(LatLng(position.latitude, position.longitude), 10);
setState(() { setState(() {
_userPosition = LatLng(position.latitude, position.longitude); _userPosition = LatLng(position.latitude, position.longitude);
}); });
LatLngBounds bounds = _mapController.camera.visibleBounds; LatLngBounds bounds = _mapController.camera.visibleBounds;
_getOpenStreetMapData(LatLng(bounds.southWest.latitude, bounds.southWest.longitude),LatLng(bounds.northEast.latitude, bounds.northEast.longitude)); _getOpenStreetMapData(LatLng(bounds.southWest.latitude, bounds.southWest.longitude),LatLng(bounds.northEast.latitude, bounds.northEast.longitude));

View File

@ -1,9 +1,6 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile/base/sidemenu.dart'; import 'package:mobile/base/sidemenu.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'models.dart' as models; import 'models.dart' as models;
import 'services/api.dart' as api; import 'services/api.dart' as api;
@ -15,147 +12,64 @@ class ReviewListPage extends StatefulWidget {
} }
class _ReviewListState extends State<ReviewListPage> { class _ReviewListState extends State<ReviewListPage> {
List<models.User> _users = [];
List<models.Review> _reviews = [];
String? _currentUserId;
models.User? _getReviewUser(models.Review review) {
try {
return _users.firstWhere((user) => user.id == review.userId);
} catch(e) {
return null;
}
}
void _confirmDeleteReview(models.Review review) {
showDialog(context: context, builder: (BuildContext context) =>
AlertDialog(
title: const Text('Delete review'),
content: const Text('Are you sure you want to delete this review?'),
actions: [
TextButton(child: const Text('Cancel'), onPressed: () => Navigator.pop(context)),
TextButton(child: const Text('Delete', style: TextStyle(color: Colors.red)), onPressed: () => _deleteReview(review)),
]
)
);
}
void _deleteReview(models.Review review) async {
Navigator.pop(context);
final response = await api.request(context, api.ApiService.app, 'DELETE', '/reviews/${review.id}', null);
if (response == null) return;
setState(() {
_reviews = _reviews.where((r) => r.id != review.id).toList();
});
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Review deleted successfully')));
}
@override
void didChangeDependencies() async {
super.didChangeDependencies();
final prefs = await SharedPreferences.getInstance();
_currentUserId = prefs.getString('id');
final arg = ModalRoute.of(context)!.settings.arguments as models.ReviewList;
_reviews = arg.reviews;
if (_reviews.isEmpty || !mounted) {
return;
}
final userIds = _reviews.map((review) => review.userId).toSet().toList();
final response = await api.request(context, api.ApiService.auth, 'GET', '/api/Users/UsersByIds?userIds=' + userIds.join(','), null);
if (response == null) return;
setState(() {
_users = (jsonDecode(response) as List<dynamic>).map((user) => models.User.fromJson(user)).toList();
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final arg = ModalRoute.of(context)!.settings.arguments as models.ReviewList; final arg = ModalRoute.of(context)!.settings.arguments as models.ReviewList;
final reviews = arg.reviews;
final place = arg.place; final place = arg.place;
return SideMenu( return SideMenu(
selectedIndex: -1, selectedIndex: -1,
body: Scaffold( body: Scaffold(
backgroundColor: const Color(0xFFF9F9F9), backgroundColor: const Color(0xFFF9F9F9),
body: _reviews.isEmpty body: SingleChildScrollView(child: Container(
? const Center(child: Text('No reviews yet. Be the first to review this place')) decoration: const BoxDecoration(color: Color(0xFFF9F9F9)),
: SingleChildScrollView(child: Container( width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(color: Color(0xFFF9F9F9)), padding: const EdgeInsets.all(20.0),
width: MediaQuery.of(context).size.width, child: Column(children:
padding: const EdgeInsets.all(20.0), reviews.map((review) => Container(
child: Column(children: [ width: double.infinity,
for (final review in _reviews) padding: const EdgeInsets.all(20),
Container( margin: const EdgeInsets.only(bottom: 10),
width: double.infinity, decoration: const BoxDecoration(
padding: const EdgeInsets.all(20), boxShadow: [
margin: const EdgeInsets.only(bottom: 10), BoxShadow(
decoration: const BoxDecoration( color: Color(0x20000000),
boxShadow: [ offset: Offset(0,1),
BoxShadow( blurRadius: 4,
color: Color(0x20000000),
offset: Offset(0,1),
blurRadius: 4,
),
],
color: Colors.white,
), ),
child: Row( ],
crossAxisAlignment: CrossAxisAlignment.start, color: Colors.white,
children: [ ),
Padding( child: Row(
padding: const EdgeInsets.only(top: 3), crossAxisAlignment: CrossAxisAlignment.start,
child: children: [
_getReviewUser(review)?.profilePicture.isNotEmpty == true const Padding(
? ClipOval( padding: EdgeInsets.only(top: 3),
child: Image( child: Icon(Icons.rate_review, color: Colors.purple, size: 36),
image: NetworkImage(_getReviewUser(review)!.profilePicture),
height: 36,
width: 36,
fit: BoxFit.cover,
),
)
: const Icon(
Icons.account_circle,
size: 36,
color: Colors.grey,
)
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Expanded(child: Text(review.title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24))),
if (review.userId == _currentUserId) IconButton(onPressed: () => _confirmDeleteReview(review), icon: const Icon(Icons.delete, color: Colors.grey)),
]),
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),
]),
const SizedBox(height: 10),
Text('Submitted by ' + (_getReviewUser(review)?.username ?? ''), style: const TextStyle(color: Colors.grey, fontSize: 12)),
],
),
),
],
), ),
), const SizedBox(width: 20),
]), Expanded(
)), child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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),
]),
],
),
),
],
),
)).toList(),
),
)),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () async { onPressed: () async {
if (!await api.isLoggedIn(context)) { if (!await api.isLoggedIn(context)) {
@ -164,7 +78,7 @@ class _ReviewListState extends State<ReviewListPage> {
} }
final review = await Navigator.pushNamed(context, '/create-review', arguments: place) as models.Review?; final review = await Navigator.pushNamed(context, '/create-review', arguments: place) as models.Review?;
if (review != null) _reviews.add(review); if (review != null) reviews.add(review);
}, },
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
focusColor: Colors.blueGrey, focusColor: Colors.blueGrey,

View File

@ -13,8 +13,6 @@ enum ApiService {
} }
Future<String?> request(BuildContext? context, ApiService service, String method, String path, dynamic body) async { Future<String?> request(BuildContext? context, ApiService service, String method, String path, dynamic body) async {
var debug = '$method $path\n $body\n';
final messenger = context != null ? ScaffoldMessenger.of(context) : null; final messenger = context != null ? ScaffoldMessenger.of(context) : null;
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@ -49,22 +47,18 @@ Future<String?> request(BuildContext? context, ApiService service, String method
); );
} }
} catch (e) { } catch (e) {
debugPrint(e.toString());
messenger?.showSnackBar(const SnackBar(content: Text('Unable to connect to server'))); messenger?.showSnackBar(const SnackBar(content: Text('Unable to connect to server')));
debug += 'FAILED\n $e';
debugPrint(debug);
return null; return null;
} }
debug += 'HTTP ${response.statusCode}\n ${response.body}';
debugPrint(debug);
if (response.statusCode < 200 || response.statusCode >= 300) { if (response.statusCode < 200 || response.statusCode >= 300) {
try { try {
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
messenger?.showSnackBar(SnackBar(content: Text(json['message'] ?? json['title']))); messenger?.showSnackBar(SnackBar(content: Text(json['message'] ?? json['title'])));
debugPrint('API error: ' + json['message']);
} catch (e) { } catch (e) {
debugPrint(e.toString());
messenger?.showSnackBar(SnackBar(content: Text('Something went wrong (HTTP ${response.statusCode})'))); messenger?.showSnackBar(SnackBar(content: Text('Something went wrong (HTTP ${response.statusCode})')));
} }
return null; return null;

View File

@ -210,10 +210,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "3.0.2"
flutter_map: flutter_map:
dependency: "direct main" dependency: "direct main"
description: description:
@ -428,10 +428,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "3.0.0"
lists: lists:
dependency: transitive dependency: transitive
description: description:

View File

@ -55,7 +55,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^4.0.0 flutter_lints: ^3.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View File

@ -6,8 +6,3 @@ Vi vil udvikle en TuristApp for SkanTravles, som gør brug af telefonens sensore
[Kravspecifikation](Docs/kravspec.md) [Kravspecifikation](Docs/kravspec.md)
[Logbog](Docs/SCRUM%20Logbog.md) [Logbog](Docs/SCRUM%20Logbog.md)
[Setup guide](Docs/setup.md)
[Figma design](https://www.figma.com/design/r5EkReHw6NVC3gd9DeIc0P/Gruppe-6---SkanTravels?node-id=0-1&t=FxBQswcVrzRnhR2T-1)

View File

@ -1,8 +0,0 @@
JWT_SECRET=DenHerMåAldrigVæreOffentligKunIDetteDemoProjekt
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_ENDPOINT_URL=
AWS_REGION=
R2_BUCKET_NAME=
R2_BUCKET_URL=

View File

@ -6,7 +6,6 @@ After=network.target
Environment=RUST_BACKEND_PORT=5002 Environment=RUST_BACKEND_PORT=5002
Environment=RUST_BACKEND_DB=/home/reimar/skantravels/database-rust.sqlite3 Environment=RUST_BACKEND_DB=/home/reimar/skantravels/database-rust.sqlite3
ExecStart=/home/reimar/skantravels/skantravels ExecStart=/home/reimar/skantravels/skantravels
WorkingDirectory=/home/reimar/skantravels
Type=simple Type=simple
[Install] [Install]

View File

@ -219,9 +219,7 @@ async fn create_review(auth: AuthorizedUser, data: web::Data<AppData>, input: we
content: input.content.clone(), content: input.content.clone(),
rating: input.rating.clone(), rating: input.rating.clone(),
image_id: input.image_id, image_id: input.image_id,
image: input.image_id.and_then(|image_id| { image: None,
db.query_row("SELECT * FROM images WHERE id = :id LIMIT 1", &[(":id", &image_id.to_string())], |row| Image::from_row(row)).ok()
}),
}), }),
Err(_) => HttpResponse::InternalServerError().finish(), Err(_) => HttpResponse::InternalServerError().finish(),
} }
@ -330,7 +328,7 @@ async fn main() -> std::io::Result<()> {
.app_data(web::Data::new(AppData { .app_data(web::Data::new(AppData {
database: conn.clone(), database: conn.clone(),
})) }))
.app_data(web::PayloadConfig::new(30 * 1024 * 1024)) .app_data(web::PayloadConfig::new(8_388_608))
.service(healthcheck) .service(healthcheck)
.service(authorized) .service(authorized)
.service(favorites) .service(favorites)