WIP still, tuning up auth and room wildcard with middleware
This commit is contained in:
parent
37e168e46b
commit
544d3b45ba
.gitignoreflake.nix
backend
.dart_frog
drift/schemas/db
lib
main.dartroutes
shared_models/lib
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
###
|
||||
# Dart
|
||||
###
|
||||
|
||||
# Files and directories created by pub
|
||||
**/.dart_tool/
|
||||
**/.packages
|
||||
@ -72,3 +73,6 @@ app.*.map.json
|
||||
|
||||
**/llm-chat.md
|
||||
|
||||
# DB files
|
||||
*.sqlite*
|
||||
|
||||
|
57
backend/.dart_frog/server.dart
Normal file
57
backend/.dart_frog/server.dart
Normal file
@ -0,0 +1,57 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, implicit_dynamic_list_literal
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
|
||||
import '../main.dart' as entrypoint;
|
||||
import '../routes/index.dart' as index;
|
||||
import '../routes/create_room.dart' as create_room;
|
||||
import '../routes/room/[roomCode]/join.dart' as room_$room_code_join;
|
||||
import '../routes/auth/index.dart' as auth_index;
|
||||
|
||||
import '../routes/_middleware.dart' as middleware;
|
||||
import '../routes/room/[roomCode]/_middleware.dart' as room_$room_code_middleware;
|
||||
|
||||
void main() async {
|
||||
final address = InternetAddress.tryParse('') ?? InternetAddress.anyIPv6;
|
||||
final port = int.tryParse(Platform.environment['PORT'] ?? '8080') ?? 8080;
|
||||
hotReload(() => createServer(address, port));
|
||||
}
|
||||
|
||||
Future<HttpServer> createServer(InternetAddress address, int port) {
|
||||
final handler = Cascade().add(buildRootHandler()).handler;
|
||||
return entrypoint.run(handler, address, port);
|
||||
}
|
||||
|
||||
Handler buildRootHandler() {
|
||||
final pipeline = const Pipeline().addMiddleware(middleware.middleware);
|
||||
final router = Router()
|
||||
..mount('/auth', (context) => buildAuthHandler()(context))
|
||||
..mount('/room/<roomCode>', (context,roomCode,) => buildRoom$roomCodeHandler(roomCode,)(context))
|
||||
..mount('/', (context) => buildHandler()(context));
|
||||
return pipeline.addHandler(router);
|
||||
}
|
||||
|
||||
Handler buildAuthHandler() {
|
||||
final pipeline = const Pipeline();
|
||||
final router = Router()
|
||||
..all('/', (context) => auth_index.onRequest(context,));
|
||||
return pipeline.addHandler(router);
|
||||
}
|
||||
|
||||
Handler buildRoom$roomCodeHandler(String roomCode,) {
|
||||
final pipeline = const Pipeline().addMiddleware(room_$room_code_middleware.middleware);
|
||||
final router = Router()
|
||||
..all('/join', (context) => room_$room_code_join.onRequest(context,roomCode,));
|
||||
return pipeline.addHandler(router);
|
||||
}
|
||||
|
||||
Handler buildHandler() {
|
||||
final pipeline = const Pipeline();
|
||||
final router = Router()
|
||||
..all('/', (context) => index.onRequest(context,))..all('/create_room', (context) => create_room.onRequest(context,));
|
||||
return pipeline.addHandler(router);
|
||||
}
|
||||
|
1
backend/drift/schemas/db/drift_schema_v1.json
Normal file
1
backend/drift/schemas/db/drift_schema_v1.json
Normal file
@ -0,0 +1 @@
|
||||
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"game_rooms","was_declared_in_moor":false,"columns":[{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"UNIQUE","dialectAwareDefaultConstraints":{"sqlite":"UNIQUE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"status","getter_name":"status","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<GameStatus>(GameStatus.values)","dart_type_name":"GameStatus"}},{"name":"code","getter_name":"code","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":6,"max":6}}]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"strict":true}},{"id":1,"references":[0],"type":"table","data":{"name":"users","was_declared_in_moor":false,"columns":[{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"UNIQUE","dialectAwareDefaultConstraints":{"sqlite":"UNIQUE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"game_room_uuid","getter_name":"gameRoomUuid","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES game_rooms (uuid)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES game_rooms (uuid)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":2,"max":32}}]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"strict":true}},{"id":2,"references":[0],"type":"index","data":{"on":0,"name":"idx_game_rooms_code","sql":"CREATE UNIQUE INDEX IF NOT EXISTS idx_game_rooms_code \nON game_rooms(code) \nWHERE status IN (\"opened\", \"running\");\n","unique":true,"columns":[]}}]}
|
@ -3,27 +3,43 @@ import 'dart:io';
|
||||
import 'package:backend/database.dart';
|
||||
import 'package:backend/service/db_access.dart';
|
||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_models/user.dart';
|
||||
|
||||
final jwtSecret = _getSecret();
|
||||
const expTimeSecs = 3600;
|
||||
|
||||
final log = Logger('Authenticator');
|
||||
|
||||
enum JWTTokenStatus {
|
||||
valid,
|
||||
expired,
|
||||
invalid,
|
||||
}
|
||||
|
||||
class Authenticator {
|
||||
Future<String?> generateToken({required String username}) async {
|
||||
final newUser = await Db.createUser(username: username);
|
||||
Future<String?> generateToken(CreateUserRequest req) async {
|
||||
final newUser = await Db.createUser(username: req.username, roomCode: req.roomCode);
|
||||
if (newUser == null) return null;
|
||||
|
||||
final iat = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
final jwt = JWT(
|
||||
{
|
||||
'uid': newUser.uuid,
|
||||
'roomUuid': newUser.gameRoomUuid,
|
||||
'iat': iat,
|
||||
'exp': iat + expTimeSecs,
|
||||
},
|
||||
);
|
||||
|
||||
return jwt.sign(SecretKey(jwtSecret));
|
||||
}
|
||||
|
||||
Future<User?> verifyToken(
|
||||
Future<(User?, JWTTokenStatus)> verifyToken(
|
||||
String token,
|
||||
) async {
|
||||
try {
|
||||
log.info('Verifying jwt: ${token.substring(0, 10)}...${token.substring(token.length - 10)}');
|
||||
final payload = JWT.verify(
|
||||
token,
|
||||
SecretKey(jwtSecret),
|
||||
@ -31,16 +47,47 @@ class Authenticator {
|
||||
|
||||
final payloadData = payload.payload as Map<String, dynamic>;
|
||||
|
||||
final iat = payloadData['iat'] as int;
|
||||
final exp = payloadData['exp'] as int;
|
||||
|
||||
if (iat + expTimeSecs != exp || DateTime.now().millisecondsSinceEpoch ~/ 1000 > exp) {
|
||||
return (null, JWTTokenStatus.expired);
|
||||
}
|
||||
|
||||
final uuid = payloadData['uuid'] as String;
|
||||
return await Db.getUser(uuid);
|
||||
return (await Db.getUser(uuid), JWTTokenStatus.valid);
|
||||
} catch (e) {
|
||||
return null;
|
||||
return (null, JWTTokenStatus.invalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load any env vars inside root of project's .env file, then looks for JWT_TOKEN_SECRET
|
||||
String _getSecret() {
|
||||
final secret = Platform.environment['JWT_TOKEN_SECRET'];
|
||||
final envs = {...Platform.environment};
|
||||
try {
|
||||
final result = Process.runSync('git', ['rev-parse', '--show-toplevel']);
|
||||
if (result.exitCode != 0) {
|
||||
log.warning('Failed to get git root directory: ${result.stderr}');
|
||||
throw Exception('Failed to get git root directory');
|
||||
}
|
||||
final rootDir = (result.stdout as String).trim();
|
||||
final envFile = File('$rootDir/.env');
|
||||
if (envFile.existsSync()) {
|
||||
for (final line in envFile.readAsLinesSync()) {
|
||||
if (line.trim().isEmpty || line.startsWith('#')) continue;
|
||||
final parts = line.split('=');
|
||||
if (parts.length != 2) continue;
|
||||
final key = parts[0].trim();
|
||||
final value = parts[1].trim();
|
||||
envs[key] = value;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.warning('Failed to load .env file: $e');
|
||||
}
|
||||
// check for secret
|
||||
final secret = envs['JWT_TOKEN_SECRET'];
|
||||
if (secret == null || secret.isEmpty) {
|
||||
throw Exception('JWT secret not configured. Define JWT_TOKEN_SECRET in environment.');
|
||||
} else {
|
||||
|
@ -7,21 +7,34 @@ part 'database.g.dart';
|
||||
|
||||
class Users extends Table {
|
||||
TextColumn get uuid => text().unique()();
|
||||
TextColumn get gameRoom => text().references(GameRooms, #uuid).nullable()();
|
||||
TextColumn get gameRoomUuid => text().references(GameRooms, #uuid)();
|
||||
TextColumn get name => text().withLength(min: 2, max: 32)();
|
||||
DateTimeColumn get createdAt => dateTime().nullable()();
|
||||
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
}
|
||||
|
||||
enum GameStatus {
|
||||
opened,
|
||||
open,
|
||||
running,
|
||||
closed,
|
||||
cancelled,
|
||||
}
|
||||
|
||||
class GameRooms extends Table {
|
||||
TextColumn get uuid => text().unique()();
|
||||
TextColumn get status => textEnum<GameStatus>()();
|
||||
TextColumn get code => text().withLength(min: 6, max: 6)();
|
||||
DateTimeColumn get createdAt => dateTime().nullable()();
|
||||
|
||||
@override
|
||||
bool get isStrict => true;
|
||||
|
||||
@override
|
||||
List<Set<Column>> get uniqueKeys => [
|
||||
{code, status},
|
||||
];
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [Users, GameRooms])
|
||||
@ -32,7 +45,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
int get schemaVersion => 1;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
return NativeDatabase.createInBackground(File('./backend.db'));
|
||||
return NativeDatabase.createInBackground(File('./db.sqlite'));
|
||||
}
|
||||
|
||||
@override
|
||||
|
47
backend/lib/middleware/auth_middleware.dart
Normal file
47
backend/lib/middleware/auth_middleware.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:backend/authenticator.dart';
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
|
||||
Authenticator? _authenticator;
|
||||
|
||||
Middleware authenticatorMiddlewareProvider() {
|
||||
return provider<Authenticator>((context) => _authenticator ??= Authenticator());
|
||||
}
|
||||
|
||||
typedef Applies = Future<bool> Function(RequestContext context);
|
||||
|
||||
Future<bool> _defaultApplies(RequestContext context) async => true;
|
||||
|
||||
Middleware tokenAuthMiddleware({
|
||||
Applies applies = _defaultApplies,
|
||||
}) {
|
||||
return (handler) => (context) async {
|
||||
if (!await applies(context)) {
|
||||
return handler(context);
|
||||
}
|
||||
final auth = context.read<Authenticator>();
|
||||
// use `auth.verifyToken(token)` to check the jwt that came in the request header bearer
|
||||
final authHeader = context.request.headers['authorization'];
|
||||
final auths = authHeader?.split(' ');
|
||||
if (authHeader == null || !authHeader.startsWith('Bearer ') || auths == null || auths.length != 2) {
|
||||
log.fine('Denied request - No Auth - ${context.request.method.value} ${context.request.uri.path}');
|
||||
return Response(statusCode: HttpStatus.unauthorized);
|
||||
}
|
||||
final token = auths.last;
|
||||
|
||||
final (user, tokStatus) = await auth.verifyToken(token);
|
||||
|
||||
if (user == null) {
|
||||
log.fine(
|
||||
'Denied request - Bad Auth:$tokStatus - ${context.request.method.value} ${context.request.uri.path}, no auth');
|
||||
return Response(statusCode: HttpStatus.unauthorized);
|
||||
}
|
||||
|
||||
return handler(
|
||||
context.provide(
|
||||
() => user,
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
24
backend/lib/middleware/logger.dart
Normal file
24
backend/lib/middleware/logger.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
final log = Logger('ServerLogger');
|
||||
|
||||
Middleware loggerMiddleware() {
|
||||
return (Handler handler) {
|
||||
return (RequestContext context) async {
|
||||
final request = context.request;
|
||||
final startTime = DateTime.now();
|
||||
|
||||
final response = await handler(context);
|
||||
|
||||
final duration = DateTime.now().difference(startTime);
|
||||
|
||||
log.info(
|
||||
'${request.method.name} ${request.uri.path} '
|
||||
'${response.statusCode} ${duration.inMilliseconds}ms',
|
||||
);
|
||||
|
||||
return response;
|
||||
};
|
||||
};
|
||||
}
|
@ -6,19 +6,40 @@ import 'package:uuid/uuid.dart';
|
||||
final log = Logger('Db');
|
||||
|
||||
class Db {
|
||||
static final _db = AppDatabase();
|
||||
|
||||
static Future<User> getUser(String uuid) {
|
||||
log.finer('Getting user $uuid');
|
||||
return AppDatabase().managers.users.filter((f) => f.uuid.equals(uuid)).get().then((u) => u.first);
|
||||
return _db.managers.users.filter((f) => f.uuid.equals(uuid)).getSingle();
|
||||
}
|
||||
|
||||
static Future<User?> createUser({required String username}) => AppDatabase()
|
||||
.managers
|
||||
.users
|
||||
.createReturningOrNull(
|
||||
(o) => o(createdAt: Value(DateTime.now()), uuid: const Uuid().v4(), name: username),
|
||||
)
|
||||
.catchError((Object err) {
|
||||
log.severe('Failed to create user', err, StackTrace.current);
|
||||
throw Exception(err.toString());
|
||||
});
|
||||
static Future<User?> createUser({required String username, required String roomCode}) async {
|
||||
final room = await _db.managers.gameRooms
|
||||
.filter((f) => f.code.equals(roomCode) & f.status.isIn([GameStatus.open, GameStatus.running]))
|
||||
.getSingleOrNull()
|
||||
.catchError((Object err) {
|
||||
log.info('Failed to find available room:$roomCode', err, StackTrace.current);
|
||||
return null;
|
||||
});
|
||||
if (room == null) return null;
|
||||
return _db.managers.users
|
||||
.createReturningOrNull(
|
||||
(o) => o(createdAt: Value(DateTime.now()), uuid: const Uuid().v4(), name: username, gameRoomUuid: room.uuid),
|
||||
)
|
||||
.catchError((Object err) {
|
||||
log.severe('Failed to create user', err, StackTrace.current);
|
||||
throw Exception(err.toString());
|
||||
});
|
||||
}
|
||||
|
||||
static Future<GameRoom> createRoom({required String roomCode}) => _db.managers.gameRooms
|
||||
.createReturning(
|
||||
(o) => o(createdAt: Value(DateTime.now()), status: GameStatus.open, uuid: const Uuid().v4(), code: roomCode),
|
||||
)
|
||||
.catchError(
|
||||
(Object err) {
|
||||
log.severe('Failed to create room', err, StackTrace.current);
|
||||
throw Exception(err.toString());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -1,17 +1,27 @@
|
||||
import 'dart:developer' as dev;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
Future<HttpServer> run(Handler handler, InternetAddress ip, int port) {
|
||||
// 1. Execute any custom code prior to starting the server...
|
||||
bool _listening = false;
|
||||
|
||||
final String logLevel = Platform.environment['LOG_LEVEL'] ?? 'INFO';
|
||||
Logger.root.level =
|
||||
Level.LEVELS.firstWhere((l) => l.name == logLevel, orElse: () => Level.INFO); // defaults to Level.INFO
|
||||
Logger.root.onRecord.listen((record) {
|
||||
stdout.writeln('${record.level.name}: ${record.time}: ${record.message}');
|
||||
});
|
||||
Future<HttpServer> run(Handler handler, InternetAddress ip, int port) async {
|
||||
final isDevelopment = (await dev.Service.getInfo()).serverUri != null;
|
||||
if (!_listening) {
|
||||
final logLevel = Platform.environment['LOG_LEVEL'] ?? (isDevelopment ? 'FINEST' : 'INFO');
|
||||
Logger.root.level =
|
||||
Level.LEVELS.firstWhere((l) => l.name == logLevel, orElse: () => Level.INFO); // defaults to Level.INFO
|
||||
Logger.root.onRecord.listen((record) {
|
||||
stdout.writeln('[${record.level.name}]:[${record.loggerName}] ${record.time}: ${record.message}');
|
||||
if (record.level.value >= Level.WARNING.value) {
|
||||
stdout.writeln(
|
||||
'[${record.level.name}]:[${record.loggerName}] ${record.error ?? "No error provided"}\n${record.stackTrace ?? "No trace provided"}',
|
||||
);
|
||||
}
|
||||
});
|
||||
_listening = true;
|
||||
}
|
||||
|
||||
return serve(handler, ip, port);
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
import 'package:backend/authenticator.dart';
|
||||
import 'package:backend/database.dart';
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
import 'package:dart_frog_auth/dart_frog_auth.dart';
|
||||
|
||||
Handler middleware(Handler handler) {
|
||||
return handler.use(
|
||||
bearerAuthentication<User>(
|
||||
authenticator: (context, token) async {
|
||||
final authenticator = context.read<Authenticator>();
|
||||
return authenticator.verifyToken(token);
|
||||
},
|
||||
// says to apply the middleware to all routes
|
||||
applies: (_) async => true,
|
||||
),
|
||||
);
|
||||
}
|
@ -1,15 +1,8 @@
|
||||
// lib/routes/tasks/_middleware.dart
|
||||
import 'package:backend/middleware/auth_middleware.dart';
|
||||
import 'package:backend/middleware/logger.dart';
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
final log = Logger('');
|
||||
|
||||
Handler middleware(Handler handler) {
|
||||
return handler.use(
|
||||
(handler) => (context) async {
|
||||
final request = context.request;
|
||||
log.info('${request.method.value} ${request.uri.path}');
|
||||
return await handler(context);
|
||||
},
|
||||
);
|
||||
return handler.use(loggerMiddleware()).use(authenticatorMiddlewareProvider());
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ import 'dart:io';
|
||||
|
||||
import 'package:backend/authenticator.dart';
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_models/user.dart';
|
||||
|
||||
final log = Logger('auth/');
|
||||
|
||||
Future<Response> onRequest(RequestContext context) async {
|
||||
// Only allow POST requests
|
||||
if (context.request.method != HttpMethod.post) {
|
||||
@ -18,28 +20,36 @@ Future<Response> onRequest(RequestContext context) async {
|
||||
|
||||
// Generate token
|
||||
final authenticator = context.read<Authenticator>();
|
||||
final token = await authenticator.generateToken(username: createUserReq.username);
|
||||
final token = await authenticator.generateToken(createUserReq);
|
||||
|
||||
if (token == null) {
|
||||
final body = CreateUserResponse(
|
||||
success: false,
|
||||
token: null,
|
||||
error: 'Room ${createUserReq.roomCode} requested is not available',
|
||||
).toJson();
|
||||
return Response.json(
|
||||
statusCode: HttpStatus.internalServerError,
|
||||
body: {'error': 'Failed to generate token'},
|
||||
statusCode: HttpStatus.badRequest,
|
||||
body: body,
|
||||
);
|
||||
}
|
||||
|
||||
// Return the token
|
||||
return Response.json(
|
||||
body: {'token': token},
|
||||
);
|
||||
} on JWTParseException {
|
||||
return Response.json(
|
||||
statusCode: HttpStatus.badRequest,
|
||||
body: {'error': 'Username is required'},
|
||||
body: CreateUserResponse(token: token, success: true).toJson(),
|
||||
);
|
||||
// }
|
||||
// on JWTParseException {
|
||||
// return Response.json(
|
||||
// statusCode: HttpStatus.badRequest,
|
||||
// body: {'error': 'Username is required'},
|
||||
// );
|
||||
} catch (e) {
|
||||
log.severe('Error:', e);
|
||||
final body = CreateUserResponse(success: false, token: null, error: 'Internal server error').toJson();
|
||||
return Response.json(
|
||||
statusCode: HttpStatus.internalServerError,
|
||||
body: {'error': 'Internal server error'},
|
||||
body: body,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
42
backend/routes/create_room.dart
Normal file
42
backend/routes/create_room.dart
Normal file
@ -0,0 +1,42 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:backend/service/db_access.dart';
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_models/room.dart';
|
||||
|
||||
final log = Logger('create_room');
|
||||
|
||||
Future<Response> onRequest(RequestContext context) async {
|
||||
// Only allow POST requests
|
||||
if (context.request.method != HttpMethod.post) {
|
||||
return Response(statusCode: HttpStatus.methodNotAllowed);
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate a random 6-letter room code
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
final random = Random();
|
||||
final roomCode = String.fromCharCodes(
|
||||
Iterable.generate(
|
||||
6,
|
||||
(_) => chars.codeUnitAt(random.nextInt(chars.length)),
|
||||
),
|
||||
);
|
||||
|
||||
// Create the room
|
||||
final room = await Db.createRoom(roomCode: roomCode);
|
||||
|
||||
// Return the room code
|
||||
return Response.json(
|
||||
body: CreateRoomResponse(success: true, roomCode: room.code).toJson(),
|
||||
);
|
||||
} catch (e) {
|
||||
log.severe('Error:', e);
|
||||
return Response.json(
|
||||
statusCode: HttpStatus.internalServerError,
|
||||
body: CreateRoomResponse(success: false, roomCode: null, error: 'Internal server error').toJson(),
|
||||
);
|
||||
}
|
||||
}
|
7
backend/routes/room/[roomCode]/_middleware.dart
Normal file
7
backend/routes/room/[roomCode]/_middleware.dart
Normal file
@ -0,0 +1,7 @@
|
||||
import 'package:backend/middleware/auth_middleware.dart';
|
||||
import 'package:dart_frog/dart_frog.dart';
|
||||
|
||||
// Middleware to check for jwt tokens on all routes under /room/[roomCode]/
|
||||
Handler middleware(Handler handler) {
|
||||
return handler.use(tokenAuthMiddleware());
|
||||
}
|
13
flake.nix
13
flake.nix
@ -28,15 +28,10 @@
|
||||
gtk3
|
||||
pcre
|
||||
libepoxy
|
||||
# For drift
|
||||
sqlite
|
||||
|
||||
# This group all seem not strictly necessary -- commands like
|
||||
# `flutter run -d linux` seem to *work* fine without them, but
|
||||
# the build does print messages about missing packages, like:
|
||||
# Package mount was not found in the pkg-config search path.
|
||||
# Perhaps you should add the directory containing `mount.pc'
|
||||
# to the PKG_CONFIG_PATH environment variable
|
||||
# To add to this list on NixOS upgrades, the Nix package
|
||||
# `nix-index` is handy: then `nix-locate mount.pc`.
|
||||
# Dev deps
|
||||
libuuid # for mount.pc
|
||||
xorg.libXdmcp.dev
|
||||
python310Packages.libselinux.dev # for libselinux.pc
|
||||
@ -48,11 +43,11 @@
|
||||
at-spi2-core.dev
|
||||
xorg.libXtst.out
|
||||
pcre2.dev
|
||||
|
||||
jdk11
|
||||
android-studio
|
||||
android-tools
|
||||
];
|
||||
LD_LIBRARY_PATH = "${pkgs.sqlite.out}/lib";
|
||||
shellHook = ''
|
||||
export PATH="$PATH":"$HOME/.pub-cache/bin"
|
||||
echo -e "\e[44m \e[0m"
|
||||
|
25
shared_models/lib/room.dart
Normal file
25
shared_models/lib/room.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'room.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateRoomRequest {
|
||||
final bool success;
|
||||
CreateRoomRequest({required this.success});
|
||||
factory CreateRoomRequest.fromJson(Map<String, dynamic> json) => _$CreateRoomRequestFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CreateRoomRequestToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateRoomResponse {
|
||||
final bool success;
|
||||
final String? roomCode;
|
||||
final String? error;
|
||||
|
||||
CreateRoomResponse({required this.success, required this.roomCode, this.error});
|
||||
|
||||
factory CreateRoomResponse.fromJson(Map<String, dynamic> json) => _$CreateRoomResponseFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CreateRoomResponseToJson(this);
|
||||
}
|
@ -2,30 +2,26 @@ import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'user.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class User {
|
||||
final String id;
|
||||
final String name;
|
||||
final String? roomId;
|
||||
|
||||
User({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.roomId,
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateUserRequest {
|
||||
final String username;
|
||||
final String roomCode;
|
||||
|
||||
CreateUserRequest({required this.username});
|
||||
|
||||
CreateUserRequest({required this.username, required this.roomCode});
|
||||
factory CreateUserRequest.fromJson(Map<String, dynamic> json) => _$CreateUserRequestFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CreateUserRequestToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateUserResponse {
|
||||
final String? token;
|
||||
final String? error;
|
||||
final bool success;
|
||||
|
||||
CreateUserResponse({required this.token, required this.success, this.error});
|
||||
|
||||
factory CreateUserResponse.fromJson(Map<String, dynamic> json) => _$CreateUserResponseFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CreateUserResponseToJson(this);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user