feat: add Docker support, refactor DB layer, update API responses
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Environment variables for family_safety_tracker
|
||||
# JWT secret used for signing tokens
|
||||
JWT_SECRET=your-super-secret-key-change-me
|
||||
# Secret pepper added to passwords before hashing (should be random and kept secret)
|
||||
PASSWORD_PEPPER=your-random-pepper-string-change-me
|
||||
# Database connection settings
|
||||
POSTGRES_HOST="db"
|
||||
POSTGRES_PORT="5432"
|
||||
POSTGRES_DB="family_safety"
|
||||
POSTGRES_USER="postgres"
|
||||
POSTGRES_PASSWORD="postgres"
|
||||
# TOKEN_LIFETIME in minutes
|
||||
TOKEN_LIFETIME=600
|
||||
@@ -18,11 +18,11 @@ class DatabaseProvider {
|
||||
Future<void> initialize() async {
|
||||
final dotenv = DotEnv();
|
||||
|
||||
final host = dotenv['POSTGRES_HOST'] ?? 'localhost';
|
||||
final host = dotenv['POSTGRES_HOST'] ?? 'db';
|
||||
final port = int.parse(dotenv['POSTGRES_PORT'] ?? '5432');
|
||||
final databaseName = dotenv['POSTGRES_DB'] ?? 'family_safety';
|
||||
final username = dotenv['POSTGRES_USER'] ?? 'postgres';
|
||||
final password = dotenv['POSTGRES_PASSWORD'] ?? '';
|
||||
final password = dotenv['POSTGRES_PASSWORD'] ?? 'postgres';
|
||||
|
||||
try {
|
||||
final defaultConnection = await Connection.open(
|
||||
@@ -43,8 +43,7 @@ class DatabaseProvider {
|
||||
|
||||
if (results.isEmpty) {
|
||||
await defaultConnection.execute(
|
||||
Sql.named('CREATE DATABASE @dbName'),
|
||||
parameters: {'dbName': databaseName},
|
||||
Sql('CREATE DATABASE $databaseName'),
|
||||
);
|
||||
print('Database $databaseName created.');
|
||||
} else {
|
||||
@@ -78,7 +77,7 @@ class DatabaseProvider {
|
||||
await _dbConnection.execute(
|
||||
Sql.named('''
|
||||
CREATE TABLE IF NOT EXISTS geopositions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
x_value DOUBLE PRECISION NOT NULL,
|
||||
y_value DOUBLE PRECISION NOT NULL,
|
||||
last_update TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
@@ -265,7 +264,7 @@ class DatabaseProvider {
|
||||
Future<Geoposition?> getLatestPosition() async {
|
||||
final results = await _dbConnection.execute(
|
||||
Sql.named('''
|
||||
SELECT id, x_value, y_value, last_update, expires_at
|
||||
SELECT x_value, y_value, last_update, expires_at
|
||||
FROM geopositions
|
||||
WHERE expires_at > NOW()
|
||||
ORDER BY last_update DESC
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
||||
import 'package:dotenv/dotenv.dart';
|
||||
|
||||
class AuthMiddleware {
|
||||
final Handler handler;
|
||||
final Future<Response> Function(Request, String) handler;
|
||||
|
||||
AuthMiddleware(this.handler);
|
||||
|
||||
@@ -19,11 +19,15 @@ class AuthMiddleware {
|
||||
try {
|
||||
final dotenv = DotEnv();
|
||||
final secret = dotenv['JWT_SECRET'] ?? '';
|
||||
final decoded = JWT.verify(token, SecretKey(secret));
|
||||
final jwt = JWT.verify(token, SecretKey(secret));
|
||||
final payload = jwt.payload;
|
||||
final login = payload['login'] as String;
|
||||
|
||||
return handler(request);
|
||||
} catch (e) {
|
||||
return Response(401, body: 'Invalid or expired token');
|
||||
return handler(request, login);
|
||||
} on JWTExpiredException {
|
||||
return Response(401, body: 'Token expired');
|
||||
} on JWTException {
|
||||
return Response(401, body: 'Invalid token');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,10 +44,7 @@ class AuthRoutes {
|
||||
final token = jwt.sign(SecretKey(secret));
|
||||
|
||||
await database.createLog(login, 'Successful login');
|
||||
return Response(200, body: jsonEncode({
|
||||
'user': user.toMap(),
|
||||
'token': token
|
||||
}));
|
||||
return Response(200, body: jsonEncode(token));
|
||||
}
|
||||
|
||||
Future<Response> _register(Request request) async {
|
||||
@@ -57,9 +54,9 @@ class AuthRoutes {
|
||||
final login = data['login'];
|
||||
final password = data['password'];
|
||||
|
||||
final user = await database.createUser(login, password);
|
||||
await database.createUser(login, password);
|
||||
await database.createLog(login, 'User registration');
|
||||
return Response(201, body: jsonEncode(user.toMap()));
|
||||
return Response(201);
|
||||
}
|
||||
|
||||
Future<Response> _watch(Request request, String login) async {
|
||||
@@ -78,6 +75,11 @@ class AuthRoutes {
|
||||
}
|
||||
|
||||
await database.createLog(login, 'Accessed share link');
|
||||
return Response(200, body: jsonEncode(position.toJson()));
|
||||
return Response(200, body: jsonEncode({
|
||||
'x': position.xValue,
|
||||
'y': position.yValue,
|
||||
'last_update': position.lastUpdate,
|
||||
'expires_at': position.expiresAt,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class GeoRoutes {
|
||||
return router;
|
||||
}
|
||||
|
||||
Future<Response> _updatePosition(Request request) async {
|
||||
Future<Response> _updatePosition(Request request, String login) async {
|
||||
final id = int.parse(request.url.queryParameters['id']!);
|
||||
final body = await request.readAsString();
|
||||
final data = jsonDecode(body);
|
||||
@@ -24,11 +24,12 @@ class GeoRoutes {
|
||||
final x = data['x'];
|
||||
final y = data['y'];
|
||||
|
||||
final position = await database.updatePosition(id, x, y);
|
||||
return Response(200, body: position.toJson());
|
||||
await database.updatePosition(id, x, y);
|
||||
await database.createLog(login, 'Updated position id=$id');
|
||||
return Response(200);
|
||||
}
|
||||
|
||||
Future<Response> _createShare(Request request) async {
|
||||
Future<Response> _createShare(Request request, String login) async {
|
||||
final body = await request.readAsString();
|
||||
final data = jsonDecode(body);
|
||||
|
||||
@@ -38,6 +39,7 @@ class GeoRoutes {
|
||||
final position = await database.createPosition(x, y);
|
||||
final shareId = database.createShareId();
|
||||
|
||||
await database.createLog(login, 'Created share link geo_id=${position.id}');
|
||||
return Response(201, body: jsonEncode({
|
||||
'geo_id': position.id,
|
||||
'share_id': shareId
|
||||
|
||||
+3
-3
@@ -21,8 +21,8 @@ void main(List<String> args) async {
|
||||
final geoRoutes = GeoRoutes(database);
|
||||
|
||||
final router = Router()
|
||||
..mount('', authRoutes.routes.call)
|
||||
..mount('', geoRoutes.routes.call)
|
||||
..mount('/', authRoutes.routes.call)
|
||||
..mount('/', geoRoutes.routes.call)
|
||||
..get('/', (Request req) => Response.ok('Family Safety Tracker API\n'));
|
||||
|
||||
final handler = Pipeline()
|
||||
@@ -30,7 +30,7 @@ void main(List<String> args) async {
|
||||
.addHandler(router.call);
|
||||
|
||||
final ip = InternetAddress.anyIPv4;
|
||||
final port = int.parse(Platform.environment['PORT'] ?? '8080');
|
||||
final port = int.parse(Platform.environment['PORT'] ?? '9090');
|
||||
final server = await serve(handler, ip, port);
|
||||
print('Server listening on port ${server.port}');
|
||||
}
|
||||
Reference in New Issue
Block a user