forked from ReiMerc/skantravels
Compare commits
18 Commits
4539d48c29
...
46f9535b4a
Author | SHA1 | Date | |
---|---|---|---|
46f9535b4a | |||
acb15df909 | |||
7c2fccb79e | |||
46bb1ce74c | |||
206483de09 | |||
102e2d6ca2 | |||
c4fc11c169 | |||
|
6ee61650d1 | ||
8e42e54c98 | |||
eef47f6ee4 | |||
debbddd05b | |||
c9769fcada | |||
|
a082441826 | ||
1c6fc39b14 | |||
3bd2ba5cbc | |||
d2bfcc4692 | |||
|
f1327571e6 | ||
bb44d2bc56 |
@ -1,6 +0,0 @@
|
|||||||
namespace API.Application.Users.Commands
|
|
||||||
{
|
|
||||||
public class UpdateUserPassword
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,11 @@
|
|||||||
| | Scrum Master | Product Owner | Kundechef |
|
| | Scrum Master | Product Owner | Kundechef |
|
||||||
| ---------- | ------------ | ------------- | --------- |
|
| ---------- | ------------ | ------------- | --------- |
|
||||||
| **Uge 32** | Alexander | Reimar | Phillip |
|
| **Uge 32** | Alexander | Reimar | Philip |
|
||||||
| **Uge 33** | Phillip | Alexander | Reimar |
|
| **Uge 33** | Philip | Alexander | Reimar |
|
||||||
| **Uge 34** | Reimar | Phillip | Alexander |
|
| **Uge 34** | Reimar | Philip | Alexander |
|
||||||
| **Uge 35** | Alexander | Reimar | Phillip |
|
| **Uge 35** | Alexander | Reimar | Philip |
|
||||||
| **Uge 36** | Phillip | Alexander | Reimar |
|
| **Uge 36** | Philip | Alexander | Reimar |
|
||||||
| **Uge 37** | Reimar | Phillip | Alexander |
|
| **Uge 37** | Reimar | Philip | Alexander |
|
||||||
|
|
||||||
# Daglig Logbog Uge 32
|
# Daglig Logbog Uge 32
|
||||||
|
|
||||||
@ -66,17 +66,59 @@ KC - Kundechef
|
|||||||
|
|
||||||
## Torsdag
|
## Torsdag
|
||||||
|
|
||||||
**PO(Alexander):**
|
**PO(Alexander):** Manglende pga. sygdom
|
||||||
|
|
||||||
**KC(Reimar):**
|
**KC(Reimar):** Implementeret JWT verifikation i rust-backenden
|
||||||
|
|
||||||
**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):**
|
**PO(Alexander):** Manglende pga. kommunemøde
|
||||||
|
|
||||||
**KC(Reimar):**
|
**KC(Reimar):** Lavet model og routes til oprettelse/sletning af favoritter i rust
|
||||||
|
|
||||||
**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,9 +29,7 @@ migrate_working_dir/
|
|||||||
.flutter-plugins-dependencies
|
.flutter-plugins-dependencies
|
||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
#/build/e514a05cd739d52d8e7038a04d724576
|
build/
|
||||||
#/build/flutter_assets
|
|
||||||
#/build/2794971a5855e7decd7bb368de5d49d1.cache.dill.track.dill
|
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
@ -43,3 +41,5 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
environment.json
|
||||||
|
7
Mobile/.run/main.dart.run.xml
Normal file
7
Mobile/.run/main.dart.run.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<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
Normal file
15
Mobile/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"args": [
|
||||||
|
"--dart-define-from-file",
|
||||||
|
"environment.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
6
Mobile/README.md
Normal file
6
Mobile/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# 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,4 +1,6 @@
|
|||||||
<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}"
|
||||||
|
4
Mobile/environment.example.json
Normal file
4
Mobile/environment.example.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"AUTH_SERVICE_HOST": "http://localhost:5287",
|
||||||
|
"APP_SERVICE_HOST": "http://localhost:8080"
|
||||||
|
}
|
81
Mobile/lib/api.dart
Normal file
81
Mobile/lib/api.dart
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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;
|
||||||
|
}
|
104
Mobile/lib/base/sidemenu.dart
Normal file
104
Mobile/lib/base/sidemenu.dart
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
83
Mobile/lib/login.dart
Normal file
83
Mobile/lib/login.dart
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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,5 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'weather.dart';
|
import 'package:flutter_map/flutter_map.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());
|
||||||
@ -7,32 +15,24 @@ 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(
|
||||||
// This is the theme of your application.
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
||||||
//
|
|
||||||
// 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: 'H4 med Flutter'),
|
home: const MyHomePage(title: 'SkanTravels'),
|
||||||
|
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,15 +40,6 @@ 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
|
||||||
@ -56,63 +47,182 @@ class MyHomePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
int _counter = 0;
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
bool _isLoggedIn = false;
|
||||||
|
|
||||||
void _incrementCounter() {
|
void _onItemTapped(int index) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// This call to setState tells the Flutter framework that something has
|
_selectedIndex = index;
|
||||||
// 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 _navigateToWeatherForecastPage() {
|
void _logout() async {
|
||||||
Navigator.push(
|
final prefs = await SharedPreferences.getInstance();
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) => WeatherForecastPage()),
|
prefs.remove('token');
|
||||||
);
|
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 Scaffold(
|
return SideMenu(
|
||||||
appBar: AppBar(
|
body: Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
key: _scaffoldKey,
|
||||||
title: Text(widget.title),
|
appBar: AppBar(
|
||||||
),
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
body: Center(
|
title: Text(widget.title),
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
const Text(
|
|
||||||
'You have clicked the button this many times:',
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'$_counter',
|
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
drawer: navigationMenu,
|
||||||
|
body: FlutterMap(
|
||||||
|
options: const MapOptions(
|
||||||
|
initialCenter: LatLng(55.9397, 9.5156), initialZoom: 7.0),
|
||||||
|
children: [
|
||||||
|
openStreetMapTileLayer,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
floatingActionButton: Row(
|
floatingActionButton: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: _isLoggedIn ? [] : [
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onPressed: _incrementCounter,
|
onPressed: () {
|
||||||
tooltip: 'Increment',
|
Navigator.push(context, MaterialPageRoute(builder: (context) => const LoginPage(title: "Login")))
|
||||||
child: const Icon(Icons.add),
|
.then(_postNavigationCallback);
|
||||||
),
|
},
|
||||||
SizedBox(width: 10), // Optional: Adds space between buttons
|
tooltip: 'Login',
|
||||||
FloatingActionButton(
|
child: const Icon(Icons.login),
|
||||||
onPressed: _navigateToWeatherForecastPage,
|
|
||||||
tooltip: 'Show Weather',
|
|
||||||
child: const Icon(Icons.star),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Drawer get navigationMenu => 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.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',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
15
Mobile/lib/profile.dart
Normal file
15
Mobile/lib/profile.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
83
Mobile/lib/register.dart
Normal file
83
Mobile/lib/register.dart
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,150 +0,0 @@
|
|||||||
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,6 +5,8 @@
|
|||||||
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,5 +8,7 @@
|
|||||||
<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,5 +4,7 @@
|
|||||||
<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"
|
||||||
equatable:
|
dart_earcut:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: equatable
|
name: dart_earcut
|
||||||
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
sha256: "41b493147e30a051efb2da1e3acb7f38fe0db60afba24ac1ea5684cee272721e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "1.1.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -65,14 +65,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
fl_chart:
|
ffi:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: fl_chart
|
name: ffi
|
||||||
sha256: "0c8baa9d4db70817d27099efb99e40e4164448bb86f73217216ef65b3976bc4f"
|
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.36.4"
|
version: "2.1.3"
|
||||||
|
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
|
||||||
@ -86,19 +94,32 @@ 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: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.6"
|
version: "1.2.2"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -107,30 +128,46 @@ 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: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.0"
|
version: "10.0.5"
|
||||||
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: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.1"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -139,6 +176,22 @@ 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:
|
||||||
@ -151,18 +204,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.15.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:
|
||||||
@ -171,14 +232,118 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.0"
|
||||||
pedantic:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pedantic
|
name: path_provider_linux
|
||||||
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.1"
|
version: "2.2.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
|
||||||
@ -228,10 +393,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
version: "0.7.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -240,6 +405,14 @@ 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:
|
||||||
@ -252,9 +425,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "13.0.0"
|
version: "14.2.4"
|
||||||
|
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.3.4 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
|
flutter: ">=3.22.0"
|
||||||
|
@ -30,12 +30,14 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
http: ^0.13.3
|
http: ^1.2.1
|
||||||
fl_chart: ^0.36.0
|
flutter_map: ^7.0.2
|
||||||
|
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:
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
# 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.
|
|
@ -1,7 +1,7 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API;
|
namespace AuthorizationService;
|
||||||
|
|
||||||
public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(options)
|
public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(options)
|
||||||
{
|
{
|
@ -1,9 +1,9 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using API.Persistence.Repositories;
|
using AuthorizationService.Persistence.Repositories;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace API.Application.Users.Commands
|
namespace AuthorizationService.Application.Users.Commands
|
||||||
{
|
{
|
||||||
public class CreateUser
|
public class CreateUser
|
||||||
{
|
{
|
||||||
@ -77,9 +77,6 @@ namespace API.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,8 +1,8 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using API.Persistence.Repositories;
|
using AuthorizationService.Persistence.Repositories;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace API.Application.Users.Commands
|
namespace AuthorizationService.Application.Users.Commands
|
||||||
{
|
{
|
||||||
public class DeleteUser
|
public class DeleteUser
|
||||||
{
|
{
|
@ -1,5 +1,5 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using API.Persistence.Repositories;
|
using AuthorizationService.Persistence.Repositories;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
@ -9,7 +9,7 @@ using System.IdentityModel.Tokens.Jwt;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace API.Application.Users.Commands
|
namespace AuthorizationService.Application.Users.Commands
|
||||||
{
|
{
|
||||||
public class LoginUser
|
public class LoginUser
|
||||||
{
|
{
|
@ -1,8 +1,8 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using API.Persistence.Repositories;
|
using AuthorizationService.Persistence.Repositories;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace API.Application.Users.Commands
|
namespace AuthorizationService.Application.Users.Commands
|
||||||
{
|
{
|
||||||
public class UpdateUser
|
public class UpdateUser
|
||||||
{
|
{
|
@ -0,0 +1,57 @@
|
|||||||
|
using AuthorizationService.Models;
|
||||||
|
using AuthorizationService.Persistence.Repositories;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace AuthorizationService.Application.Users.Commands
|
||||||
|
{
|
||||||
|
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,8 +1,8 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using API.Persistence.Repositories;
|
using AuthorizationService.Persistence.Repositories;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace API.Application.Users.Queries
|
namespace AuthorizationService.Application.Users.Queries
|
||||||
{
|
{
|
||||||
public class QueryAllUsers
|
public class QueryAllUsers
|
||||||
{
|
{
|
@ -1,8 +1,9 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using API.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 API.Application.Users.Queries
|
namespace AuthorizationService.Application.Users.Queries
|
||||||
{
|
{
|
||||||
public class QueryUserById
|
public class QueryUserById
|
||||||
{
|
{
|
||||||
@ -13,10 +14,15 @@ namespace API.Application.Users.Queries
|
|||||||
_repository = repository;
|
_repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserDTO> Handle(string id)
|
public async Task<ActionResult<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,
|
@ -9,7 +9,7 @@
|
|||||||
<WebStackScaffolding_IsPartialViewSelected>False</WebStackScaffolding_IsPartialViewSelected>
|
<WebStackScaffolding_IsPartialViewSelected>False</WebStackScaffolding_IsPartialViewSelected>
|
||||||
<WebStackScaffolding_IsReferencingScriptLibrariesSelected>True</WebStackScaffolding_IsReferencingScriptLibrariesSelected>
|
<WebStackScaffolding_IsReferencingScriptLibrariesSelected>True</WebStackScaffolding_IsReferencingScriptLibrariesSelected>
|
||||||
<WebStackScaffolding_LayoutPageFile />
|
<WebStackScaffolding_LayoutPageFile />
|
||||||
<WebStackScaffolding_DbContextTypeFullName>API.AppDBContext</WebStackScaffolding_DbContextTypeFullName>
|
<WebStackScaffolding_DbContextTypeFullName>AuthorizationService.AppDBContext</WebStackScaffolding_DbContextTypeFullName>
|
||||||
<WebStackScaffolding_IsAsyncSelected>False</WebStackScaffolding_IsAsyncSelected>
|
<WebStackScaffolding_IsAsyncSelected>False</WebStackScaffolding_IsAsyncSelected>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.8.34511.84
|
VisualStudioVersion = 17.8.34511.84
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API.csproj", "{5DF9B7D8-FA4E-4209-A677-C4CF4886D4B3}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthorizationService", "AuthorizationService.csproj", "{5DF9B7D8-FA4E-4209-A677-C4CF4886D4B3}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace API.Controllers
|
namespace AuthorizationService.Controllers
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
@ -1,6 +1,6 @@
|
|||||||
using API.Application.Users.Commands;
|
using AuthorizationService.Application.Users.Commands;
|
||||||
using API.Application.Users.Queries;
|
using AuthorizationService.Application.Users.Queries;
|
||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -10,7 +10,7 @@ using System.Security.Claims;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace API.Controllers
|
namespace AuthorizationService.Controllers
|
||||||
{
|
{
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
@ -20,6 +20,7 @@ namespace API.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;
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ namespace API.Controllers
|
|||||||
QueryUserById queryUserById,
|
QueryUserById queryUserById,
|
||||||
CreateUser createUser,
|
CreateUser createUser,
|
||||||
UpdateUser updateUser,
|
UpdateUser updateUser,
|
||||||
|
UpdateUserPassword updateUserPassword,
|
||||||
DeleteUser deleteUser,
|
DeleteUser deleteUser,
|
||||||
LoginUser loginUser)
|
LoginUser loginUser)
|
||||||
{
|
{
|
||||||
@ -35,18 +37,17 @@ namespace API.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()
|
||||||
@ -54,8 +55,7 @@ namespace API.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,25 +63,26 @@ namespace API.Controllers
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT: api/Users/5
|
|
||||||
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPut("{id}")]
|
[HttpPut]
|
||||||
public async Task<IActionResult> PutUser(UserDTO userDTO)
|
public async Task<IActionResult> PutUser(UserDTO userDTO)
|
||||||
{
|
{
|
||||||
return await _updateUser.Handle(userDTO);
|
return await _updateUser.Handle(userDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: api/Users
|
[Authorize]
|
||||||
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
[HttpPut("password")]
|
||||||
|
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)
|
@ -9,17 +9,17 @@ EXPOSE 8081
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
ARG BUILD_CONFIGURATION=Release
|
ARG BUILD_CONFIGURATION=Release
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY ["API.csproj", "."]
|
COPY ["AuthorizationService.csproj", "."]
|
||||||
RUN dotnet restore "./././API.csproj"
|
RUN dotnet restore "./././AuthorizationService.csproj"
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR "/src/."
|
WORKDIR "/src/."
|
||||||
RUN dotnet build "./API.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
RUN dotnet build "./AuthorizationService.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
FROM build AS publish
|
FROM build AS publish
|
||||||
ARG BUILD_CONFIGURATION=Release
|
ARG BUILD_CONFIGURATION=Release
|
||||||
RUN dotnet publish "./API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
RUN dotnet publish "./AuthorizationService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
FROM base AS final
|
FROM base AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=publish /app/publish .
|
COPY --from=publish /app/publish .
|
||||||
ENTRYPOINT ["dotnet", "API.dll"]
|
ENTRYPOINT ["dotnet", "AuthorizationService.dll"]
|
@ -1,5 +1,5 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using API;
|
using AuthorizationService;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace API.Migrations
|
namespace AuthorizationService.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(AppDBContext))]
|
[DbContext(typeof(AppDBContext))]
|
||||||
[Migration("20240812084720_CreateUser")]
|
[Migration("20240812084720_CreateUser")]
|
||||||
@ -19,7 +19,7 @@ namespace API.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.0-preview.6.24327.4");
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0-preview.6.24327.4");
|
||||||
|
|
||||||
modelBuilder.Entity("API.Models.User", b =>
|
modelBuilder.Entity("AuthorizationService.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace API.Migrations
|
namespace AuthorizationService.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class CreateUser : Migration
|
public partial class CreateUser : Migration
|
@ -1,6 +1,6 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using API;
|
using AuthorizationService;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace API.Migrations
|
namespace AuthorizationService.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(AppDBContext))]
|
[DbContext(typeof(AppDBContext))]
|
||||||
[Migration("20240813075158_ChangedUserWithGuid")]
|
[Migration("20240813075158_ChangedUserWithGuid")]
|
||||||
@ -20,7 +20,7 @@ namespace API.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
||||||
|
|
||||||
modelBuilder.Entity("API.Models.User", b =>
|
modelBuilder.Entity("AuthorizationService.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace API.Migrations
|
namespace AuthorizationService.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class ChangedUserWithGuid : Migration
|
public partial class ChangedUserWithGuid : Migration
|
@ -1,6 +1,6 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using API;
|
using AuthorizationService;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace API.Migrations
|
namespace AuthorizationService.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(AppDBContext))]
|
[DbContext(typeof(AppDBContext))]
|
||||||
[Migration("20240813112418_NoMoreGuid")]
|
[Migration("20240813112418_NoMoreGuid")]
|
||||||
@ -20,7 +20,7 @@ namespace API.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
||||||
|
|
||||||
modelBuilder.Entity("API.Models.User", b =>
|
modelBuilder.Entity("AuthorizationService.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace API.Migrations
|
namespace AuthorizationService.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class NoMoreGuid : Migration
|
public partial class NoMoreGuid : Migration
|
54
services/AuthorizationService/Migrations/20240816102314_removedSaltAndBackdoor.Designer.cs
generated
Normal file
54
services/AuthorizationService/Migrations/20240816102314_removedSaltAndBackdoor.Designer.cs
generated
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// <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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
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: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using API;
|
using AuthorizationService;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace API.Migrations
|
namespace AuthorizationService.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(AppDBContext))]
|
[DbContext(typeof(AppDBContext))]
|
||||||
partial class AppDBContextModelSnapshot : ModelSnapshot
|
partial class AppDBContextModelSnapshot : ModelSnapshot
|
||||||
@ -17,7 +17,7 @@ namespace API.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
||||||
|
|
||||||
modelBuilder.Entity("API.Models.User", b =>
|
modelBuilder.Entity("AuthorizationService.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
@ -35,14 +35,6 @@ namespace API.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");
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
namespace API.Models
|
namespace AuthorizationService.Models
|
||||||
{
|
{
|
||||||
public class BaseModel
|
public class BaseModel
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace API.Models;
|
namespace AuthorizationService.Models;
|
||||||
|
|
||||||
public class User : BaseModel
|
public class User : BaseModel
|
||||||
{
|
{
|
||||||
@ -8,8 +8,6 @@ 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
|
||||||
@ -30,5 +28,12 @@ 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; }
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
|
|
||||||
namespace API.Persistence.Repositories
|
namespace AuthorizationService.Persistence.Repositories
|
||||||
{
|
{
|
||||||
public interface IUserRepository
|
public interface IUserRepository
|
||||||
{
|
{
|
||||||
@ -10,5 +10,6 @@ namespace API.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
using API.Models;
|
using AuthorizationService.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
|
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
|
||||||
|
|
||||||
namespace API.Persistence.Repositories
|
namespace AuthorizationService.Persistence.Repositories
|
||||||
{
|
{
|
||||||
public class UserRepository(AppDBContext context) : IUserRepository
|
public class UserRepository(AppDBContext context) : IUserRepository
|
||||||
{
|
{
|
||||||
@ -21,7 +21,6 @@ namespace API.Persistence.Repositories
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|
||||||
return new User();
|
return new User();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +57,21 @@ namespace API.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);
|
@ -1,13 +1,13 @@
|
|||||||
using API.Application.Users.Commands;
|
using AuthorizationService.Application.Users.Commands;
|
||||||
using API.Application.Users.Queries;
|
using AuthorizationService.Application.Users.Queries;
|
||||||
using API.Persistence.Repositories;
|
using AuthorizationService.Persistence.Repositories;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace API
|
namespace AuthorizationService
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
@ -36,6 +36,7 @@ namespace API
|
|||||||
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>();
|
@ -7,7 +7,7 @@ Environment=DOTNET_ROOT=/home/reimar/.dotnet
|
|||||||
Environment=PATH=$PATH:/home/reimar/.dotnet
|
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/AuthorizationService --urls=http://0.0.0.0:5001
|
||||||
Type=simple
|
Type=simple
|
||||||
|
|
||||||
[Install]
|
[Install]
|
@ -1,10 +1,11 @@
|
|||||||
mod auth;
|
mod auth;
|
||||||
mod models;
|
mod models;
|
||||||
|
|
||||||
use actix_web::{get, Responder, HttpResponse, HttpServer, App, web};
|
use actix_web::{get, post, delete, 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;
|
||||||
@ -51,6 +52,49 @@ 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();
|
||||||
@ -81,6 +125,8 @@ 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