diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/database/database_provider.dart b/bin/database/database_provider.dart
index d634746..86bda3c 100644
--- a/bin/database/database_provider.dart
+++ b/bin/database/database_provider.dart
@@ -12,7 +12,7 @@ import '../models/log.dart';
class DatabaseProvider {
late Connection _dbConnection;
- final Map _shareLinks = {};
+ final Map _shareLinks = {};
final _uuid = const Uuid();
Future initialize() async {
@@ -79,11 +79,9 @@ class DatabaseProvider {
Sql.named('''
CREATE TABLE IF NOT EXISTS geopositions (
id SERIAL PRIMARY KEY,
- user_id INTEGER NOT NULL REFERENCES users(id),
x_value DOUBLE PRECISION NOT NULL,
y_value DOUBLE PRECISION NOT NULL,
- datetime TIMESTAMP NOT NULL DEFAULT NOW(),
- lifetime INTERVAL NOT NULL,
+ last_update TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL
)
'''),
@@ -204,24 +202,20 @@ class DatabaseProvider {
// ==================== Geoposition operations ====================
Future createPosition(
- int userId,
double x,
double y,
- Duration lifetime,
) async {
- final expiresAt = DateTime.now().add(lifetime);
+ final expiresAt = DateTime.now().add(const Duration(hours: 24));
final results = await _dbConnection.execute(
Sql.named('''
- INSERT INTO geopositions (user_id, x_value, y_value, datetime, lifetime, expires_at)
- VALUES (@userId, @xValue, @yValue, NOW(), @lifetime, @expiresAt)
- RETURNING id, user_id, x_value, y_value, datetime, lifetime, expires_at
+ INSERT INTO geopositions (x_value, y_value, last_update, expires_at)
+ VALUES (@xValue, @yValue, NOW(), @expiresAt)
+ RETURNING id, x_value, y_value, last_update, expires_at
'''),
parameters: {
- 'userId': userId,
'xValue': x,
'yValue': y,
- 'lifetime': _toInterval(lifetime),
'expiresAt': expiresAt.toIso8601String(),
},
);
@@ -229,34 +223,28 @@ class DatabaseProvider {
final row = results.first;
return Geoposition(
id: int.parse(row[0].toString()),
- userId: int.parse(row[1].toString()),
- xValue: double.parse(row[2].toString()),
- yValue: double.parse(row[3].toString()),
- datetime: DateTime.parse(row[4].toString()),
- lifetime: lifetime,
- expiresAt: DateTime.parse(row[6].toString()),
+ xValue: double.parse(row[1].toString()),
+ yValue: double.parse(row[2].toString()),
+ lastUpdate: DateTime.parse(row[3].toString()),
+ expiresAt: DateTime.parse(row[4].toString()),
);
}
Future updatePosition(
- int userId,
double x,
double y,
- Duration lifetime,
) async {
- final expiresAt = DateTime.now().add(lifetime);
+ final expiresAt = DateTime.now().add(const Duration(hours: 24));
final results = await _dbConnection.execute(
Sql.named('''
- INSERT INTO geopositions (user_id, x_value, y_value, datetime, lifetime, expires_at)
- VALUES (@userId, @xValue, @yValue, NOW(), @lifetime, @expiresAt)
- RETURNING id, user_id, x_value, y_value, datetime, lifetime, expires_at
+ INSERT INTO geopositions (x_value, y_value, last_update, expires_at)
+ VALUES (@xValue, @yValue, NOW(), @expiresAt)
+ RETURNING id, x_value, y_value, last_update, expires_at
'''),
parameters: {
- 'userId': userId,
'xValue': x,
'yValue': y,
- 'lifetime': _toInterval(lifetime),
'expiresAt': expiresAt.toIso8601String(),
},
);
@@ -264,25 +252,22 @@ class DatabaseProvider {
final row = results.first;
return Geoposition(
id: int.parse(row[0].toString()),
- userId: int.parse(row[1].toString()),
- xValue: double.parse(row[2].toString()),
- yValue: double.parse(row[3].toString()),
- datetime: DateTime.parse(row[4].toString()),
- lifetime: lifetime,
- expiresAt: DateTime.parse(row[6].toString()),
+ xValue: double.parse(row[1].toString()),
+ yValue: double.parse(row[2].toString()),
+ lastUpdate: DateTime.parse(row[3].toString()),
+ expiresAt: DateTime.parse(row[4].toString()),
);
}
- Future getLatestPosition(int userId) async {
+ Future getLatestPosition() async {
final results = await _dbConnection.execute(
Sql.named('''
- SELECT id, user_id, x_value, y_value, datetime, lifetime, expires_at
+ SELECT id, x_value, y_value, last_update, expires_at
FROM geopositions
- WHERE user_id = @userId AND expires_at > NOW()
- ORDER BY datetime DESC
+ WHERE expires_at > NOW()
+ ORDER BY last_update DESC
LIMIT 1
'''),
- parameters: {'userId': userId},
);
if (results.isEmpty) return null;
@@ -290,13 +275,10 @@ class DatabaseProvider {
final row = results.first;
return Geoposition(
id: int.parse(row[0].toString()),
- userId: int.parse(row[1].toString()),
- xValue: double.parse(row[2].toString()),
- yValue: double.parse(row[3].toString()),
- datetime: DateTime.parse(row[4].toString()),
- lifetime: Duration(
- seconds: int.tryParse(row[5].toString()) ?? 0),
- expiresAt: DateTime.parse(row[6].toString()),
+ xValue: double.parse(row[1].toString()),
+ yValue: double.parse(row[2].toString()),
+ lastUpdate: DateTime.parse(row[3].toString()),
+ expiresAt: DateTime.parse(row[4].toString()),
);
}
@@ -308,14 +290,14 @@ class DatabaseProvider {
// ==================== Share operations ====================
- String createShareId(int userId) {
+ String createShareId() {
final uniqueId = _uuid.v4();
- _shareLinks[uniqueId] = userId;
+ _shareLinks[uniqueId] = true;
return uniqueId;
}
- int? getUserIdByShareId(String uniqueId) {
- return _shareLinks[uniqueId];
+ bool isValidShareId(String uniqueId) {
+ return _shareLinks[uniqueId] == true;
}
// ==================== Log operations ====================
@@ -349,8 +331,4 @@ class DatabaseProvider {
.toList();
}
- String _toInterval(Duration duration) {
- final seconds = duration.inSeconds;
- return '$seconds seconds';
- }
-}
\ No newline at end of file
+ }
\ No newline at end of file
diff --git a/bin/database/migration.sql b/bin/database/migration.sql
index 240ce06..a7740d9 100644
--- a/bin/database/migration.sql
+++ b/bin/database/migration.sql
@@ -6,11 +6,9 @@ CREATE TABLE IF NOT EXISTS users (
CREATE TABLE IF NOT EXISTS geopositions (
id SERIAL PRIMARY KEY,
- user_id INTEGER NOT NULL REFERENCES users(id),
x_value DOUBLE PRECISION NOT NULL,
y_value DOUBLE PRECISION NOT NULL,
- datetime TIMESTAMP NOT NULL DEFAULT NOW(),
- lifetime INTERVAL NOT NULL,
+ last_update TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL
);
diff --git a/bin/middleware/auth_middleware.dart b/bin/middleware/auth_middleware.dart
new file mode 100644
index 0000000..92d4576
--- /dev/null
+++ b/bin/middleware/auth_middleware.dart
@@ -0,0 +1,29 @@
+import 'package:shelf/shelf.dart';
+import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
+import 'package:dotenv/dotenv.dart';
+
+class AuthMiddleware {
+ final Handler handler;
+
+ AuthMiddleware(this.handler);
+
+ Future call(Request request) async {
+ final authorization = request.headers['authorization'];
+
+ if (authorization == null || !authorization.startsWith('Bearer ')) {
+ return Response(401, body: 'Authorization header missing or invalid');
+ }
+
+ final token = authorization.substring(7);
+
+ try {
+ final dotenv = DotEnv();
+ final secret = dotenv['JWT_SECRET'] ?? '';
+ final decoded = JWT.verify(token, SecretKey(secret));
+
+ return handler(request);
+ } catch (e) {
+ return Response(401, body: 'Invalid or expired token');
+ }
+ }
+}
\ No newline at end of file
diff --git a/bin/models/geoposition.dart b/bin/models/geoposition.dart
index a9e08b3..396b9c6 100644
--- a/bin/models/geoposition.dart
+++ b/bin/models/geoposition.dart
@@ -1,19 +1,15 @@
class Geoposition {
final int id;
- final int userId;
final double xValue;
final double yValue;
- final DateTime datetime;
- final Duration lifetime;
+ final DateTime lastUpdate;
final DateTime expiresAt;
Geoposition({
required this.id,
- required this.userId,
required this.xValue,
required this.yValue,
- required this.datetime,
- required this.lifetime,
+ required this.lastUpdate,
required this.expiresAt,
});
@@ -22,7 +18,7 @@ class Geoposition {
'id': id,
'x': xValue,
'y': yValue,
- 'datetime': datetime.toIso8601String(),
+ 'lastUpdate': lastUpdate.toIso8601String(),
'remainingSeconds': expiresAt.difference(DateTime.now()).inSeconds,
};
}
@@ -30,11 +26,9 @@ class Geoposition {
factory Geoposition.fromMap(Map map) {
return Geoposition(
id: map['id'],
- userId: map['user_id'],
xValue: map['x_value'].toDouble(),
yValue: map['y_value'].toDouble(),
- datetime: map['datetime'] as DateTime,
- lifetime: Duration(seconds: map['lifetime']),
+ lastUpdate: map['last_update'] as DateTime,
expiresAt: map['expires_at'] as DateTime,
);
}
diff --git a/bin/routes/auth_routes.dart b/bin/routes/auth_routes.dart
index 4ce4420..5e7235f 100644
--- a/bin/routes/auth_routes.dart
+++ b/bin/routes/auth_routes.dart
@@ -1,7 +1,10 @@
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:bcrypt/bcrypt.dart';
+import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
+import 'package:dotenv/dotenv.dart';
import '../database/database_provider.dart';
+import '../middleware/auth_middleware.dart';
import 'dart:convert';
class AuthRoutes {
@@ -12,7 +15,8 @@ class AuthRoutes {
Router get routes {
final router = Router();
router.post('/login', _login);
- router.get('/watch', _watch);
+ router.post('/reg', _register);
+ router.get('/watch', AuthMiddleware(_watch).call);
return router;
}
@@ -29,19 +33,40 @@ class AuthRoutes {
return Response(401, body: 'Invalid credentials');
}
- return Response(200, body: jsonEncode({'user': user.toMap()}));
+ // Генерация JWT токена
+ final dotenv = DotEnv();
+ final secret = dotenv['JWT_SECRET'] ?? '';
+ final jwt = JWT(
+ {'user_id': user.id, 'login': user.login},
+ issuer: 'family_safety_tracker'
+ );
+ final token = jwt.sign(SecretKey(secret));
+
+ return Response(200, body: jsonEncode({
+ 'user': user.toMap(),
+ 'token': token
+ }));
+ }
+
+ Future _register(Request request) async {
+ final body = await request.readAsString();
+ final data = jsonDecode(body);
+
+ final login = data['login'];
+ final password = data['password'];
+
+ final user = await database.createUser(login, password);
+ return Response(201, body: jsonEncode(user.toMap()));
}
Future _watch(Request request) async {
final uniqueId = request.url.queryParameters['unique_id'];
- final userId = database.getUserIdByShareId(uniqueId!);
-
- if (userId == null) {
+ if (!database.isValidShareId(uniqueId!)) {
return Response(404, body: 'Share link not found');
}
- final position = await database.getLatestPosition(userId);
+ final position = await database.getLatestPosition();
if (position == null) {
return Response(404, body: 'No position available');
diff --git a/bin/routes/geo_routes.dart b/bin/routes/geo_routes.dart
index 0f3d808..ddd8337 100644
--- a/bin/routes/geo_routes.dart
+++ b/bin/routes/geo_routes.dart
@@ -1,6 +1,7 @@
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import '../database/database_provider.dart';
+import '../middleware/auth_middleware.dart';
import 'dart:convert';
class GeoRoutes {
@@ -10,9 +11,9 @@ class GeoRoutes {
Router get routes {
final router = Router();
- router.post('/geo', _createPosition);
- router.put('/geo', _updatePosition);
- router.post('/share', _createShare);
+ router.post('/geo', AuthMiddleware(_createPosition).call);
+ router.put('/geo', AuthMiddleware(_updatePosition).call);
+ router.post('/share', AuthMiddleware(_createShare).call);
return router;
}
@@ -20,13 +21,10 @@ class GeoRoutes {
final body = await request.readAsString();
final data = jsonDecode(body);
- final userId = data['user_id'];
final x = data['x'];
final y = data['y'];
- final lifetimeSeconds = data['lifetime'];
- final lifetime = Duration(seconds: lifetimeSeconds);
- final position = await database.createPosition(userId, x, y, lifetime);
+ final position = await database.createPosition(x, y);
return Response(201, body: position.toJson());
}
@@ -34,22 +32,15 @@ class GeoRoutes {
final body = await request.readAsString();
final data = jsonDecode(body);
- final userId = data['user_id'];
final x = data['x'];
final y = data['y'];
- final lifetimeSeconds = data['lifetime'];
- final lifetime = Duration(seconds: lifetimeSeconds);
- final position = await database.updatePosition(userId, x, y, lifetime);
+ final position = await database.updatePosition(x, y);
return Response(200, body: position.toJson());
}
Future _createShare(Request request) async {
- final body = await request.readAsString();
- final data = jsonDecode(body);
-
- final userId = data['user_id'];
- final shareId = database.createShareId(userId);
+ final shareId = database.createShareId();
return Response(200, body: jsonEncode({'share_id': shareId}));
}
diff --git a/bin/routes/user_routes.dart b/bin/routes/user_routes.dart
deleted file mode 100644
index 0ca124a..0000000
--- a/bin/routes/user_routes.dart
+++ /dev/null
@@ -1,54 +0,0 @@
-import 'package:shelf/shelf.dart';
-import 'package:shelf_router/shelf_router.dart';
-import '../database/database_provider.dart';
-import 'dart:convert';
-
-class UserRoutes {
- final DatabaseProvider database;
-
- UserRoutes(this.database);
-
- Router get routes {
- final router = Router();
- router.get('/user', _getAllUsers);
- router.post('/user', _createUser);
- router.put('/user/', _updateUser);
- router.delete('/user/', _deleteUser);
- return router;
- }
-
- Future _getAllUsers(Request request) async {
- final users = await database.getAllUsers();
- return Response(200, body: jsonEncode(users.map((u) => u.toMap()).toList()));
- }
-
- Future _createUser(Request request) async {
- final body = await request.readAsString();
- final data = jsonDecode(body);
-
- final login = data['login'];
- final password = data['password'];
-
- final user = await database.createUser(login, password);
- return Response(201, body: jsonEncode(user.toMap()));
- }
-
- Future _updateUser(Request request) async {
- final id = int.parse(request.params['id']!);
-
- final body = await request.readAsString();
- final data = jsonDecode(body);
-
- final login = data['login'];
- final password = data['password'];
-
- final user = await database.updateUser(id, login, password);
- return Response(200, body: jsonEncode(user.toMap()));
- }
-
- Future _deleteUser(Request request) async {
- final id = int.parse(request.params['id']!);
- await database.deleteUser(id);
- return Response(204);
- }
-}
\ No newline at end of file
diff --git a/bin/server.dart b/bin/server.dart
index 14ab860..cb22ad1 100644
--- a/bin/server.dart
+++ b/bin/server.dart
@@ -7,7 +7,6 @@ import 'package:shelf_router/shelf_router.dart';
import 'database/database_provider.dart';
import 'routes/auth_routes.dart';
-import 'routes/user_routes.dart';
import 'routes/geo_routes.dart';
void main(List args) async {
@@ -19,12 +18,10 @@ void main(List args) async {
});
final authRoutes = AuthRoutes(database);
- final userRoutes = UserRoutes(database);
final geoRoutes = GeoRoutes(database);
final router = Router()
..mount('', authRoutes.routes.call)
- ..mount('', userRoutes.routes.call)
..mount('', geoRoutes.routes.call)
..get('/', (Request req) => Response.ok('Family Safety Tracker API\n'));