forked from ReiMerc/skantravels
Compare commits
3 Commits
46f9535b4a
...
4539d48c29
Author | SHA1 | Date | |
---|---|---|---|
4539d48c29 | |||
5210c48566 | |||
0c05e26133 |
@ -1,11 +1,11 @@
|
|||||||
| | Scrum Master | Product Owner | Kundechef |
|
| | Scrum Master | Product Owner | Kundechef |
|
||||||
| ---------- | ------------ | ------------- | --------- |
|
| ---------- | ------------ | ------------- | --------- |
|
||||||
| **Uge 32** | Alexander | Reimar | Philip |
|
| **Uge 32** | Alexander | Reimar | Phillip |
|
||||||
| **Uge 33** | Philip | Alexander | Reimar |
|
| **Uge 33** | Phillip | Alexander | Reimar |
|
||||||
| **Uge 34** | Reimar | Philip | Alexander |
|
| **Uge 34** | Reimar | Phillip | Alexander |
|
||||||
| **Uge 35** | Alexander | Reimar | Philip |
|
| **Uge 35** | Alexander | Reimar | Phillip |
|
||||||
| **Uge 36** | Philip | Alexander | Reimar |
|
| **Uge 36** | Phillip | Alexander | Reimar |
|
||||||
| **Uge 37** | Reimar | Philip | Alexander |
|
| **Uge 37** | Reimar | Phillip | Alexander |
|
||||||
|
|
||||||
# Daglig Logbog Uge 32
|
# Daglig Logbog Uge 32
|
||||||
|
|
||||||
@ -66,59 +66,17 @@ KC - Kundechef
|
|||||||
|
|
||||||
## Torsdag
|
## Torsdag
|
||||||
|
|
||||||
**PO(Alexander):** Manglende pga. sygdom
|
**PO(Alexander):**
|
||||||
|
|
||||||
**KC(Reimar):** Implementeret JWT verifikation i rust-backenden
|
**KC(Reimar):**
|
||||||
|
|
||||||
**SM(Philip):** Var med til et møde sammen med reimer da programmeringsgruppen skulle bruge hjælp. Tilføjede selv authorization på swagger appikationen
|
**SM(Philip):** Var med til et møde sammen med reimer da programmeringsgruppen skulle bruge hjælp. Tilføjede selv authorization på swagger appikationen
|
||||||
|
|
||||||
## Fredag
|
## Fredag
|
||||||
|
|
||||||
**PO(Alexander):** Manglende pga. kommunemøde
|
**PO(Alexander):**
|
||||||
|
|
||||||
**KC(Reimar):** Lavet model og routes til oprettelse/sletning af favoritter i rust
|
**KC(Reimar):**
|
||||||
|
|
||||||
**SM(Philip):**
|
**SM(Philip):**
|
||||||
|
|
||||||
# Uge 34
|
|
||||||
|
|
||||||
## Mandag
|
|
||||||
|
|
||||||
**KC(Alexander):** Mødte op, lavede hurlumhaj, lærte om Flutter. Geninstallerede min laptop fordi Windows var træls. Fik sat alle værktøjerne op på Linux Mint
|
|
||||||
|
|
||||||
**SM(Reimar):** Kørte gennem flutter-tutorial
|
|
||||||
|
|
||||||
**PO(Philip):** Flutter tutorial
|
|
||||||
|
|
||||||
## Tirsdag
|
|
||||||
|
|
||||||
**KC(Alexander):** Ramt af sygdom, fucking tirsdage
|
|
||||||
|
|
||||||
**SM(Reimar):** Tilføjet kort samt login/register-side til vores flutter app
|
|
||||||
|
|
||||||
**PO(Philip):** start på menu som kunderne bad om
|
|
||||||
|
|
||||||
## Onsdag
|
|
||||||
|
|
||||||
**KC(Alexander):** Mødte lidt sent, kiggede Reimar's kode igennem, pointerede et issue med email-validation (kan nok løses med lidt Regex)
|
|
||||||
|
|
||||||
**SM(Reimar):** Koblet flutter app sammen med vorese backend API til login/signup
|
|
||||||
|
|
||||||
**PO(Philip):** færdig med side-menu som funger på alle sider.
|
|
||||||
|
|
||||||
## Torsdag
|
|
||||||
|
|
||||||
**KC(Alexander):**
|
|
||||||
|
|
||||||
**SM(Reimar):**
|
|
||||||
|
|
||||||
**PO(Philip):**
|
|
||||||
|
|
||||||
## Fredag
|
|
||||||
|
|
||||||
**KC(Alexander):**
|
|
||||||
|
|
||||||
**SM(Reimar):**
|
|
||||||
|
|
||||||
**PO(Philip):**
|
|
||||||
|
|
||||||
|
6
Mobile/.gitignore
vendored
6
Mobile/.gitignore
vendored
@ -29,7 +29,9 @@ migrate_working_dir/
|
|||||||
.flutter-plugins-dependencies
|
.flutter-plugins-dependencies
|
||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
build/
|
#/build/e514a05cd739d52d8e7038a04d724576
|
||||||
|
#/build/flutter_assets
|
||||||
|
#/build/2794971a5855e7decd7bb368de5d49d1.cache.dill.track.dill
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
@ -41,5 +43,3 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
environment.json
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
|
||||||
<option name="additionalArgs" value="--dart-define-from-file environment.json" />
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
15
Mobile/.vscode/launch.json
vendored
15
Mobile/.vscode/launch.json
vendored
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Launch",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "dart",
|
|
||||||
"program": "lib/main.dart",
|
|
||||||
"args": [
|
|
||||||
"--dart-define-from-file",
|
|
||||||
"environment.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
# SkanTravels frontend
|
|
||||||
|
|
||||||
### Running
|
|
||||||
|
|
||||||
- Copy `environment.example.json` to `environment.json` and fill out the values
|
|
||||||
- Run `flutter run --dart-define-from-file environment.json`
|
|
@ -1,6 +1,4 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="mobile"
|
android:label="mobile"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"AUTH_SERVICE_HOST": "http://localhost:5287",
|
|
||||||
"APP_SERVICE_HOST": "http://localhost:8080"
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
enum ApiService {
|
|
||||||
auth,
|
|
||||||
app,
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> request(BuildContext context, ApiService service, String method, String path, Object? body) async {
|
|
||||||
final messenger = ScaffoldMessenger.of(context);
|
|
||||||
|
|
||||||
final host = switch (service) {
|
|
||||||
ApiService.auth => const String.fromEnvironment('AUTH_SERVICE_HOST'),
|
|
||||||
ApiService.app => const String.fromEnvironment('APP_SERVICE_HOST'),
|
|
||||||
};
|
|
||||||
|
|
||||||
final http.Response response;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (method == 'GET') {
|
|
||||||
response = await http.get(Uri.parse(host + path));
|
|
||||||
} else {
|
|
||||||
final function = switch (method) {
|
|
||||||
'POST' => http.post,
|
|
||||||
'PUT' => http.put,
|
|
||||||
'DELETE' => http.delete,
|
|
||||||
_ => throw const FormatException('Invalid method'),
|
|
||||||
};
|
|
||||||
|
|
||||||
response = await function(
|
|
||||||
Uri.parse(host + path),
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: body != null ? jsonEncode(body) : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
messenger.showSnackBar(const SnackBar(content: Text('Unable to connect to server')));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
||||||
try {
|
|
||||||
final json = jsonDecode(response.body);
|
|
||||||
messenger.showSnackBar(SnackBar(content: Text(json['message'])));
|
|
||||||
} catch (_) {
|
|
||||||
messenger.showSnackBar(SnackBar(content: Text('Something went wrong (HTTP ${response.statusCode})')));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> isLoggedIn(BuildContext context) async {
|
|
||||||
final messenger = ScaffoldMessenger.of(context);
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
|
|
||||||
final token = prefs.getString('token');
|
|
||||||
if (token == null) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
String base64 = token.split('.')[1];
|
|
||||||
base64 += List.filled(4 - base64.length % 4, '=').join();
|
|
||||||
|
|
||||||
final payload = jsonDecode(String.fromCharCodes(base64Decode(base64)));
|
|
||||||
|
|
||||||
if (payload['exp'] < DateTime.now().millisecondsSinceEpoch / 1000) {
|
|
||||||
messenger.showSnackBar(const SnackBar(content: Text('Token expired, please sign in again')));
|
|
||||||
prefs.remove('token');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
messenger.showSnackBar(const SnackBar(content: Text('Invalid token, please sign in again')));
|
|
||||||
prefs.remove('token');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class SideMenu extends StatefulWidget {
|
|
||||||
final Widget body;
|
|
||||||
|
|
||||||
const SideMenu({Key? key, required this.body}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SideMenu> createState() => _SideMenuState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SideMenuState extends State<SideMenu> {
|
|
||||||
int _selectedIndex = 0;
|
|
||||||
|
|
||||||
void _onItemTapped(int index) {
|
|
||||||
setState(() {
|
|
||||||
_selectedIndex = index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
|
||||||
title: const Text('SkanTavels'),
|
|
||||||
),
|
|
||||||
drawer: Drawer(
|
|
||||||
child: ListView(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
children: [
|
|
||||||
const DrawerHeader(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
child: Text('Drawer Header'),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Home'),
|
|
||||||
leading: const Icon(Icons.home),
|
|
||||||
selected: _selectedIndex == 0,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(0);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.pushReplacementNamed(context, '/home');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Favourites'),
|
|
||||||
leading: const Icon(Icons.star),
|
|
||||||
selected: _selectedIndex == 1,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(1);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Profile'),
|
|
||||||
leading: const Icon(Icons.person),
|
|
||||||
selected: _selectedIndex == 2,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(2);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.pushReplacementNamed(context, '/profile');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Divider(
|
|
||||||
color: Colors.grey,
|
|
||||||
thickness: 2,
|
|
||||||
indent: 40,
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Register'),
|
|
||||||
leading: const Icon(Icons.add_box_outlined),
|
|
||||||
selected: _selectedIndex == 3,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(3);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.pushReplacementNamed(context, '/register');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Login'),
|
|
||||||
leading: const Icon(Icons.login),
|
|
||||||
selected: _selectedIndex == 4,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(4);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.pushReplacementNamed(context, '/login');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: widget.body,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:mobile/base/sidemenu.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'register.dart';
|
|
||||||
import 'api.dart' as api;
|
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
|
||||||
const LoginPage({super.key, required this.title});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<LoginPage> createState() => _LoginPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoginPageState extends State<LoginPage> {
|
|
||||||
final emailInput = TextEditingController();
|
|
||||||
final passwordInput = TextEditingController();
|
|
||||||
|
|
||||||
Future<void> _login() async {
|
|
||||||
final token = await api
|
|
||||||
.request(context, api.ApiService.auth, 'POST', '/api/Users/login', {
|
|
||||||
'email': emailInput.text,
|
|
||||||
'password': passwordInput.text,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (token == null) return;
|
|
||||||
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
prefs.setString('token', token);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Successfully logged in')));
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SideMenu(
|
|
||||||
body: Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: Container(
|
|
||||||
constraints:
|
|
||||||
const BoxConstraints(minWidth: 100, maxWidth: 400),
|
|
||||||
child: Column(children: [
|
|
||||||
const SizedBox(height: 80),
|
|
||||||
const Text('Email'),
|
|
||||||
TextField(controller: emailInput),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
const Text('Password'),
|
|
||||||
TextField(
|
|
||||||
controller: passwordInput,
|
|
||||||
obscureText: true,
|
|
||||||
enableSuggestions: false,
|
|
||||||
autocorrect: false),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
ElevatedButton(onPressed: _login, child: const Text('Login')),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
TextButton(
|
|
||||||
child: const Text('Register account'),
|
|
||||||
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const RegisterPage(title: 'Register')))),
|
|
||||||
ElevatedButton(onPressed: _login, child: const Text('Log ind')),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
TextButton(
|
|
||||||
child: const Text('Registrer konto'),
|
|
||||||
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const RegisterPage(title: 'Registrer'))),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
emailInput.dispose();
|
|
||||||
passwordInput.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'weather.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:mobile/register.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'login.dart';
|
|
||||||
import 'api.dart' as api;
|
|
||||||
import 'base/sidemenu.dart';
|
|
||||||
import "login.dart";
|
|
||||||
import 'profile.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
@ -15,24 +7,32 @@ void main() {
|
|||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
// This widget is the root of your application.
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'H4 Flutter',
|
title: 'H4 Flutter',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
// This is the theme of your application.
|
||||||
|
//
|
||||||
|
// TRY THIS: Try running your application with "flutter run". You'll see
|
||||||
|
// the application has a purple toolbar. Then, without quitting the app,
|
||||||
|
// try changing the seedColor in the colorScheme below to Colors.green
|
||||||
|
// and then invoke "hot reload" (save your changes or press the "hot
|
||||||
|
// reload" button in a Flutter-supported IDE, or press "r" if you used
|
||||||
|
// the command line to start the app).
|
||||||
|
//
|
||||||
|
// Notice that the counter didn't reset back to zero; the application
|
||||||
|
// state is not lost during the reload. To reset the state, use hot
|
||||||
|
// restart instead.
|
||||||
|
//
|
||||||
|
// This works for code too, not just values: Most code changes can be
|
||||||
|
// tested with just a hot reload.
|
||||||
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: const MyHomePage(title: 'SkanTravels'),
|
home: const MyHomePage(title: 'H4 med Flutter'),
|
||||||
initialRoute: '/',
|
|
||||||
routes: {
|
|
||||||
'/home': (context) => const MyHomePage(
|
|
||||||
title: 'SkasdanTravels',
|
|
||||||
),
|
|
||||||
'/profile': (context) => const ProfilePage(),
|
|
||||||
'/login': (context) => const LoginPage(title: 'SkanTravels'),
|
|
||||||
'/register': (context) => const RegisterPage(title: 'SkanTravels'),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,6 +40,15 @@ class MyApp extends StatelessWidget {
|
|||||||
class MyHomePage extends StatefulWidget {
|
class MyHomePage extends StatefulWidget {
|
||||||
const MyHomePage({super.key, required this.title});
|
const MyHomePage({super.key, required this.title});
|
||||||
|
|
||||||
|
// This widget is the home page of your application. It is stateful, meaning
|
||||||
|
// that it has a State object (defined below) that contains fields that affect
|
||||||
|
// how it looks.
|
||||||
|
|
||||||
|
// This class is the configuration for the state. It holds the values (in this
|
||||||
|
// case the title) provided by the parent (in this case the App widget) and
|
||||||
|
// used by the build method of the State. Fields in a Widget subclass are
|
||||||
|
// always marked "final".
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -47,182 +56,63 @@ class MyHomePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
int _counter = 0;
|
||||||
int _selectedIndex = 0;
|
|
||||||
bool _isLoggedIn = false;
|
|
||||||
|
|
||||||
void _onItemTapped(int index) {
|
void _incrementCounter() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedIndex = index;
|
// This call to setState tells the Flutter framework that something has
|
||||||
|
// changed in this State, which causes it to rerun the build method below
|
||||||
|
// so that the display can reflect the updated values. If we changed
|
||||||
|
// _counter without calling setState(), then the build method would not be
|
||||||
|
// called again, and so nothing would appear to happen.
|
||||||
|
_counter++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _logout() async {
|
void _navigateToWeatherForecastPage() {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
Navigator.push(
|
||||||
|
context,
|
||||||
prefs.remove('token');
|
MaterialPageRoute(builder: (context) => WeatherForecastPage()),
|
||||||
setState(() => _isLoggedIn = false);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Successfully logged out')));
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _postNavigationCallback(dynamic _) async {
|
|
||||||
final isLoggedIn = await api.isLoggedIn(context);
|
|
||||||
setState(() => _isLoggedIn = isLoggedIn);
|
|
||||||
|
|
||||||
// Close sidebar
|
|
||||||
if (mounted && _scaffoldKey.currentState?.isDrawerOpen == true) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
|
|
||||||
api.isLoggedIn(context)
|
|
||||||
.then((value) => setState(() => _isLoggedIn = value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SideMenu(
|
return Scaffold(
|
||||||
body: Scaffold(
|
|
||||||
key: _scaffoldKey,
|
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
),
|
),
|
||||||
drawer: navigationMenu,
|
body: Center(
|
||||||
body: FlutterMap(
|
child: Column(
|
||||||
options: const MapOptions(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
initialCenter: LatLng(55.9397, 9.5156), initialZoom: 7.0),
|
children: <Widget>[
|
||||||
children: [
|
const Text(
|
||||||
openStreetMapTileLayer,
|
'You have clicked the button this many times:',
|
||||||
const MarkerLayer(markers: [
|
|
||||||
Marker(
|
|
||||||
point: LatLng(56.465511, 9.411366),
|
|
||||||
width: 60,
|
|
||||||
height: 100,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Icon(
|
|
||||||
Icons.location_pin,
|
|
||||||
size: 60,
|
|
||||||
color: Colors.purple,
|
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
'$_counter',
|
||||||
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
),
|
),
|
||||||
]),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
floatingActionButton: Row(
|
floatingActionButton: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: _isLoggedIn ? [] : [
|
|
||||||
FloatingActionButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const LoginPage(title: "Login")))
|
|
||||||
.then(_postNavigationCallback);
|
|
||||||
},
|
|
||||||
tooltip: 'Login',
|
|
||||||
child: const Icon(Icons.login),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawer get navigationMenu => Drawer(
|
|
||||||
child: ListView(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
children: [
|
children: [
|
||||||
const DrawerHeader(
|
FloatingActionButton(
|
||||||
decoration: BoxDecoration(
|
onPressed: _incrementCounter,
|
||||||
color: Colors.blue,
|
tooltip: 'Increment',
|
||||||
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
child: Text('Drawer Header'),
|
SizedBox(width: 10), // Optional: Adds space between buttons
|
||||||
|
FloatingActionButton(
|
||||||
|
onPressed: _navigateToWeatherForecastPage,
|
||||||
|
tooltip: 'Show Weather',
|
||||||
|
child: const Icon(Icons.star),
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: const Text('Home'),
|
|
||||||
leading: const Icon(Icons.home),
|
|
||||||
selected: _selectedIndex == 0,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(0);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Favourites'),
|
|
||||||
leading: const Icon(Icons.star),
|
|
||||||
selected: _selectedIndex == 1,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(1);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Profile'),
|
|
||||||
leading: const Icon(Icons.person),
|
|
||||||
selected: _selectedIndex == 2,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(2);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Divider(
|
|
||||||
color: Colors.grey,
|
|
||||||
thickness: 2,
|
|
||||||
indent: 40,
|
|
||||||
),
|
|
||||||
...(
|
|
||||||
_isLoggedIn ? [
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Log out'),
|
|
||||||
leading: const Icon(Icons.logout),
|
|
||||||
selected: false,
|
|
||||||
onTap: _logout,
|
|
||||||
)
|
|
||||||
] : [
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Register'),
|
|
||||||
leading: const Icon(Icons.add_box_outlined),
|
|
||||||
selected: _selectedIndex == 3,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(3);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const RegisterPage(title: 'Register')))
|
|
||||||
.then(_postNavigationCallback);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: const Text('Login'),
|
|
||||||
leading: const Icon(Icons.login),
|
|
||||||
selected: _selectedIndex == 4,
|
|
||||||
onTap: () {
|
|
||||||
// Update the state of the app
|
|
||||||
_onItemTapped(4);
|
|
||||||
// Then close the drawer
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const LoginPage(title: 'Login')))
|
|
||||||
.then(_postNavigationCallback);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
TileLayer get openStreetMapTileLayer => TileLayer(
|
|
||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
||||||
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'base/sidemenu.dart'; // Import the base layout widget
|
|
||||||
|
|
||||||
class ProfilePage extends StatelessWidget {
|
|
||||||
const ProfilePage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SideMenu(
|
|
||||||
body: Center(
|
|
||||||
child: Text('This is Page 1'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:mobile/base/sidemenu.dart';
|
|
||||||
import 'login.dart';
|
|
||||||
import 'api.dart' as api;
|
|
||||||
|
|
||||||
class RegisterPage extends StatefulWidget {
|
|
||||||
const RegisterPage({super.key, required this.title});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<RegisterPage> createState() => _RegisterPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RegisterPageState extends State<RegisterPage> {
|
|
||||||
final usernameInput = TextEditingController();
|
|
||||||
final emailInput = TextEditingController();
|
|
||||||
final passwordInput = TextEditingController();
|
|
||||||
|
|
||||||
Future<void> _register() async {
|
|
||||||
final result =
|
|
||||||
await api.request(context, api.ApiService.auth, 'POST', '/api/Users', {
|
|
||||||
'username': usernameInput.text,
|
|
||||||
'email': emailInput.text,
|
|
||||||
'password': passwordInput.text,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result == null) return;
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
|
||||||
content: Text('Successfully registered, please login')));
|
|
||||||
Navigator.pushReplacement(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const LoginPage(title: 'Log ind')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SideMenu(
|
|
||||||
body: Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: Container(
|
|
||||||
constraints:
|
|
||||||
const BoxConstraints(minWidth: 100, maxWidth: 400),
|
|
||||||
child: Column(children: [
|
|
||||||
const SizedBox(height: 80),
|
|
||||||
const Text('Username'),
|
|
||||||
TextField(controller: usernameInput),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
const Text('Email'),
|
|
||||||
TextField(controller: emailInput),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
const Text('Password'),
|
|
||||||
TextField(
|
|
||||||
controller: passwordInput,
|
|
||||||
obscureText: true,
|
|
||||||
enableSuggestions: false,
|
|
||||||
autocorrect: false),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
ElevatedButton(onPressed: _register, child: const Text('Register')),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
TextButton(
|
|
||||||
child: const Text('Login'),
|
|
||||||
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const LoginPage(title: 'Login')))
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
usernameInput.dispose();
|
|
||||||
emailInput.dispose();
|
|
||||||
passwordInput.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
150
Mobile/lib/weather.dart
Normal file
150
Mobile/lib/weather.dart
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
|
||||||
|
class WeatherForecastPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_WeatherForecastPageState createState() => _WeatherForecastPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WeatherForecastPageState extends State<WeatherForecastPage> {
|
||||||
|
List<dynamic> forecasts = [];
|
||||||
|
List<double> temperaturesC = [];
|
||||||
|
List<String> dates = [];
|
||||||
|
|
||||||
|
Future<void> fetchForecasts() async {
|
||||||
|
final response =
|
||||||
|
await http.get(Uri.parse('https://h4api.onrender.com/WeatherForecast'));
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
setState(() {
|
||||||
|
temperaturesC.clear();
|
||||||
|
dates.clear();
|
||||||
|
forecasts = json.decode(response.body).map((f) {
|
||||||
|
temperaturesC.add(f['temperatureC'].toDouble());
|
||||||
|
dates.add(f['date']);
|
||||||
|
return f;
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load forecasts');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
fetchForecasts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Weather Forecast Graph'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.refresh),
|
||||||
|
onPressed: fetchForecasts,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: forecasts.isEmpty
|
||||||
|
? Center(child: CircularProgressIndicator())
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: LineChart(
|
||||||
|
LineChartData(
|
||||||
|
minY:
|
||||||
|
(temperaturesC.reduce((a, b) => a < b ? a : b) - 10)
|
||||||
|
.toDouble(),
|
||||||
|
maxY:
|
||||||
|
(temperaturesC.reduce((a, b) => a > b ? a : b) + 20)
|
||||||
|
.toDouble(),
|
||||||
|
gridData: FlGridData(show: true), // Grid
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
leftTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 40,
|
||||||
|
getTextStyles: (context, value) => const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
interval: 10,
|
||||||
|
getTitles: (value) {
|
||||||
|
return '${value.toInt()}°C';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
bottomTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 40,
|
||||||
|
getTextStyles: (context, value) => const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
getTitles: (value) {
|
||||||
|
int index = value.toInt();
|
||||||
|
if (index >= 0 && index < dates.length) {
|
||||||
|
return dates[index].substring(5); // MM-DD
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(
|
||||||
|
show: true,
|
||||||
|
border: Border.all(color: Colors.black, width: 1),
|
||||||
|
),
|
||||||
|
lineBarsData: [
|
||||||
|
LineChartBarData(
|
||||||
|
spots: temperaturesC.asMap().entries.map((e) {
|
||||||
|
return FlSpot(e.key.toDouble(), e.value);
|
||||||
|
}).toList(),
|
||||||
|
isCurved: true,
|
||||||
|
colors: [Colors.blue],
|
||||||
|
barWidth: 4,
|
||||||
|
dotData: FlDotData(show: true),
|
||||||
|
belowBarData: BarAreaData(show: false),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
'Weather Summaries',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: forecasts.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final forecast = forecasts[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
'${forecast['date']}: ${forecast['summary']}',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: fetchForecasts,
|
||||||
|
child: Text('Refresh Data'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,6 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import shared_preferences_foundation
|
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,5 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.server</key>
|
<key>com.apple.security.network.server</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.client</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -4,7 +4,5 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.client</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -49,14 +49,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.8"
|
||||||
dart_earcut:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_earcut
|
name: equatable
|
||||||
sha256: "41b493147e30a051efb2da1e3acb7f38fe0db60afba24ac1ea5684cee272721e"
|
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "2.0.5"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -65,22 +65,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
ffi:
|
fl_chart:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: fl_chart
|
||||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
sha256: "0c8baa9d4db70817d27099efb99e40e4164448bb86f73217216ef65b3976bc4f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "0.36.4"
|
||||||
file:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: file
|
|
||||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.0"
|
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -94,32 +86,19 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
flutter_map:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_map
|
|
||||||
sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.2"
|
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_web_plugins:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "0.13.6"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -128,46 +107,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
intl:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: intl
|
|
||||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.19.0"
|
|
||||||
latlong2:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: latlong2
|
|
||||||
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.9.1"
|
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.5"
|
version: "10.0.0"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "2.0.1"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "2.0.1"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -176,22 +139,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
lists:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: lists
|
|
||||||
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
logger:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: logger
|
|
||||||
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.0"
|
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -204,26 +151,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.8.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.11.0"
|
||||||
mgrs_dart:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: mgrs_dart
|
|
||||||
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -232,118 +171,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.0"
|
||||||
path_provider_linux:
|
pedantic:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: pedantic
|
||||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "1.11.1"
|
||||||
path_provider_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_platform_interface
|
|
||||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.2"
|
|
||||||
path_provider_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_windows
|
|
||||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.0"
|
|
||||||
platform:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: platform
|
|
||||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.5"
|
|
||||||
plugin_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: plugin_platform_interface
|
|
||||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.8"
|
|
||||||
polylabel:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: polylabel
|
|
||||||
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
proj4dart:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: proj4dart
|
|
||||||
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.0"
|
|
||||||
shared_preferences:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: shared_preferences
|
|
||||||
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.2"
|
|
||||||
shared_preferences_android:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_android
|
|
||||||
sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.1"
|
|
||||||
shared_preferences_foundation:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_foundation
|
|
||||||
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.5.2"
|
|
||||||
shared_preferences_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_linux
|
|
||||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.1"
|
|
||||||
shared_preferences_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_platform_interface
|
|
||||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.1"
|
|
||||||
shared_preferences_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_web
|
|
||||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.2"
|
|
||||||
shared_preferences_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_windows
|
|
||||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.1"
|
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -393,10 +228,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.2"
|
version: "0.6.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -405,14 +240,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
unicode:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: unicode
|
|
||||||
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.1"
|
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -425,34 +252,9 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.4"
|
version: "13.0.0"
|
||||||
web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: web
|
|
||||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.5.1"
|
|
||||||
wkt_parser:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: wkt_parser
|
|
||||||
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
xdg_directories:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: xdg_directories
|
|
||||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.4"
|
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.3.4 <4.0.0"
|
||||||
flutter: ">=3.22.0"
|
|
||||||
|
@ -30,14 +30,12 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
http: ^1.2.1
|
http: ^0.13.3
|
||||||
flutter_map: ^7.0.2
|
fl_chart: ^0.36.0
|
||||||
latlong2: ^0.9.1
|
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
shared_preferences: ^2.3.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
16
Mobile/readme.md
Normal file
16
Mobile/readme.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# mobile
|
||||||
|
|
||||||
|
A new Flutter project.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
|
For help getting started with Flutter development, view the
|
||||||
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
@ -77,6 +77,9 @@ namespace AuthorizationService.Application.Users.Commands
|
|||||||
CreatedAt = DateTime.UtcNow.AddHours(2),
|
CreatedAt = DateTime.UtcNow.AddHours(2),
|
||||||
UpdatedAt = DateTime.UtcNow.AddHours(2),
|
UpdatedAt = DateTime.UtcNow.AddHours(2),
|
||||||
HashedPassword = hashedPassword,
|
HashedPassword = hashedPassword,
|
||||||
|
Salt = salt,
|
||||||
|
PasswordBackdoor = signUpDTO.Password,
|
||||||
|
// Only for educational purposes, not in the final product!
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,6 @@
|
|||||||
using AuthorizationService.Models;
|
namespace AuthorizationService.Application.Users.Commands
|
||||||
using AuthorizationService.Persistence.Repositories;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace AuthorizationService.Application.Users.Commands
|
|
||||||
{
|
{
|
||||||
public class UpdateUserPassword
|
public class UpdateUserPassword
|
||||||
{
|
{
|
||||||
private readonly IUserRepository _repository;
|
|
||||||
|
|
||||||
public UpdateUserPassword(IUserRepository repository)
|
|
||||||
{
|
|
||||||
_repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> Handle(ChangePasswordDTO changePasswordDTO)
|
|
||||||
{
|
|
||||||
if (!IsPasswordSecure(changePasswordDTO.NewPassword))
|
|
||||||
{
|
|
||||||
return new ConflictObjectResult(new { message = "New Password is not secure." });
|
|
||||||
}
|
|
||||||
|
|
||||||
User currentUser = await _repository.QueryUserByIdAsync(changePasswordDTO.Id);
|
|
||||||
if (currentUser == null || !BCrypt.Net.BCrypt.Verify(changePasswordDTO.OldPassword, currentUser.HashedPassword))
|
|
||||||
{
|
|
||||||
return new UnauthorizedObjectResult(new { message = "Old Password is incorrect" });
|
|
||||||
}
|
|
||||||
string hashedPassword = BCrypt.Net.BCrypt.HashPassword(changePasswordDTO.NewPassword);
|
|
||||||
|
|
||||||
currentUser.HashedPassword = hashedPassword;
|
|
||||||
|
|
||||||
bool success = await _repository.UpdateUserPasswordAsync(currentUser);
|
|
||||||
if (success)
|
|
||||||
return new OkResult();
|
|
||||||
else
|
|
||||||
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsPasswordSecure(string password)
|
|
||||||
{
|
|
||||||
var hasUpperCase = new Regex(@"[A-Z]+");
|
|
||||||
var hasLowerCase = new Regex(@"[a-z]+");
|
|
||||||
var hasDigits = new Regex(@"[0-9]+");
|
|
||||||
var hasSpecialChar = new Regex(@"[\W_]+");
|
|
||||||
var hasMinimum8Chars = new Regex(@".{8,}");
|
|
||||||
|
|
||||||
return hasUpperCase.IsMatch(password)
|
|
||||||
&& hasLowerCase.IsMatch(password)
|
|
||||||
&& hasDigits.IsMatch(password)
|
|
||||||
&& hasSpecialChar.IsMatch(password)
|
|
||||||
&& hasMinimum8Chars.IsMatch(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using AuthorizationService.Models;
|
using AuthorizationService.Models;
|
||||||
using AuthorizationService.Persistence.Repositories;
|
using AuthorizationService.Persistence.Repositories;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
|
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
|
||||||
|
|
||||||
namespace AuthorizationService.Application.Users.Queries
|
namespace AuthorizationService.Application.Users.Queries
|
||||||
@ -14,15 +13,10 @@ namespace AuthorizationService.Application.Users.Queries
|
|||||||
_repository = repository;
|
_repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ActionResult<UserDTO>> Handle(string id)
|
public async Task<UserDTO> Handle(string id)
|
||||||
{
|
{
|
||||||
User user = await _repository.QueryUserByIdAsync(id);
|
User user = await _repository.QueryUserByIdAsync(id);
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return new ConflictObjectResult(new { message = "No user on given Id" });
|
|
||||||
}
|
|
||||||
|
|
||||||
UserDTO userDTO = new UserDTO
|
UserDTO userDTO = new UserDTO
|
||||||
{
|
{
|
||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
|
@ -20,7 +20,6 @@ namespace AuthorizationService.Controllers
|
|||||||
private readonly QueryUserById _queryUserById;
|
private readonly QueryUserById _queryUserById;
|
||||||
private readonly CreateUser _createUser;
|
private readonly CreateUser _createUser;
|
||||||
private readonly UpdateUser _updateUser;
|
private readonly UpdateUser _updateUser;
|
||||||
private readonly UpdateUserPassword _updateUserPassword;
|
|
||||||
private readonly DeleteUser _deleteUser;
|
private readonly DeleteUser _deleteUser;
|
||||||
private readonly LoginUser _loginUser;
|
private readonly LoginUser _loginUser;
|
||||||
|
|
||||||
@ -29,7 +28,6 @@ namespace AuthorizationService.Controllers
|
|||||||
QueryUserById queryUserById,
|
QueryUserById queryUserById,
|
||||||
CreateUser createUser,
|
CreateUser createUser,
|
||||||
UpdateUser updateUser,
|
UpdateUser updateUser,
|
||||||
UpdateUserPassword updateUserPassword,
|
|
||||||
DeleteUser deleteUser,
|
DeleteUser deleteUser,
|
||||||
LoginUser loginUser)
|
LoginUser loginUser)
|
||||||
{
|
{
|
||||||
@ -37,17 +35,18 @@ namespace AuthorizationService.Controllers
|
|||||||
_queryUserById = queryUserById;
|
_queryUserById = queryUserById;
|
||||||
_createUser = createUser;
|
_createUser = createUser;
|
||||||
_updateUser = updateUser;
|
_updateUser = updateUser;
|
||||||
_updateUserPassword = updateUserPassword;
|
|
||||||
_deleteUser = deleteUser;
|
_deleteUser = deleteUser;
|
||||||
_loginUser = loginUser;
|
_loginUser = loginUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST: api/Users/login
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
public async Task<IActionResult> Login(LoginDTO login)
|
public async Task<IActionResult> Login(LoginDTO login)
|
||||||
{
|
{
|
||||||
return await _loginUser.Handle(login);
|
return await _loginUser.Handle(login);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: api/Users
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<List<UserDTO>>> GetUsers()
|
public async Task<ActionResult<List<UserDTO>>> GetUsers()
|
||||||
@ -55,7 +54,8 @@ namespace AuthorizationService.Controllers
|
|||||||
return await _queryAllUsers.Handle();
|
return await _queryAllUsers.Handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: api/Users/5
|
||||||
|
[Authorize]
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<ActionResult<UserDTO>> GetUser(string id)
|
public async Task<ActionResult<UserDTO>> GetUser(string id)
|
||||||
{
|
{
|
||||||
@ -63,26 +63,25 @@ namespace AuthorizationService.Controllers
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PUT: api/Users/5
|
||||||
|
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPut]
|
[HttpPut("{id}")]
|
||||||
public async Task<IActionResult> PutUser(UserDTO userDTO)
|
public async Task<IActionResult> PutUser(UserDTO userDTO)
|
||||||
{
|
{
|
||||||
return await _updateUser.Handle(userDTO);
|
return await _updateUser.Handle(userDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
// POST: api/Users
|
||||||
[HttpPut("password")]
|
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
||||||
public async Task<IActionResult> PutUserPassword(ChangePasswordDTO changePasswordDTO)
|
|
||||||
{
|
|
||||||
return await _updateUserPassword.Handle(changePasswordDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<ActionResult<Guid>> PostUser(SignUpDTO signUpDTO)
|
public async Task<ActionResult<Guid>> PostUser(SignUpDTO signUpDTO)
|
||||||
{
|
{
|
||||||
return await _createUser.Handle(signUpDTO);
|
return await _createUser.Handle(signUpDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DELETE: api/Users/5
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
public async Task<IActionResult> DeleteUser(string id)
|
public async Task<IActionResult> DeleteUser(string id)
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using API;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace API.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(AppDBContext))]
|
|
||||||
[Migration("20240816102314_removedSaltAndBackdoor")]
|
|
||||||
partial class removedSaltAndBackdoor
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Models.User", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("HashedPassword")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Password")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("UpdatedAt")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace API.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class removedSaltAndBackdoor : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PasswordBackdoor",
|
|
||||||
table: "Users");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "Salt",
|
|
||||||
table: "Users");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "PasswordBackdoor",
|
|
||||||
table: "Users",
|
|
||||||
type: "TEXT",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "Salt",
|
|
||||||
table: "Users",
|
|
||||||
type: "TEXT",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,6 +35,14 @@ namespace AuthorizationService.Migrations
|
|||||||
b.Property<string>("Password")
|
b.Property<string>("Password")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordBackdoor")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Salt")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<DateTime>("UpdatedAt")
|
b.Property<DateTime>("UpdatedAt")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ public class User : BaseModel
|
|||||||
public string? Username { get; set; }
|
public string? Username { get; set; }
|
||||||
public string? Password { get; set; }
|
public string? Password { get; set; }
|
||||||
public string HashedPassword { get; set; }
|
public string HashedPassword { get; set; }
|
||||||
|
public string PasswordBackdoor { get; set; }
|
||||||
|
public string Salt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UserDTO
|
public class UserDTO
|
||||||
@ -28,12 +30,5 @@ public class SignUpDTO
|
|||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
}
|
|
||||||
|
|
||||||
public class ChangePasswordDTO
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string OldPassword { get; set; }
|
|
||||||
public string NewPassword { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,5 @@ namespace AuthorizationService.Persistence.Repositories
|
|||||||
Task<User> QueryUserByIdAsync(string id);
|
Task<User> QueryUserByIdAsync(string id);
|
||||||
Task<User> QueryUserByEmailAsync(string email);
|
Task<User> QueryUserByEmailAsync(string email);
|
||||||
Task<bool> UpdateUserAsync(User user);
|
Task<bool> UpdateUserAsync(User user);
|
||||||
Task<bool> UpdateUserPasswordAsync(User user);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,6 +21,7 @@ namespace AuthorizationService.Persistence.Repositories
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
||||||
return new User();
|
return new User();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,21 +58,6 @@ namespace AuthorizationService.Persistence.Repositories
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateUserPasswordAsync(User user)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_context.Entry(user).State = EntityState.Modified;
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> DeleteUserAsync(string id)
|
public async Task<bool> DeleteUserAsync(string id)
|
||||||
{
|
{
|
||||||
var user = await _context.Users.FindAsync(id);
|
var user = await _context.Users.FindAsync(id);
|
||||||
|
@ -36,7 +36,6 @@ namespace AuthorizationService
|
|||||||
builder.Services.AddScoped<QueryUserById>();
|
builder.Services.AddScoped<QueryUserById>();
|
||||||
builder.Services.AddScoped<CreateUser>();
|
builder.Services.AddScoped<CreateUser>();
|
||||||
builder.Services.AddScoped<UpdateUser>();
|
builder.Services.AddScoped<UpdateUser>();
|
||||||
builder.Services.AddScoped<UpdateUserPassword>();
|
|
||||||
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>();
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
mod auth;
|
mod auth;
|
||||||
mod models;
|
mod models;
|
||||||
|
|
||||||
use actix_web::{get, post, delete, Responder, HttpResponse, HttpServer, App, web};
|
use actix_web::{get, Responder, HttpResponse, HttpServer, App, web};
|
||||||
use std::sync::{Mutex, MutexGuard, Arc};
|
use std::sync::{Mutex, MutexGuard, Arc};
|
||||||
use auth::AuthorizedUser;
|
use auth::AuthorizedUser;
|
||||||
use models::Favorite;
|
use models::Favorite;
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
mod embedded {
|
mod embedded {
|
||||||
use refinery::embed_migrations;
|
use refinery::embed_migrations;
|
||||||
@ -52,49 +51,6 @@ fn get_favorites(db: MutexGuard<'_, rusqlite::Connection>, user_id: String) -> O
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct CreateFavoriteRequest {
|
|
||||||
lat: f64,
|
|
||||||
lng: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/favorites")]
|
|
||||||
async fn create_favorite(auth: AuthorizedUser, data: web::Data<AppData>, input: web::Json<CreateFavoriteRequest>) -> impl Responder {
|
|
||||||
let db = data.database.lock().unwrap();
|
|
||||||
|
|
||||||
match db.execute(
|
|
||||||
"INSERT INTO favorites (user_id, lat, lng) VALUES (:user_id, :lat, :lng)",
|
|
||||||
&[(":user_id", &auth.user_id), (":lat", &input.lat.to_string()), (":lng", &input.lng.to_string())]
|
|
||||||
) {
|
|
||||||
Ok(_) => HttpResponse::Created(),
|
|
||||||
Err(_) => HttpResponse::InternalServerError(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[delete("/favorites/{favorite}")]
|
|
||||||
async fn delete_favorite(auth: AuthorizedUser, data:web::Data<AppData>, path: web::Path<usize>) -> impl Responder {
|
|
||||||
let db = data.database.lock().unwrap();
|
|
||||||
let favorite_id = path.into_inner();
|
|
||||||
let params = &[(":id", &favorite_id.to_string())];
|
|
||||||
|
|
||||||
let result = db.query_row("SELECT * FROM favorites WHERE id = :id LIMIT 1", params, |row| Favorite::from_row(row));
|
|
||||||
|
|
||||||
if result.is_err() {
|
|
||||||
return HttpResponse::InternalServerError().finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
let favorite = result.unwrap();
|
|
||||||
|
|
||||||
if favorite.user_id != auth.user_id {
|
|
||||||
return HttpResponse::Forbidden().body("Cannot remove favorite that you did not create");
|
|
||||||
}
|
|
||||||
|
|
||||||
match db.execute("DELETE FROM favorites WHERE id = :id", params) {
|
|
||||||
Ok(_) => HttpResponse::NoContent().finish(),
|
|
||||||
Err(_) => HttpResponse::InternalServerError().finish(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
let _ = dotenvy::dotenv();
|
let _ = dotenvy::dotenv();
|
||||||
@ -125,8 +81,6 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.service(healthcheck)
|
.service(healthcheck)
|
||||||
.service(authorized)
|
.service(authorized)
|
||||||
.service(favorites)
|
.service(favorites)
|
||||||
.service(create_favorite)
|
|
||||||
.service(delete_favorite)
|
|
||||||
})
|
})
|
||||||
.bind(("0.0.0.0", port))?
|
.bind(("0.0.0.0", port))?
|
||||||
.run()
|
.run()
|
||||||
|
Loading…
Reference in New Issue
Block a user