1d79dde5e1
CI/CD / CI · API (dotnet build + test) (push) Successful in 42s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 37s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 23s
Head-start on the Koja-Flutter build while pub access is unavailable (pub.dev 403 under sanctions). NOT yet built/verified — needs `flutter create` + `pub get` once package access is restored. - core/theme/app_theme.dart: centralized MeeziTheme (brand green #0F6E56, Material 3, filled/outlined buttons, inputs), wired into main.dart (was a brown seed, no theme). - public_api.dart: discover() gains the full filter set (themes/vibes/occasions/ spaceFeatures/noise/priceTier/size/openNow) + discoverNearby/nlpParse/discoverTaxonomy, matching the web Koja's backend surface. Follows the existing dio pattern.
171 lines
5.8 KiB
Dart
171 lines
5.8 KiB
Dart
import '../../core/api/api_client.dart';
|
|
|
|
class PublicApi {
|
|
PublicApi(this._client);
|
|
|
|
final ApiClient _client;
|
|
|
|
Future<List<Map<String, dynamic>>> discover({
|
|
String? city,
|
|
String? q,
|
|
double? minRating,
|
|
String? sort,
|
|
List<String>? themes,
|
|
List<String>? vibes,
|
|
List<String>? occasions,
|
|
List<String>? spaceFeatures,
|
|
String? noise,
|
|
String? priceTier,
|
|
String? size,
|
|
bool openNow = false,
|
|
}) async {
|
|
final params = <String, String>{};
|
|
if (city != null && city.isNotEmpty) params['city'] = city;
|
|
if (q != null && q.isNotEmpty) params['q'] = q;
|
|
if (minRating != null) params['minRating'] = minRating.toString();
|
|
if (sort != null && sort.isNotEmpty) params['sort'] = sort;
|
|
if (themes != null && themes.isNotEmpty) params['themes'] = themes.join(',');
|
|
if (vibes != null && vibes.isNotEmpty) params['vibes'] = vibes.join(',');
|
|
if (occasions != null && occasions.isNotEmpty) params['occasions'] = occasions.join(',');
|
|
if (spaceFeatures != null && spaceFeatures.isNotEmpty) params['spaceFeatures'] = spaceFeatures.join(',');
|
|
if (noise != null && noise.isNotEmpty) params['noise'] = noise;
|
|
if (priceTier != null && priceTier.isNotEmpty) params['priceTier'] = priceTier;
|
|
if (size != null && size.isNotEmpty) params['size'] = size;
|
|
if (openNow) params['openNow'] = 'true';
|
|
final res = await _client.dio.get<Map<String, dynamic>>(
|
|
'/api/public/discover',
|
|
queryParameters: params.isEmpty ? null : params,
|
|
);
|
|
final list = res.data?['data'] as List<dynamic>? ?? [];
|
|
return list.cast<Map<String, dynamic>>();
|
|
}
|
|
|
|
/// Cafés near a coordinate, sorted by distance (for "near me").
|
|
Future<List<Map<String, dynamic>>> discoverNearby({
|
|
required double lat,
|
|
required double lng,
|
|
String? excludeSlug,
|
|
int limit = 12,
|
|
}) async {
|
|
final res = await _client.dio.get<Map<String, dynamic>>(
|
|
'/api/public/discover/near',
|
|
queryParameters: {
|
|
'lat': lat,
|
|
'lng': lng,
|
|
if (excludeSlug != null && excludeSlug.isNotEmpty) 'excludeSlug': excludeSlug,
|
|
'limit': limit,
|
|
},
|
|
);
|
|
final list = res.data?['data'] as List<dynamic>? ?? [];
|
|
return list.cast<Map<String, dynamic>>();
|
|
}
|
|
|
|
/// Parse a free-text query into structured discovery hints (themes/vibes/...).
|
|
Future<Map<String, dynamic>?> nlpParse(String q) async {
|
|
final res = await _client.dio.get<Map<String, dynamic>>(
|
|
'/api/public/discover/nlp-parse',
|
|
queryParameters: {'q': q},
|
|
);
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
|
|
/// The discovery taxonomy (available themes, vibes, occasions, space features).
|
|
Future<Map<String, dynamic>?> discoverTaxonomy() async {
|
|
final res = await _client.dio.get<Map<String, dynamic>>(
|
|
'/api/public/discover-profile/taxonomy',
|
|
);
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> getReviews(String slug, {int page = 1}) async {
|
|
final res = await _client.dio.get<Map<String, dynamic>>(
|
|
'/api/public/cafes/$slug/reviews',
|
|
queryParameters: {'page': page, 'pageSize': 20},
|
|
);
|
|
final list = res.data?['data'] as List<dynamic>? ?? [];
|
|
return list.cast<Map<String, dynamic>>();
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> createReview(
|
|
String slug, {
|
|
required String authorName,
|
|
required int rating,
|
|
String? comment,
|
|
String? authorPhone,
|
|
}) async {
|
|
final res = await _client.dio.post<Map<String, dynamic>>(
|
|
'/api/public/cafes/$slug/reviews',
|
|
data: {
|
|
'authorName': authorName,
|
|
'rating': rating,
|
|
if (comment != null && comment.isNotEmpty) 'comment': comment,
|
|
if (authorPhone != null && authorPhone.isNotEmpty) 'authorPhone': authorPhone,
|
|
},
|
|
);
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> getCafe(String slug) async {
|
|
final res = await _client.dio.get<Map<String, dynamic>>('/api/public/cafes/$slug');
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> getMenu(String slug) async {
|
|
final res = await _client.dio.get<Map<String, dynamic>>('/api/public/cafes/$slug/menu');
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> resolveQr(String qrCode) async {
|
|
final res = await _client.dio.get<Map<String, dynamic>>('/api/q/$qrCode');
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> placeOrder(
|
|
String slug, {
|
|
required String? tableId,
|
|
required List<Map<String, dynamic>> items,
|
|
String? guestPhone,
|
|
String? guestName,
|
|
}) async {
|
|
final res = await _client.dio.post<Map<String, dynamic>>(
|
|
'/api/public/cafes/$slug/orders',
|
|
data: {
|
|
'orderType': 'DineIn',
|
|
if (tableId != null) 'tableId': tableId,
|
|
if (guestPhone != null) 'guestPhone': guestPhone,
|
|
if (guestName != null) 'guestName': guestName,
|
|
'items': items,
|
|
},
|
|
);
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> trackOrder(String orderId) async {
|
|
final res = await _client.dio.get<Map<String, dynamic>>('/api/public/orders/$orderId/track');
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> createReservation(
|
|
String slug, {
|
|
required String guestName,
|
|
required String guestPhone,
|
|
required String date,
|
|
required String time,
|
|
required int partySize,
|
|
String? notes,
|
|
}) async {
|
|
final res = await _client.dio.post<Map<String, dynamic>>(
|
|
'/api/public/cafes/$slug/reservations',
|
|
data: {
|
|
'guestName': guestName,
|
|
'guestPhone': guestPhone,
|
|
'date': date,
|
|
'time': time,
|
|
'partySize': partySize,
|
|
if (notes != null) 'notes': notes,
|
|
},
|
|
);
|
|
return res.data?['data'] as Map<String, dynamic>?;
|
|
}
|
|
}
|