diff --git a/wien_talks/wien_talks_client/lib/src/protocol/quotes/quote.dart b/wien_talks/wien_talks_client/lib/src/protocol/quotes/quote.dart index 20d381a..099720f 100644 --- a/wien_talks/wien_talks_client/lib/src/protocol/quotes/quote.dart +++ b/wien_talks/wien_talks_client/lib/src/protocol/quotes/quote.dart @@ -13,13 +13,12 @@ import 'package:serverpod_client/serverpod_client.dart' as _i1; abstract class Quote implements _i1.SerializableModel { Quote._({ - required this.id, + this.id, required this.userId, required this.text, this.authorName, required this.lat, - required this.lng, - required this.geohash, + required this.long, required this.createdAt, required this.visibility, required this.upvotes, @@ -28,13 +27,12 @@ abstract class Quote implements _i1.SerializableModel { }); factory Quote({ - required int id, + int? id, required int userId, required String text, String? authorName, required double lat, - required double lng, - required String geohash, + required double long, required DateTime createdAt, required int visibility, required int upvotes, @@ -44,13 +42,12 @@ abstract class Quote implements _i1.SerializableModel { factory Quote.fromJson(Map jsonSerialization) { return Quote( - id: jsonSerialization['id'] as int, + id: jsonSerialization['id'] as int?, userId: jsonSerialization['userId'] as int, text: jsonSerialization['text'] as String, authorName: jsonSerialization['authorName'] as String?, lat: (jsonSerialization['lat'] as num).toDouble(), - lng: (jsonSerialization['lng'] as num).toDouble(), - geohash: jsonSerialization['geohash'] as String, + long: (jsonSerialization['long'] as num).toDouble(), createdAt: _i1.DateTimeJsonExtension.fromJson(jsonSerialization['createdAt']), visibility: jsonSerialization['visibility'] as int, @@ -62,7 +59,10 @@ abstract class Quote implements _i1.SerializableModel { ); } - int id; + /// The database id, set if the object has been inserted into the + /// database or if it has been fetched from the database. Otherwise, + /// the id will be null. + int? id; int userId; @@ -72,9 +72,7 @@ abstract class Quote implements _i1.SerializableModel { double lat; - double lng; - - String geohash; + double long; DateTime createdAt; @@ -95,8 +93,7 @@ abstract class Quote implements _i1.SerializableModel { String? text, String? authorName, double? lat, - double? lng, - String? geohash, + double? long, DateTime? createdAt, int? visibility, int? upvotes, @@ -106,13 +103,12 @@ abstract class Quote implements _i1.SerializableModel { @override Map toJson() { return { - 'id': id, + if (id != null) 'id': id, 'userId': userId, 'text': text, if (authorName != null) 'authorName': authorName, 'lat': lat, - 'lng': lng, - 'geohash': geohash, + 'long': long, 'createdAt': createdAt.toJson(), 'visibility': visibility, 'upvotes': upvotes, @@ -131,13 +127,12 @@ class _Undefined {} class _QuoteImpl extends Quote { _QuoteImpl({ - required int id, + int? id, required int userId, required String text, String? authorName, required double lat, - required double lng, - required String geohash, + required double long, required DateTime createdAt, required int visibility, required int upvotes, @@ -149,8 +144,7 @@ class _QuoteImpl extends Quote { text: text, authorName: authorName, lat: lat, - lng: lng, - geohash: geohash, + long: long, createdAt: createdAt, visibility: visibility, upvotes: upvotes, @@ -163,13 +157,12 @@ class _QuoteImpl extends Quote { @_i1.useResult @override Quote copyWith({ - int? id, + Object? id = _Undefined, int? userId, String? text, Object? authorName = _Undefined, double? lat, - double? lng, - String? geohash, + double? long, DateTime? createdAt, int? visibility, int? upvotes, @@ -177,13 +170,12 @@ class _QuoteImpl extends Quote { Object? tags = _Undefined, }) { return Quote( - id: id ?? this.id, + id: id is int? ? id : this.id, userId: userId ?? this.userId, text: text ?? this.text, authorName: authorName is String? ? authorName : this.authorName, lat: lat ?? this.lat, - lng: lng ?? this.lng, - geohash: geohash ?? this.geohash, + long: long ?? this.long, createdAt: createdAt ?? this.createdAt, visibility: visibility ?? this.visibility, upvotes: upvotes ?? this.upvotes, diff --git a/wien_talks/wien_talks_client/pubspec.yaml b/wien_talks/wien_talks_client/pubspec.yaml index 223e12d..a438c4f 100644 --- a/wien_talks/wien_talks_client/pubspec.yaml +++ b/wien_talks/wien_talks_client/pubspec.yaml @@ -6,3 +6,4 @@ environment: dependencies: serverpod_client: 2.9.1 + serverpod_auth_server: ^2.9.1 diff --git a/wien_talks/wien_talks_flutter/pubspec.yaml b/wien_talks/wien_talks_flutter/pubspec.yaml index 3f0110a..9e35577 100644 --- a/wien_talks/wien_talks_flutter/pubspec.yaml +++ b/wien_talks/wien_talks_flutter/pubspec.yaml @@ -27,6 +27,8 @@ dependencies: serverpod_flutter: 2.9.1 wien_talks_client: path: ../wien_talks_client + serverpod_auth_shared_flutter: ^2.9.1 + # The following adds the Cupertino Icons font to your application. diff --git a/wien_talks/wien_talks_server/lib/src/generated/endpoints.dart b/wien_talks/wien_talks_server/lib/src/generated/endpoints.dart index 399a21e..fba3ffe 100644 --- a/wien_talks/wien_talks_server/lib/src/generated/endpoints.dart +++ b/wien_talks/wien_talks_server/lib/src/generated/endpoints.dart @@ -11,7 +11,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:serverpod/serverpod.dart' as _i1; import '../greeting_endpoint.dart' as _i2; -import '../quotes/location_endpoint.dart' as _i3; +import 'package:serverpod_auth_server/serverpod_auth_server.dart' as _i3; class Endpoints extends _i1.EndpointDispatch { @override @@ -22,13 +22,7 @@ class Endpoints extends _i1.EndpointDispatch { server, 'greeting', null, - ), - 'recipe': _i3.RecipeEndpoint() - ..initialize( - server, - 'recipe', - null, - ), + ) }; connectors['greeting'] = _i1.EndpointConnector( name: 'greeting', @@ -54,29 +48,6 @@ class Endpoints extends _i1.EndpointDispatch { ) }, ); - connectors['recipe'] = _i1.EndpointConnector( - name: 'recipe', - endpoint: endpoints['recipe']!, - methodConnectors: { - 'postQuote': _i1.MethodConnector( - name: 'postQuote', - params: { - 'quote': _i1.ParameterDescription( - name: 'quote', - type: _i1.getType(), - nullable: false, - ) - }, - call: ( - _i1.Session session, - Map params, - ) async => - (endpoints['recipe'] as _i3.RecipeEndpoint).postQuote( - session, - params['quote'], - ), - ) - }, - ); + modules['serverpod_auth'] = _i3.Endpoints()..initializeEndpoints(server); } } diff --git a/wien_talks/wien_talks_server/lib/src/generated/protocol.dart b/wien_talks/wien_talks_server/lib/src/generated/protocol.dart index 7187a0b..74b47bc 100644 --- a/wien_talks/wien_talks_server/lib/src/generated/protocol.dart +++ b/wien_talks/wien_talks_server/lib/src/generated/protocol.dart @@ -11,9 +11,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:serverpod/serverpod.dart' as _i1; import 'package:serverpod/protocol.dart' as _i2; -import 'greeting.dart' as _i3; -import 'quotes/create_quote.dart' as _i4; -import 'quotes/quote.dart' as _i5; +import 'package:serverpod_auth_server/serverpod_auth_server.dart' as _i3; +import 'greeting.dart' as _i4; +import 'quotes/create_quote.dart' as _i5; +import 'quotes/quote.dart' as _i6; export 'greeting.dart'; export 'quotes/create_quote.dart'; export 'quotes/quote.dart'; @@ -26,7 +27,100 @@ class Protocol extends _i1.SerializationManagerServer { static final Protocol _instance = Protocol._(); static final List<_i2.TableDefinition> targetTableDefinitions = [ - ..._i2.Protocol.targetTableDefinitions + _i2.TableDefinition( + name: 'quote', + dartName: 'Quote', + schema: 'public', + module: 'wien_talks', + columns: [ + _i2.ColumnDefinition( + name: 'id', + columnType: _i2.ColumnType.bigint, + isNullable: false, + dartType: 'int?', + columnDefault: 'nextval(\'quote_id_seq\'::regclass)', + ), + _i2.ColumnDefinition( + name: 'userId', + columnType: _i2.ColumnType.bigint, + isNullable: false, + dartType: 'int', + ), + _i2.ColumnDefinition( + name: 'text', + columnType: _i2.ColumnType.text, + isNullable: false, + dartType: 'String', + ), + _i2.ColumnDefinition( + name: 'authorName', + columnType: _i2.ColumnType.text, + isNullable: true, + dartType: 'String?', + ), + _i2.ColumnDefinition( + name: 'lat', + columnType: _i2.ColumnType.doublePrecision, + isNullable: false, + dartType: 'double', + ), + _i2.ColumnDefinition( + name: 'long', + columnType: _i2.ColumnType.doublePrecision, + isNullable: false, + dartType: 'double', + ), + _i2.ColumnDefinition( + name: 'createdAt', + columnType: _i2.ColumnType.timestampWithoutTimeZone, + isNullable: false, + dartType: 'DateTime', + ), + _i2.ColumnDefinition( + name: 'visibility', + columnType: _i2.ColumnType.bigint, + isNullable: false, + dartType: 'int', + ), + _i2.ColumnDefinition( + name: 'upvotes', + columnType: _i2.ColumnType.bigint, + isNullable: false, + dartType: 'int', + ), + _i2.ColumnDefinition( + name: 'downvotes', + columnType: _i2.ColumnType.bigint, + isNullable: false, + dartType: 'int', + ), + _i2.ColumnDefinition( + name: 'tags', + columnType: _i2.ColumnType.json, + isNullable: true, + dartType: 'List?', + ), + ], + foreignKeys: [], + indexes: [ + _i2.IndexDefinition( + indexName: 'quote_pkey', + tableSpace: null, + elements: [ + _i2.IndexElementDefinition( + type: _i2.IndexElementDefinitionType.column, + definition: 'id', + ) + ], + type: 'btree', + isUnique: true, + isPrimary: true, + ) + ], + managed: true, + ), + ..._i3.Protocol.targetTableDefinitions, + ..._i2.Protocol.targetTableDefinitions, ]; @override @@ -35,23 +129,23 @@ class Protocol extends _i1.SerializationManagerServer { Type? t, ]) { t ??= T; - if (t == _i3.Greeting) { - return _i3.Greeting.fromJson(data) as T; + if (t == _i4.Greeting) { + return _i4.Greeting.fromJson(data) as T; } - if (t == _i4.CreateQuoteRequest) { - return _i4.CreateQuoteRequest.fromJson(data) as T; + if (t == _i5.CreateQuoteRequest) { + return _i5.CreateQuoteRequest.fromJson(data) as T; } - if (t == _i5.Quote) { - return _i5.Quote.fromJson(data) as T; + if (t == _i6.Quote) { + return _i6.Quote.fromJson(data) as T; } - if (t == _i1.getType<_i3.Greeting?>()) { - return (data != null ? _i3.Greeting.fromJson(data) : null) as T; + if (t == _i1.getType<_i4.Greeting?>()) { + return (data != null ? _i4.Greeting.fromJson(data) : null) as T; } - if (t == _i1.getType<_i4.CreateQuoteRequest?>()) { - return (data != null ? _i4.CreateQuoteRequest.fromJson(data) : null) as T; + if (t == _i1.getType<_i5.CreateQuoteRequest?>()) { + return (data != null ? _i5.CreateQuoteRequest.fromJson(data) : null) as T; } - if (t == _i1.getType<_i5.Quote?>()) { - return (data != null ? _i5.Quote.fromJson(data) : null) as T; + if (t == _i1.getType<_i6.Quote?>()) { + return (data != null ? _i6.Quote.fromJson(data) : null) as T; } if (t == _i1.getType?>()) { return (data != null @@ -63,6 +157,9 @@ class Protocol extends _i1.SerializationManagerServer { ? (data as List).map((e) => deserialize(e)).toList() : null) as T; } + try { + return _i3.Protocol().deserialize(data, t); + } on _i1.DeserializationTypeNotFoundException catch (_) {} try { return _i2.Protocol().deserialize(data, t); } on _i1.DeserializationTypeNotFoundException catch (_) {} @@ -73,19 +170,23 @@ class Protocol extends _i1.SerializationManagerServer { String? getClassNameForObject(Object? data) { String? className = super.getClassNameForObject(data); if (className != null) return className; - if (data is _i3.Greeting) { + if (data is _i4.Greeting) { return 'Greeting'; } - if (data is _i4.CreateQuoteRequest) { + if (data is _i5.CreateQuoteRequest) { return 'CreateQuoteRequest'; } - if (data is _i5.Quote) { + if (data is _i6.Quote) { return 'Quote'; } className = _i2.Protocol().getClassNameForObject(data); if (className != null) { return 'serverpod.$className'; } + className = _i3.Protocol().getClassNameForObject(data); + if (className != null) { + return 'serverpod_auth.$className'; + } return null; } @@ -96,29 +197,43 @@ class Protocol extends _i1.SerializationManagerServer { return super.deserializeByClassName(data); } if (dataClassName == 'Greeting') { - return deserialize<_i3.Greeting>(data['data']); + return deserialize<_i4.Greeting>(data['data']); } if (dataClassName == 'CreateQuoteRequest') { - return deserialize<_i4.CreateQuoteRequest>(data['data']); + return deserialize<_i5.CreateQuoteRequest>(data['data']); } if (dataClassName == 'Quote') { - return deserialize<_i5.Quote>(data['data']); + return deserialize<_i6.Quote>(data['data']); } if (dataClassName.startsWith('serverpod.')) { data['className'] = dataClassName.substring(10); return _i2.Protocol().deserializeByClassName(data); } + if (dataClassName.startsWith('serverpod_auth.')) { + data['className'] = dataClassName.substring(15); + return _i3.Protocol().deserializeByClassName(data); + } return super.deserializeByClassName(data); } @override _i1.Table? getTableForType(Type t) { + { + var table = _i3.Protocol().getTableForType(t); + if (table != null) { + return table; + } + } { var table = _i2.Protocol().getTableForType(t); if (table != null) { return table; } } + switch (t) { + case _i6.Quote: + return _i6.Quote.t; + } return null; } diff --git a/wien_talks/wien_talks_server/lib/src/generated/quotes/quote.dart b/wien_talks/wien_talks_server/lib/src/generated/quotes/quote.dart index 8e1ee3a..f0f5606 100644 --- a/wien_talks/wien_talks_server/lib/src/generated/quotes/quote.dart +++ b/wien_talks/wien_talks_server/lib/src/generated/quotes/quote.dart @@ -11,16 +11,14 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:serverpod/serverpod.dart' as _i1; -abstract class Quote - implements _i1.SerializableModel, _i1.ProtocolSerialization { +abstract class Quote implements _i1.TableRow, _i1.ProtocolSerialization { Quote._({ - required this.id, + this.id, required this.userId, required this.text, this.authorName, required this.lat, - required this.lng, - required this.geohash, + required this.long, required this.createdAt, required this.visibility, required this.upvotes, @@ -29,13 +27,12 @@ abstract class Quote }); factory Quote({ - required int id, + int? id, required int userId, required String text, String? authorName, required double lat, - required double lng, - required String geohash, + required double long, required DateTime createdAt, required int visibility, required int upvotes, @@ -45,13 +42,12 @@ abstract class Quote factory Quote.fromJson(Map jsonSerialization) { return Quote( - id: jsonSerialization['id'] as int, + id: jsonSerialization['id'] as int?, userId: jsonSerialization['userId'] as int, text: jsonSerialization['text'] as String, authorName: jsonSerialization['authorName'] as String?, lat: (jsonSerialization['lat'] as num).toDouble(), - lng: (jsonSerialization['lng'] as num).toDouble(), - geohash: jsonSerialization['geohash'] as String, + long: (jsonSerialization['long'] as num).toDouble(), createdAt: _i1.DateTimeJsonExtension.fromJson(jsonSerialization['createdAt']), visibility: jsonSerialization['visibility'] as int, @@ -63,7 +59,12 @@ abstract class Quote ); } - int id; + static final t = QuoteTable(); + + static const db = QuoteRepository._(); + + @override + int? id; int userId; @@ -73,9 +74,7 @@ abstract class Quote double lat; - double lng; - - String geohash; + double long; DateTime createdAt; @@ -87,6 +86,9 @@ abstract class Quote List? tags; + @override + _i1.Table get table => t; + /// Returns a shallow copy of this [Quote] /// with some or all fields replaced by the given arguments. @_i1.useResult @@ -96,8 +98,7 @@ abstract class Quote String? text, String? authorName, double? lat, - double? lng, - String? geohash, + double? long, DateTime? createdAt, int? visibility, int? upvotes, @@ -107,13 +108,12 @@ abstract class Quote @override Map toJson() { return { - 'id': id, + if (id != null) 'id': id, 'userId': userId, 'text': text, if (authorName != null) 'authorName': authorName, 'lat': lat, - 'lng': lng, - 'geohash': geohash, + 'long': long, 'createdAt': createdAt.toJson(), 'visibility': visibility, 'upvotes': upvotes, @@ -125,13 +125,12 @@ abstract class Quote @override Map toJsonForProtocol() { return { - 'id': id, + if (id != null) 'id': id, 'userId': userId, 'text': text, if (authorName != null) 'authorName': authorName, 'lat': lat, - 'lng': lng, - 'geohash': geohash, + 'long': long, 'createdAt': createdAt.toJson(), 'visibility': visibility, 'upvotes': upvotes, @@ -140,6 +139,30 @@ abstract class Quote }; } + static QuoteInclude include() { + return QuoteInclude._(); + } + + static QuoteIncludeList includeList({ + _i1.WhereExpressionBuilder? where, + int? limit, + int? offset, + _i1.OrderByBuilder? orderBy, + bool orderDescending = false, + _i1.OrderByListBuilder? orderByList, + QuoteInclude? include, + }) { + return QuoteIncludeList._( + where: where, + limit: limit, + offset: offset, + orderBy: orderBy?.call(Quote.t), + orderDescending: orderDescending, + orderByList: orderByList?.call(Quote.t), + include: include, + ); + } + @override String toString() { return _i1.SerializationManager.encode(this); @@ -150,13 +173,12 @@ class _Undefined {} class _QuoteImpl extends Quote { _QuoteImpl({ - required int id, + int? id, required int userId, required String text, String? authorName, required double lat, - required double lng, - required String geohash, + required double long, required DateTime createdAt, required int visibility, required int upvotes, @@ -168,8 +190,7 @@ class _QuoteImpl extends Quote { text: text, authorName: authorName, lat: lat, - lng: lng, - geohash: geohash, + long: long, createdAt: createdAt, visibility: visibility, upvotes: upvotes, @@ -182,13 +203,12 @@ class _QuoteImpl extends Quote { @_i1.useResult @override Quote copyWith({ - int? id, + Object? id = _Undefined, int? userId, String? text, Object? authorName = _Undefined, double? lat, - double? lng, - String? geohash, + double? long, DateTime? createdAt, int? visibility, int? upvotes, @@ -196,13 +216,12 @@ class _QuoteImpl extends Quote { Object? tags = _Undefined, }) { return Quote( - id: id ?? this.id, + id: id is int? ? id : this.id, userId: userId ?? this.userId, text: text ?? this.text, authorName: authorName is String? ? authorName : this.authorName, lat: lat ?? this.lat, - lng: lng ?? this.lng, - geohash: geohash ?? this.geohash, + long: long ?? this.long, createdAt: createdAt ?? this.createdAt, visibility: visibility ?? this.visibility, upvotes: upvotes ?? this.upvotes, @@ -211,3 +230,326 @@ class _QuoteImpl extends Quote { ); } } + +class QuoteTable extends _i1.Table { + QuoteTable({super.tableRelation}) : super(tableName: 'quote') { + userId = _i1.ColumnInt( + 'userId', + this, + ); + text = _i1.ColumnString( + 'text', + this, + ); + authorName = _i1.ColumnString( + 'authorName', + this, + ); + lat = _i1.ColumnDouble( + 'lat', + this, + ); + long = _i1.ColumnDouble( + 'long', + this, + ); + createdAt = _i1.ColumnDateTime( + 'createdAt', + this, + ); + visibility = _i1.ColumnInt( + 'visibility', + this, + ); + upvotes = _i1.ColumnInt( + 'upvotes', + this, + ); + downvotes = _i1.ColumnInt( + 'downvotes', + this, + ); + tags = _i1.ColumnSerializable( + 'tags', + this, + ); + } + + late final _i1.ColumnInt userId; + + late final _i1.ColumnString text; + + late final _i1.ColumnString authorName; + + late final _i1.ColumnDouble lat; + + late final _i1.ColumnDouble long; + + late final _i1.ColumnDateTime createdAt; + + late final _i1.ColumnInt visibility; + + late final _i1.ColumnInt upvotes; + + late final _i1.ColumnInt downvotes; + + late final _i1.ColumnSerializable tags; + + @override + List<_i1.Column> get columns => [ + id, + userId, + text, + authorName, + lat, + long, + createdAt, + visibility, + upvotes, + downvotes, + tags, + ]; +} + +class QuoteInclude extends _i1.IncludeObject { + QuoteInclude._(); + + @override + Map get includes => {}; + + @override + _i1.Table get table => Quote.t; +} + +class QuoteIncludeList extends _i1.IncludeList { + QuoteIncludeList._({ + _i1.WhereExpressionBuilder? where, + super.limit, + super.offset, + super.orderBy, + super.orderDescending, + super.orderByList, + super.include, + }) { + super.where = where?.call(Quote.t); + } + + @override + Map get includes => include?.includes ?? {}; + + @override + _i1.Table get table => Quote.t; +} + +class QuoteRepository { + const QuoteRepository._(); + + /// Returns a list of [Quote]s matching the given query parameters. + /// + /// Use [where] to specify which items to include in the return value. + /// If none is specified, all items will be returned. + /// + /// To specify the order of the items use [orderBy] or [orderByList] + /// when sorting by multiple columns. + /// + /// The maximum number of items can be set by [limit]. If no limit is set, + /// all items matching the query will be returned. + /// + /// [offset] defines how many items to skip, after which [limit] (or all) + /// items are read from the database. + /// + /// ```dart + /// var persons = await Persons.db.find( + /// session, + /// where: (t) => t.lastName.equals('Jones'), + /// orderBy: (t) => t.firstName, + /// limit: 100, + /// ); + /// ``` + Future> find( + _i1.Session session, { + _i1.WhereExpressionBuilder? where, + int? limit, + int? offset, + _i1.OrderByBuilder? orderBy, + bool orderDescending = false, + _i1.OrderByListBuilder? orderByList, + _i1.Transaction? transaction, + }) async { + return session.db.find( + where: where?.call(Quote.t), + orderBy: orderBy?.call(Quote.t), + orderByList: orderByList?.call(Quote.t), + orderDescending: orderDescending, + limit: limit, + offset: offset, + transaction: transaction, + ); + } + + /// Returns the first matching [Quote] matching the given query parameters. + /// + /// Use [where] to specify which items to include in the return value. + /// If none is specified, all items will be returned. + /// + /// To specify the order use [orderBy] or [orderByList] + /// when sorting by multiple columns. + /// + /// [offset] defines how many items to skip, after which the next one will be picked. + /// + /// ```dart + /// var youngestPerson = await Persons.db.findFirstRow( + /// session, + /// where: (t) => t.lastName.equals('Jones'), + /// orderBy: (t) => t.age, + /// ); + /// ``` + Future findFirstRow( + _i1.Session session, { + _i1.WhereExpressionBuilder? where, + int? offset, + _i1.OrderByBuilder? orderBy, + bool orderDescending = false, + _i1.OrderByListBuilder? orderByList, + _i1.Transaction? transaction, + }) async { + return session.db.findFirstRow( + where: where?.call(Quote.t), + orderBy: orderBy?.call(Quote.t), + orderByList: orderByList?.call(Quote.t), + orderDescending: orderDescending, + offset: offset, + transaction: transaction, + ); + } + + /// Finds a single [Quote] by its [id] or null if no such row exists. + Future findById( + _i1.Session session, + int id, { + _i1.Transaction? transaction, + }) async { + return session.db.findById( + id, + transaction: transaction, + ); + } + + /// Inserts all [Quote]s in the list and returns the inserted rows. + /// + /// The returned [Quote]s will have their `id` fields set. + /// + /// This is an atomic operation, meaning that if one of the rows fails to + /// insert, none of the rows will be inserted. + Future> insert( + _i1.Session session, + List rows, { + _i1.Transaction? transaction, + }) async { + return session.db.insert( + rows, + transaction: transaction, + ); + } + + /// Inserts a single [Quote] and returns the inserted row. + /// + /// The returned [Quote] will have its `id` field set. + Future insertRow( + _i1.Session session, + Quote row, { + _i1.Transaction? transaction, + }) async { + return session.db.insertRow( + row, + transaction: transaction, + ); + } + + /// Updates all [Quote]s in the list and returns the updated rows. If + /// [columns] is provided, only those columns will be updated. Defaults to + /// all columns. + /// This is an atomic operation, meaning that if one of the rows fails to + /// update, none of the rows will be updated. + Future> update( + _i1.Session session, + List rows, { + _i1.ColumnSelections? columns, + _i1.Transaction? transaction, + }) async { + return session.db.update( + rows, + columns: columns?.call(Quote.t), + transaction: transaction, + ); + } + + /// Updates a single [Quote]. The row needs to have its id set. + /// Optionally, a list of [columns] can be provided to only update those + /// columns. Defaults to all columns. + Future updateRow( + _i1.Session session, + Quote row, { + _i1.ColumnSelections? columns, + _i1.Transaction? transaction, + }) async { + return session.db.updateRow( + row, + columns: columns?.call(Quote.t), + transaction: transaction, + ); + } + + /// Deletes all [Quote]s in the list and returns the deleted rows. + /// This is an atomic operation, meaning that if one of the rows fail to + /// be deleted, none of the rows will be deleted. + Future> delete( + _i1.Session session, + List rows, { + _i1.Transaction? transaction, + }) async { + return session.db.delete( + rows, + transaction: transaction, + ); + } + + /// Deletes a single [Quote]. + Future deleteRow( + _i1.Session session, + Quote row, { + _i1.Transaction? transaction, + }) async { + return session.db.deleteRow( + row, + transaction: transaction, + ); + } + + /// Deletes all rows matching the [where] expression. + Future> deleteWhere( + _i1.Session session, { + required _i1.WhereExpressionBuilder where, + _i1.Transaction? transaction, + }) async { + return session.db.deleteWhere( + where: where(Quote.t), + transaction: transaction, + ); + } + + /// Counts the number of rows matching the [where] expression. If omitted, + /// will return the count of all rows in the table. + Future count( + _i1.Session session, { + _i1.WhereExpressionBuilder? where, + int? limit, + _i1.Transaction? transaction, + }) async { + return session.db.count( + where: where?.call(Quote.t), + limit: limit, + transaction: transaction, + ); + } +} diff --git a/wien_talks/wien_talks_server/lib/src/quotes/location_endpoint.dart b/wien_talks/wien_talks_server/lib/src/quotes/location_endpoint.dart deleted file mode 100644 index 406eb7d..0000000 --- a/wien_talks/wien_talks_server/lib/src/quotes/location_endpoint.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:async'; - -import 'package:serverpod/serverpod.dart'; - -class RecipeEndpoint extends Endpoint { - Future postQuote(Session session, String quote) async { - // validate content - - // persist quote - return Future.value('none'); - } -} diff --git a/wien_talks/wien_talks_server/lib/src/quotes/quote.spy.yaml b/wien_talks/wien_talks_server/lib/src/quotes/quote.spy.yaml index f84c340..3a734d5 100644 --- a/wien_talks/wien_talks_server/lib/src/quotes/quote.spy.yaml +++ b/wien_talks/wien_talks_server/lib/src/quotes/quote.spy.yaml @@ -1,12 +1,12 @@ class: Quote +table: quote fields: - id: int + id: int? userId: int text: String authorName: String? lat: double - lng: double - geohash: String + long: double createdAt: DateTime visibility: int upvotes: int diff --git a/wien_talks/wien_talks_server/lib/src/quotes/quote_controller.dart b/wien_talks/wien_talks_server/lib/src/quotes/quote_controller.dart new file mode 100644 index 0000000..533ee21 --- /dev/null +++ b/wien_talks/wien_talks_server/lib/src/quotes/quote_controller.dart @@ -0,0 +1,28 @@ +import 'package:serverpod/serverpod.dart'; +import 'package:wien_talks_server/src/generated/protocol.dart'; + +String validateQuote(CreateQuoteRequest req) { + final text = req.text.trim(); + if (text.isEmpty || text.length > 500) { + throw FormatException('Text must be 1..500 chars'); + } + if (req.lat.isNaN || req.lng.isNaN) { + throw FormatException('Invalid coordinates'); + } + if (req.lat < -90 || req.lat > 90 || req.lng < -180 || req.lng > 180) { + throw FormatException('Coordinates out of bounds'); + } + return text; +} + +Future> listNearby( + Session session, { + required double lat, + required double lng, + int radiusMeters = 1500, + int limit = 50, +}) async { + throw UnimplementedError(); +} + +enum Visibility { public, private, locallyPublic } diff --git a/wien_talks/wien_talks_server/lib/src/quotes/quotes_endpoint.dart b/wien_talks/wien_talks_server/lib/src/quotes/quotes_endpoint.dart new file mode 100644 index 0000000..9fc7ded --- /dev/null +++ b/wien_talks/wien_talks_server/lib/src/quotes/quotes_endpoint.dart @@ -0,0 +1,43 @@ +import 'package:serverpod/serverpod.dart'; +import 'package:wien_talks_server/src/generated/protocol.dart'; +import 'package:wien_talks_server/src/quotes/quote_controller.dart'; + +class QuoteEndpoint extends Endpoint { + Future create(Session session, CreateQuoteRequest req) async { + final authInfo = await session.authenticated; + final userId = authInfo?.userId; + + if (userId == null) { + throw Exception('Not signed in'); + } + + String text = validateQuote(req); + + final quote = Quote( + id: 0, + userId: userId, + text: text, + authorName: req.authorName?.trim().isEmpty == true + ? null + : req.authorName!.trim(), + lat: req.lat, + long: req.lng, + createdAt: DateTime.now().toUtc(), + visibility: 0, + upvotes: 0, + downvotes: 0, + ); + + final inserted = await session.db.insertRow(quote); + return inserted; + } + + Future getQuoteById(Session session, int id) async { + final quote = await Quote.db.findById(session, id); + if (quote != null) { + return quote; + } + + throw Exception('Quote not found'); + } +}