Compare commits

...

2 commits

Author SHA1 Message Date
tk
33c98019bb regenerate stream endpoints 2025-08-16 18:39:33 +02:00
tk
9de633feb9 merge frontend changes 2025-08-16 18:10:28 +02:00
9 changed files with 211 additions and 195 deletions

View file

@ -23,21 +23,6 @@ class EndpointQuote extends _i1.EndpointRef {
@override @override
String get name => 'quote'; String get name => 'quote';
_i2.Future<void> updateQuote(_i3.Quote quote) =>
caller.callServerEndpoint<void>(
'quote',
'updateQuote',
{'quote': quote},
);
_i2.Stream<_i3.Quote> quoteUpdates() =>
caller.callStreamingServerEndpoint<_i2.Stream<_i3.Quote>, _i3.Quote>(
'quote',
'quoteUpdates',
{},
{},
);
_i2.Future<_i3.Quote> createQuote(_i4.CreateQuoteRequest req) => _i2.Future<_i3.Quote> createQuote(_i4.CreateQuoteRequest req) =>
caller.callServerEndpoint<_i3.Quote>( caller.callServerEndpoint<_i3.Quote>(
'quote', 'quote',
@ -45,18 +30,18 @@ class EndpointQuote extends _i1.EndpointRef {
{'req': req}, {'req': req},
); );
_i2.Future<_i3.Quote> getQuoteById(int id) => _i2.Future<void> updateQuote(_i3.Quote quote) =>
caller.callServerEndpoint<_i3.Quote>( caller.callServerEndpoint<void>(
'quote', 'quote',
'getQuoteById', 'updateQuote',
{'id': id}, {'quote': quote},
); );
_i2.Future<List<_i3.Quote>> getAllQuotes() => _i2.Future<List<_i3.Quote>> getAllQuotes({required int limit}) =>
caller.callServerEndpoint<List<_i3.Quote>>( caller.callServerEndpoint<List<_i3.Quote>>(
'quote', 'quote',
'getAllQuotes', 'getAllQuotes',
{}, {'limit': limit},
); );
} }

View file

@ -3,7 +3,7 @@ import 'package:wien_talks_flutter/helper/funmap_mgr.dart';
import 'package:wien_talks_flutter/helper/go_router.dart'; import 'package:wien_talks_flutter/helper/go_router.dart';
void main() { void main() {
FunmapMgr(); FunmapMgr().configure();
runApp(const MyApp()); runApp(const MyApp());
} }

View file

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:wien_talks_flutter/show_latest_news_widget.dart';
import 'package:wien_talks_flutter/widgets/heading_text.dart';
import 'package:wien_talks_flutter/widgets/screen_widget.dart';
class NewsScreen extends StatelessWidget {
const NewsScreen({
super.key,
});
@override
Widget build(BuildContext context) {
return ScreenWidget(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
HeadingText(text: "Latest news"),
ShowLatestNewsWidget(),
SizedBox(
height: 30,
),
ElevatedButton(
onPressed: () {
context.pushNamed("create_event");
},
child: Text("Submit your own event")),
],
),
),
);
}
}

View file

@ -1,24 +1,87 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:wien_talks_flutter/widgets/heading_text.dart'; import 'package:wien_talks_client/wien_talks_client.dart';
import 'package:wien_talks_flutter/helper/funmap_mgr.dart';
import 'helper/funmap_mgr.dart'; class ShowLatestNewsWidget extends StatefulWidget {
class ShowLatestNewsWidget extends StatelessWidget {
const ShowLatestNewsWidget({super.key}); const ShowLatestNewsWidget({super.key});
@override
State<ShowLatestNewsWidget> createState() => _ShowLatestNewsWidgetState();
}
class _ShowLatestNewsWidgetState extends State<ShowLatestNewsWidget> {
final _controller = StreamController<List<Quote>>.broadcast();
Timer? _timer;
@override
void initState() {
super.initState();
_reload();
_timer = Timer.periodic(const Duration(seconds: 30), (_) => _reload());
}
Future<void> _reload() async {
try {
final quotes = await FunmapMgr().client.quote.getAllQuotes(limit: 200);
_controller.add(quotes);
} catch (e, st) {
_controller.addError(e, st);
}
}
@override
void dispose() {
_timer?.cancel();
_controller.close();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder( return RefreshIndicator(
stream: FunmapMgr().client.quote.stream, onRefresh: _reload,
builder: (BuildContext context, AsyncSnapshot snapshot) { child: StreamBuilder<List<Quote>>(
return Column( stream: _controller.stream,
crossAxisAlignment: CrossAxisAlignment.start, initialData: const <Quote>[],
builder: (context, snap) {
if (snap.hasError) {
return ListView(
children: [ children: [
HeadingText(text: "Latest news"), Padding(
if (snapshot.hasError) Text('Error: ${snapshot.error}'), padding: const EdgeInsets.all(16),
Text(snapshot.data ?? "Be the first to submit amazing news!", style: TextStyle(fontSize: 20, color: Theme.of(context).colorScheme.error)), child: Text('Error: ${snap.error}'),
),
], ],
); );
}); }
final quotes = snap.data ?? const <Quote>[];
if (quotes.isEmpty) {
return ListView(
children: const [
Padding(
padding: EdgeInsets.all(16),
child: Text('No quotes yet. Pull to refresh.'),
),
],
);
}
return ListView.separated(
itemCount: quotes.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, i) {
final q = quotes[i];
return ListTile(
title: Text(q.text),
subtitle: Text([
if ((q.authorName ?? '').isNotEmpty) q.authorName!,
q.createdAt.toLocal().toString(),
].where((e) => e.isNotEmpty).join(' · ')),
);
},
);
},
),
);
} }
} }

