import 'dart:async'; import 'package:pocketbase/pocketbase.dart'; import 'package:listenmeister/models/models.dart'; class ApiService { final PocketBase pb; ApiService(this.pb); String? get userId => pb.authStore.record?.id; Future login(String email, String password) async { await pb.collection('users').authWithPassword(email, password); } Future register(String email, String password) async { await pb .collection('users') .create( body: { 'email': email, 'password': password, 'passwordConfirm': password, 'emailVisibility': true, }, ); await login(email, password); } void logout() { pb.authStore.clear(); } Stream watchLists() { if (userId == null) return Stream.value(null); late final StreamController controller; Future Function()? unsubscribe; controller = StreamController( onListen: () async { unsubscribe = await pb.collection('lists').subscribe('*', (e) { if (!controller.isClosed) { controller.add(null); } }, filter: 'owner = "$userId" || members ?~ "$userId"'); }, onCancel: () { unsubscribe?.call(); }, ); return controller.stream; } Future> getLists() async { if (userId == null) return []; final List res = await pb .collection('lists') .getFullList(filter: 'owner = "$userId" || members ?~ "$userId"'); return res.map((r) => Liste.fromJson(r.toJson())).toList(); } Future createList(String title) async { if (userId == null) throw Exception('Nicht eingeloggt'); final RecordModel rec = await pb .collection('lists') .create(body: {'title': title, 'owner': userId}); return Liste.fromJson(rec.toJson()); } Future updateList( String listId, { String? title, List? members, }) async { if (title == null && members == null) return; final body = {}; if (title != null) { body['title'] = title; } if (members != null) { body['members'] = members; } await pb.collection('lists').update(listId, body: body); } Future deleteList(String listId) async { await pb.collection('lists').delete(listId); } Stream watchItems(String listId) { late final StreamController controller; Future Function()? unsubscribe; controller = StreamController( onListen: () async { unsubscribe = await pb.collection('items').subscribe('*', (e) { if (!controller.isClosed) { controller.add(null); } }, filter: 'list = "$listId"'); }, onCancel: () { unsubscribe?.call(); }, ); return controller.stream; } Future> getItems(String listId) async { final List res = await pb .collection('items') .getFullList(filter: 'list = "$listId"', sort: 'position'); return res.map((r) => Item.fromJson(r.toJson())).toList(); } Future createItem(String listId, String name, int position) async { final RecordModel rec = await pb .collection('items') .create( body: { 'list': listId, 'name': name, 'position': position, 'checked': false, }, ); return Item.fromJson(rec.toJson()); } Future deleteItem(String itemId) async { await pb.collection('items').delete(itemId); } Future updateItem(String itemId, {bool? checked, String? name}) async { if (checked == null && name == null) return; final Map body = {}; if (checked != null) { body['checked'] = checked; } if (name != null) { body['name'] = name; } await pb.collection('items').update(itemId, body: body); } Future findUserByEmail(String email) async { try { return await pb.collection('users').getFirstListItem('email = "$email"'); } catch (e) { return null; } } Future> getUsersByIds(List ids) async { if (ids.isEmpty) return []; final String filter = ids.map((id) => 'id = "$id"').join(' || '); return await pb.collection('users').getFullList(filter: filter); } Stream watchStoreLayouts() { if (userId == null) return Stream.value(null); late final StreamController controller; Future Function()? unsubscribe; controller = StreamController( onListen: () async { final String filter = 'owner = "$userId" || isPublic = true'; unsubscribe = await pb.collection('store_layouts').subscribe('*', (e) { if (!controller.isClosed) { controller.add(null); } }, filter: filter); }, onCancel: () => unsubscribe?.call(), ); return controller.stream; } Stream watchStoreLayout(String layoutId) { late final StreamController controller; Future Function()? unsubscribe; controller = StreamController( onListen: () async { unsubscribe = await pb.collection('store_layouts').subscribe(layoutId, ( e, ) { if (!controller.isClosed) { controller.add(null); } }); }, onCancel: () => unsubscribe?.call(), ); return controller.stream; } Future> getStoreLayouts({bool includePublic = true}) async { if (userId == null) return []; final List filterParts = ['owner = "$userId"']; if (includePublic) { filterParts.add('isPublic = true'); } final String filter = filterParts.join(' || '); final List res = await pb .collection('store_layouts') .getFullList(filter: filter, sort: '-updated'); final layouts = res.map((r) => StoreLayout.fromJson(r.toJson())).toList(); layouts.sort((a, b) { final bool aOwn = a.owner == userId; final bool bOwn = b.owner == userId; if (aOwn == bOwn) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); } return aOwn ? -1 : 1; }); return layouts; } Future createStoreLayout({ required String name, double? latitude, double? longitude, String? address, bool isPublic = false, }) async { if (userId == null) throw Exception('Nicht eingeloggt'); final Map body = { 'name': name, 'owner': userId, 'isPublic': isPublic, }; if (latitude != null) body['latitude'] = latitude; if (longitude != null) body['longitude'] = longitude; if (address != null && address.isNotEmpty) body['address'] = address; final RecordModel rec = await pb .collection('store_layouts') .create(body: body); return StoreLayout.fromJson(rec.toJson()); } Future getStoreLayoutById(String layoutId) async { try { final RecordModel rec = await pb .collection('store_layouts') .getOne(layoutId); return StoreLayout.fromJson(rec.toJson()); } catch (e) { return null; } } Future updateStoreLayout( String layoutId, { String? name, double? latitude, double? longitude, String? address, bool? isPublic, bool clearLatitude = false, bool clearLongitude = false, bool clearAddress = false, }) async { final Map body = {}; if (name != null) body['name'] = name; if (latitude != null) body['latitude'] = latitude; if (clearLatitude && latitude == null) body['latitude'] = null; if (longitude != null) body['longitude'] = longitude; if (clearLongitude && longitude == null) body['longitude'] = null; if (address != null) body['address'] = address; if (clearAddress && address == null) body['address'] = null; if (isPublic != null) body['isPublic'] = isPublic; if (body.isEmpty) return; await pb.collection('store_layouts').update(layoutId, body: body); } Future deleteStoreLayout(String layoutId) async { await pb.collection('store_layouts').delete(layoutId); } Stream watchStoreSections(String layoutId) { late final StreamController controller; Future Function()? unsubscribe; controller = StreamController( onListen: () async { unsubscribe = await pb.collection('store_sections').subscribe('*', (e) { if (!controller.isClosed) { controller.add(null); } }, filter: 'layout = "$layoutId"'); }, onCancel: () => unsubscribe?.call(), ); return controller.stream; } Future> getStoreSections(String layoutId) async { final List res = await pb .collection('store_sections') .getFullList(filter: 'layout = "$layoutId"', sort: 'position'); return res.map((r) => StoreSection.fromJson(r.toJson())).toList(); } Future createStoreSection({ required String layoutId, required String name, int position = 0, String? category, }) async { final RecordModel rec = await pb .collection('store_sections') .create( body: { 'layout': layoutId, 'name': name, 'position': position, if (category != null && category.isNotEmpty) 'category': category, }, ); return StoreSection.fromJson(rec.toJson()); } Future updateStoreSection( String sectionId, { String? name, int? position, String? category, bool clearCategory = false, }) async { final Map body = {}; if (name != null) body['name'] = name; if (position != null) body['position'] = position; if (category != null) body['category'] = category; if (clearCategory && category == null) body['category'] = null; if (body.isEmpty) return; await pb.collection('store_sections').update(sectionId, body: body); } Future deleteStoreSection(String sectionId) async { await pb.collection('store_sections').delete(sectionId); } }