View file

@ -11,9 +11,9 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _i1; import 'package:serverpod/serverpod.dart' as _i1;
import '../quotes/quotes_endpoint.dart' as _i2; import '../quotes/quotes_endpoint.dart' as _i2;
import 'package:wien_talks_server/src/generated/quotes/quote.dart' as _i3;
import 'package:wien_talks_server/src/generated/quotes/create_quote.dart' import 'package:wien_talks_server/src/generated/quotes/create_quote.dart'
as _i4; as _i3;
import 'package:wien_talks_server/src/generated/quotes/quote.dart' as _i4;
import 'package:serverpod_auth_server/serverpod_auth_server.dart' as _i5; import 'package:serverpod_auth_server/serverpod_auth_server.dart' as _i5;
class Endpoints extends _i1.EndpointDispatch { class Endpoints extends _i1.EndpointDispatch {
@ -31,30 +31,12 @@ class Endpoints extends _i1.EndpointDispatch {
name: 'quote', name: 'quote',
endpoint: endpoints['quote']!, endpoint: endpoints['quote']!,
methodConnectors: { methodConnectors: {
'updateQuote': _i1.MethodConnector(
name: 'updateQuote',
params: {
'quote': _i1.ParameterDescription(
name: 'quote',
type: _i1.getType<_i3.Quote>(),
nullable: false,
)
},
call: (
_i1.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['quote'] as _i2.QuoteEndpoint).updateQuote(
session,
params['quote'],
),
),
'createQuote': _i1.MethodConnector( 'createQuote': _i1.MethodConnector(
name: 'createQuote', name: 'createQuote',
params: { params: {
'req': _i1.ParameterDescription( 'req': _i1.ParameterDescription(
name: 'req', name: 'req',
type: _i1.getType<_i4.CreateQuoteRequest>(), type: _i1.getType<_i3.CreateQuoteRequest>(),
nullable: false, nullable: false,
) )
}, },
@ -67,11 +49,29 @@ class Endpoints extends _i1.EndpointDispatch {
params['req'], params['req'],
), ),
), ),
'getQuoteById': _i1.MethodConnector( 'updateQuote': _i1.MethodConnector(
name: 'getQuoteById', name: 'updateQuote',
params: { params: {
'id': _i1.ParameterDescription( 'quote': _i1.ParameterDescription(
name: 'id', name: 'quote',
type: _i1.getType<_i4.Quote>(),
nullable: false,
)
},
call: (
_i1.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['quote'] as _i2.QuoteEndpoint).updateQuote(
session,
params['quote'],
),
),
'getAllQuotes': _i1.MethodConnector(
name: 'getAllQuotes',
params: {
'limit': _i1.ParameterDescription(
name: 'limit',
type: _i1.getType<int>(), type: _i1.getType<int>(),
nullable: false, nullable: false,
) )
@ -80,32 +80,11 @@ class Endpoints extends _i1.EndpointDispatch {
_i1.Session session, _i1.Session session,
Map<String, dynamic> params, Map<String, dynamic> params,
) async => ) async =>
(endpoints['quote'] as _i2.QuoteEndpoint).getQuoteById( (endpoints['quote'] as _i2.QuoteEndpoint).getAllQuotes(
session, session,
params['id'], limit: params['limit'],
), ),
), ),
'getAllQuotes': _i1.MethodConnector(
name: 'getAllQuotes',
params: {},
call: (
_i1.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['quote'] as _i2.QuoteEndpoint).getAllQuotes(session),
),
'quoteUpdates': _i1.MethodStreamConnector(
name: 'quoteUpdates',
params: {},
streamParams: {},
returnType: _i1.MethodStreamReturnType.streamType,
call: (
_i1.Session session,
Map<String, dynamic> params,
Map<String, Stream> streamParams,
) =>
(endpoints['quote'] as _i2.QuoteEndpoint).quoteUpdates(session),
),
}, },
); );
modules['serverpod_auth'] = _i5.Endpoints()..initializeEndpoints(server); modules['serverpod_auth'] = _i5.Endpoints()..initializeEndpoints(server);

View file

@ -1,6 +1,4 @@
quote: quote:
- updateQuote:
- quoteUpdates:
- createQuote: - createQuote:
- getQuoteById: - updateQuote:
- getAllQuotes: - getAllQuotes:

View file

@ -1,34 +1,22 @@
import 'dart:math'; // lib/src/endpoints/quote_endpoint.dart
import 'dart:async';
import 'package:serverpod/serverpod.dart'; import 'package:serverpod/serverpod.dart';
import 'package:wien_talks_server/src/generated/protocol.dart'; import 'package:wien_talks_server/src/generated/protocol.dart';
import 'package:wien_talks_server/src/quotes/quote_util.dart'; import 'package:wien_talks_server/src/quotes/quote_util.dart';
class QuoteEndpoint extends Endpoint { class ShowLatestNewsWidget extends Endpoint {
static const _channelQuoteUpdates = 'quote-updates'; static const _channelQuoteUpdates = 'quote-updates';
Future<void> updateQuote(Session session, Quote quote) async {
await Quote.db.updateRow(session, quote);
await session.messages.postMessage(_channelQuoteUpdates, quote);
}
Stream<Quote> quoteUpdates(Session session) async* {
var updateStream =
session.messages.createStream<Quote>(_channelQuoteUpdates);
await for (var quote in updateStream) {
yield quote;
}
}
Future<Quote> createQuote(Session session, CreateQuoteRequest req) async { Future<Quote> createQuote(Session session, CreateQuoteRequest req) async {
final authInfo = await session.authenticated; final authInfo = await session.authenticated;
final userId = Random().nextInt(100); final userId = authInfo?.userId;
String text = validateQuote(req); final text = validateQuote(req);
final quote = Quote( final toInsert = Quote(
userId: userId, id: 0,
userId: userId ?? 12,
text: text, text: text,
authorName: req.authorName, authorName: req.authorName,
lat: req.lat, lat: req.lat,
@ -39,36 +27,61 @@ class QuoteEndpoint extends Endpoint {
downvotes: 0, downvotes: 0,
); );
final inserted = await session.db.insertRow<Quote>(quote); final inserted = await session.db.insertRow<Quote>(toInsert);
await session.messages.postMessage(_channelQuoteUpdates, quote); await session.messages.postMessage(_channelQuoteUpdates, inserted);
return inserted; return inserted;
} }
Future<Quote> getQuoteById(Session session, int id) async { Future<void> updateQuote(Session session, Quote quote) async {
final quote = await Quote.db.findById(session, id); await Quote.db.updateRow(session, quote);
if (quote != null) { await session.messages.postMessage(_channelQuoteUpdates, quote);
return quote;
} }
throw Exception('Quote not found'); Future<List<Quote>> getAllQuotes(Session session, {int limit = 200}) async {
final quoteList = await Quote.db.find(session);
return quoteList;
} }
Future<List<Quote>> getAllQuotes(Session session) async { Future<Stream<Quote>> streamAllQuotes(StreamingSession session,
final quotes = await Quote.db.find(session); {int limit = 200}) async {
return quotes;
}
Stream streamAllQuotes(
StreamingSession session, {
int limit = 200,
}) async* {
if (limit <= 0 || limit > 500) limit = 200; if (limit <= 0 || limit > 500) limit = 200;
final quoteStream = session.messages.createStream<Quote>('quotes'); final controller = StreamController<Quote>();
final live = session.messages.createStream<Quote>(_channelQuoteUpdates);
final liveSub = live.listen(
(q) {
if (q.visibility == 0) controller.add(q);
},
onError: controller.addError,
onDone: () {
if (!controller.isClosed) controller.close();
},
cancelOnError: false,
);
await for (final Quote quote in quoteStream) { () async* {
yield quote; try {
final snapshot = await Quote.db.find(
session,
where: (t) => t.visibility.equals(0),
orderBy: (t) => t.createdAt,
orderDescending: true,
limit: limit,
);
for (final q in snapshot.reversed) {
controller.add(q);
} }
} catch (e, st) {
controller.addError(e, st);
}
}();
await session.close().then((_) async {
await liveSub.cancel();
await controller.close();
});
return controller.stream;
} }
} }

View file

@ -130,64 +130,6 @@ class _QuoteEndpoint {
final _i2.SerializationManager _serializationManager; final _i2.SerializationManager _serializationManager;
_i3.Future<void> updateQuote(
_i1.TestSessionBuilder sessionBuilder,
_i4.Quote quote,
) async {
return _i1.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _i1.InternalTestSessionBuilder).internalBuild(
endpoint: 'quote',
method: 'updateQuote',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'quote',
methodName: 'updateQuote',
parameters: _i1.testObjectToJson({'quote': quote}),
serializationManager: _serializationManager,
);
var _localReturnValue = await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
) as _i3.Future<void>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
_i3.Stream<_i4.Quote> quoteUpdates(_i1.TestSessionBuilder sessionBuilder) {
var _localTestStreamManager = _i1.TestStreamManager<_i4.Quote>();
_i1.callStreamFunctionAndHandleExceptions(
() async {
var _localUniqueSession =
(sessionBuilder as _i1.InternalTestSessionBuilder).internalBuild(
endpoint: 'quote',
method: 'quoteUpdates',
);
var _localCallContext =
await _endpointDispatch.getMethodStreamCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'quote',
methodName: 'quoteUpdates',
arguments: {},
requestedInputStreams: [],
serializationManager: _serializationManager,
);
await _localTestStreamManager.callStreamMethod(
_localCallContext,
_localUniqueSession,
{},
);
},
_localTestStreamManager.outputStreamController,
);
return _localTestStreamManager.outputStreamController.stream;
}
_i3.Future<_i4.Quote> createQuote( _i3.Future<_i4.Quote> createQuote(
_i1.TestSessionBuilder sessionBuilder, _i1.TestSessionBuilder sessionBuilder,
_i5.CreateQuoteRequest req, _i5.CreateQuoteRequest req,
@ -217,28 +159,28 @@ class _QuoteEndpoint {
}); });
} }
_i3.Future<_i4.Quote> getQuoteById( _i3.Future<void> updateQuote(
_i1.TestSessionBuilder sessionBuilder, _i1.TestSessionBuilder sessionBuilder,
int id, _i4.Quote quote,
) async { ) async {
return _i1.callAwaitableFunctionAndHandleExceptions(() async { return _i1.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession = var _localUniqueSession =
(sessionBuilder as _i1.InternalTestSessionBuilder).internalBuild( (sessionBuilder as _i1.InternalTestSessionBuilder).internalBuild(
endpoint: 'quote', endpoint: 'quote',
method: 'getQuoteById', method: 'updateQuote',
); );
try { try {
var _localCallContext = await _endpointDispatch.getMethodCallContext( var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession, createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'quote', endpointPath: 'quote',
methodName: 'getQuoteById', methodName: 'updateQuote',
parameters: _i1.testObjectToJson({'id': id}), parameters: _i1.testObjectToJson({'quote': quote}),
serializationManager: _serializationManager, serializationManager: _serializationManager,
); );
var _localReturnValue = await (_localCallContext.method.call( var _localReturnValue = await (_localCallContext.method.call(
_localUniqueSession, _localUniqueSession,
_localCallContext.arguments, _localCallContext.arguments,
) as _i3.Future<_i4.Quote>); ) as _i3.Future<void>);
return _localReturnValue; return _localReturnValue;
} finally { } finally {
await _localUniqueSession.close(); await _localUniqueSession.close();
@ -247,7 +189,9 @@ class _QuoteEndpoint {
} }
_i3.Future<List<_i4.Quote>> getAllQuotes( _i3.Future<List<_i4.Quote>> getAllQuotes(
_i1.TestSessionBuilder sessionBuilder) async { _i1.TestSessionBuilder sessionBuilder, {
required int limit,
}) async {
return _i1.callAwaitableFunctionAndHandleExceptions(() async { return _i1.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession = var _localUniqueSession =
(sessionBuilder as _i1.InternalTestSessionBuilder).internalBuild( (sessionBuilder as _i1.InternalTestSessionBuilder).internalBuild(
@ -259,7 +203,7 @@ class _QuoteEndpoint {
createSessionCallback: (_) => _localUniqueSession, createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'quote', endpointPath: 'quote',
methodName: 'getAllQuotes', methodName: 'getAllQuotes',
parameters: _i1.testObjectToJson({}), parameters: _i1.testObjectToJson({'limit': limit}),
serializationManager: _serializationManager, serializationManager: _serializationManager,
); );
var _localReturnValue = await (_localCallContext.method.call( var _localReturnValue = await (_localCallContext.method.call(