feat(todo): the server on halkyon_base

Todo's server includes halkyon_base (ADR 0014), configured for Todo:
its basics say it serves `todo`, sign-in mails name Halkyon Todo, the
source is this repository, minimumAppBuild 1. Authentication through
the module (anonymous, GitHub, email codes, server-side sessions) in
place of the scaffold's email-and-password provider; the module's
pruning scheduled once however often the server starts. The scaffold's
greeting, its Flutter web hosting, cloud storage and Docker files go;
session logs are never persisted. Todo was never deployed, so its
migrations start again with one initial migration. The root's lints
apply, with the four constructor rules off as on Learn's server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-19 21:56:12 +02:00
commit 28dd3ea65a
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
47 changed files with 1047 additions and 1741 deletions

View file

@ -10,9 +10,7 @@
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _ida;
import 'package:halkyon_todo_client/src/protocol/greetings/greeting.dart'
as _i5fdz6wc;
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
import 'package:http/http.dart' as _i85jenna;
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
as _iacc;
@ -21,213 +19,15 @@ import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import 'protocol.dart' as _il2as5qe;
/// By extending [EmailIdpBaseEndpoint], the email identity provider endpoints
/// are made available on the server and enable the corresponding sign-in widget
/// on the client.
/// {@category Endpoint}
class EndpointEmailIdp extends _iaic.EndpointEmailIdpBase {
EndpointEmailIdp(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'emailIdp';
/// Logs in the user and returns a new session.
///
/// Throws an [EmailAccountLoginException] in case of errors, with reason:
/// - [EmailAccountLoginExceptionReason.invalidCredentials] if the email or
/// password is incorrect.
/// - [EmailAccountLoginExceptionReason.tooManyAttempts] if there have been
/// too many failed login attempts.
///
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
@override
_ida.Future<_iacc.AuthSuccess> login({
required String email,
required String password,
}) => caller.callServerEndpoint<_iacc.AuthSuccess>(
'emailIdp',
'login',
{
'email': email,
'password': password,
},
);
/// Starts the registration for a new user account with an email-based login
/// associated to it.
///
/// Upon successful completion of this method, an email will have been
/// sent to [email] with a verification link, which the user must open to
/// complete the registration.
///
/// Always returns a account request ID, which can be used to complete the
/// registration. If the email is already registered, the returned ID will not
/// be valid.
@override
_ida.Future<_isc.UuidValue> startRegistration({required String email}) =>
caller.callServerEndpoint<_isc.UuidValue>(
'emailIdp',
'startRegistration',
{'email': email},
);
/// Verifies an account request code and returns a token
/// that can be used to complete the account creation.
///
/// Throws an [EmailAccountRequestException] in case of errors, with reason:
/// - [EmailAccountRequestExceptionReason.expired] if the account request has
/// already expired.
/// - [EmailAccountRequestExceptionReason.policyViolation] if the password
/// does not comply with the password policy.
/// - [EmailAccountRequestExceptionReason.invalid] if no request exists
/// for the given [accountRequestId] or [verificationCode] is invalid.
@override
_ida.Future<String> verifyRegistrationCode({
required _isc.UuidValue accountRequestId,
required String verificationCode,
}) => caller.callServerEndpoint<String>(
'emailIdp',
'verifyRegistrationCode',
{
'accountRequestId': accountRequestId,
'verificationCode': verificationCode,
},
);
/// Completes a new account registration, creating a new auth user with a
/// profile and attaching the given email account to it.
///
/// Throws an [EmailAccountRequestException] in case of errors, with reason:
/// - [EmailAccountRequestExceptionReason.expired] if the account request has
/// already expired.
/// - [EmailAccountRequestExceptionReason.policyViolation] if the password
/// does not comply with the password policy.
/// - [EmailAccountRequestExceptionReason.invalid] if the [registrationToken]
/// is invalid.
///
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
///
/// Returns a session for the newly created user.
@override
_ida.Future<_iacc.AuthSuccess> finishRegistration({
required String registrationToken,
required String password,
}) => caller.callServerEndpoint<_iacc.AuthSuccess>(
'emailIdp',
'finishRegistration',
{
'registrationToken': registrationToken,
'password': password,
},
);
/// Requests a password reset for [email].
///
/// If the email address is registered, an email with reset instructions will
/// be send out. If the email is unknown, this method will have no effect.
///
/// Always returns a password reset request ID, which can be used to complete
/// the reset. If the email is not registered, the returned ID will not be
/// valid.
///
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
/// - [EmailAccountPasswordResetExceptionReason.tooManyAttempts] if the user has
/// made too many attempts trying to request a password reset.
///
@override
_ida.Future<_isc.UuidValue> startPasswordReset({required String email}) =>
caller.callServerEndpoint<_isc.UuidValue>(
'emailIdp',
'startPasswordReset',
{'email': email},
);
/// Verifies a password reset code and returns a finishPasswordResetToken
/// that can be used to finish the password reset.
///
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
/// - [EmailAccountPasswordResetExceptionReason.expired] if the password reset
/// request has already expired.
/// - [EmailAccountPasswordResetExceptionReason.tooManyAttempts] if the user has
/// made too many attempts trying to verify the password reset.
/// - [EmailAccountPasswordResetExceptionReason.invalid] if no request exists
/// for the given [passwordResetRequestId] or [verificationCode] is invalid.
///
/// If multiple steps are required to complete the password reset, this endpoint
/// should be overridden to return credentials for the next step instead
/// of the credentials for setting the password.
@override
_ida.Future<String> verifyPasswordResetCode({
required _isc.UuidValue passwordResetRequestId,
required String verificationCode,
}) => caller.callServerEndpoint<String>(
'emailIdp',
'verifyPasswordResetCode',
{
'passwordResetRequestId': passwordResetRequestId,
'verificationCode': verificationCode,
},
);
/// Completes a password reset request by setting a new password.
///
/// The [verificationCode] returned from [verifyPasswordResetCode] is used to
/// validate the password reset request.
///
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
/// - [EmailAccountPasswordResetExceptionReason.expired] if the password reset
/// request has already expired.
/// - [EmailAccountPasswordResetExceptionReason.policyViolation] if the new
/// password does not comply with the password policy.
/// - [EmailAccountPasswordResetExceptionReason.invalid] if no request exists
/// for the given [passwordResetRequestId] or [verificationCode] is invalid.
///
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
@override
_ida.Future<void> finishPasswordReset({
required String finishPasswordResetToken,
required String newPassword,
}) => caller.callServerEndpoint<void>(
'emailIdp',
'finishPasswordReset',
{
'finishPasswordResetToken': finishPasswordResetToken,
'newPassword': newPassword,
},
);
@override
_ida.Future<bool> hasAccount() => caller.callServerEndpoint<bool>(
'emailIdp',
'hasAccount',
{},
);
}
/// This is an example endpoint that returns a greeting message through
/// its [hello] method.
/// {@category Endpoint}
class EndpointGreeting extends _isc.EndpointRef {
EndpointGreeting(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'greeting';
/// Returns a personalized greeting message: "Hello {name}".
_ida.Future<_i5fdz6wc.Greeting> hello(String name) =>
caller.callServerEndpoint<_i5fdz6wc.Greeting>(
'greeting',
'hello',
{'name': name},
);
}
class Modules {
Modules(Client client) {
base = _ihk4p5t7.Caller(client);
serverpod_auth_idp = _iaic.Caller(client);
serverpod_auth_core = _iacc.Caller(client);
}
late final _ihk4p5t7.Caller base;
late final _iaic.Caller serverpod_auth_idp;
late final _iacc.Caller serverpod_auth_core;
@ -260,25 +60,17 @@ class Client extends _isc.ServerpodClientShared {
disconnectStreamsOnLostInternetConnection,
httpClientOverride: httpClientOverride,
) {
emailIdp = EndpointEmailIdp(this);
greeting = EndpointGreeting(this);
modules = Modules(this);
}
late final EndpointEmailIdp emailIdp;
late final EndpointGreeting greeting;
late final Modules modules;
@override
Map<String, _isc.EndpointRef> get endpointRefLookup => {
'emailIdp': emailIdp,
'greeting': greeting,
};
Map<String, _isc.EndpointRef> get endpointRefLookup => {};
@override
Map<String, _isc.ModuleEndpointCaller> get moduleLookup => {
'base': modules.base,
'serverpod_auth_idp': modules.serverpod_auth_idp,
'serverpod_auth_core': modules.serverpod_auth_core,
};

View file

@ -1,109 +0,0 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// A greeting message which can be sent to or from the server.
abstract class Greeting
implements _isc.SerializableModel, _isc.ProtocolSerialization {
Greeting._({
required this.message,
required this.author,
required this.timestamp,
});
factory Greeting({
required String message,
required String author,
required DateTime timestamp,
}) = _GreetingImpl;
factory Greeting.fromJson(Map<String, dynamic> jsonSerialization) {
return Greeting(
message: jsonSerialization['message'] as String,
author: jsonSerialization['author'] as String,
timestamp: _isc.DateTimeJsonExtension.fromJson(
jsonSerialization['timestamp'],
),
);
}
/// The greeting message.
String message;
/// The author of the greeting message.
String author;
/// The time when the message was created.
DateTime timestamp;
/// Returns a shallow copy of this [Greeting]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
Greeting copyWith({
String? message,
String? author,
DateTime? timestamp,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'Greeting',
'message': message,
'author': author,
'timestamp': timestamp.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'Greeting',
'message': message,
'author': author,
'timestamp': timestamp.toJson(),
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _GreetingImpl extends Greeting {
_GreetingImpl({
required String message,
required String author,
required DateTime timestamp,
}) : super._(
message: message,
author: author,
timestamp: timestamp,
);
/// Returns a shallow copy of this [Greeting]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
Greeting copyWith({
String? message,
String? author,
DateTime? timestamp,
}) {
return Greeting(
message: message ?? this.message,
author: author ?? this.author,
timestamp: timestamp ?? this.timestamp,
);
}
}

View file

@ -11,13 +11,12 @@
// ignore_for_file: dead_code, unnecessary_type_check
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
as _iacc;
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
as _iaic;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import 'greetings/greeting.dart' as _izw8z7ou;
export 'greetings/greeting.dart';
export 'client.dart';
class Protocol extends _isc.SerializationManager {
@ -54,12 +53,9 @@ class Protocol extends _isc.SerializationManager {
}
}
if (t == _izw8z7ou.Greeting) {
return _izw8z7ou.Greeting.fromJson(data) as T;
}
if (t == _isc.getType<_izw8z7ou.Greeting?>()) {
return (data != null ? _izw8z7ou.Greeting.fromJson(data) : null) as T;
}
try {
return _ihk4p5t7.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
try {
return _iaic.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
@ -71,7 +67,6 @@ class Protocol extends _isc.SerializationManager {
static String? getClassNameForType(Type type) {
return switch (type) {
_izw8z7ou.Greeting => 'Greeting',
_ => null,
};
}
@ -88,9 +83,9 @@ class Protocol extends _isc.SerializationManager {
);
}
switch (data) {
case _izw8z7ou.Greeting():
return 'Greeting';
className = _ihk4p5t7.Protocol().getClassNameForObject(data);
if (className != null) {
return className.contains('.') ? className : 'halkyon_base.$className';
}
className = _iaic.Protocol().getClassNameForObject(data);
if (className != null) {
@ -113,8 +108,9 @@ class Protocol extends _isc.SerializationManager {
if (dataClassName is! String) {
return super.deserializeByClassName(data);
}
if (dataClassName == 'Greeting') {
return deserialize<_izw8z7ou.Greeting>(data['data']);
if (dataClassName.startsWith('halkyon_base.')) {
data['className'] = dataClassName.substring(13);
return _ihk4p5t7.Protocol().deserializeByClassName(data);
}
if (dataClassName.startsWith('serverpod_auth_idp.')) {
data['className'] = dataClassName.substring(19);
@ -128,6 +124,7 @@ class Protocol extends _isc.SerializationManager {
}
void _registerHostProtocols() {
_ihk4p5t7.Protocol().registerHostProtocol('halkyon_todo', this);
_iaic.Protocol().registerHostProtocol('halkyon_todo', this);
_iacc.Protocol().registerHostProtocol('halkyon_todo', this);
}
@ -144,6 +141,9 @@ class Protocol extends _isc.SerializationManager {
if (record == null) {
return null;
}
try {
return _ihk4p5t7.Protocol().mapRecordToJson(record);
} catch (_) {}
try {
return _iaic.Protocol().mapRecordToJson(record);
} catch (_) {}

View file

@ -1,5 +1,6 @@
name: halkyon_todo_client
description: Starting point for a Serverpod client.
description: Halkyon Todo's client, generated by Serverpod.
publish_to: none
environment:
sdk: '^3.13.0'
@ -7,5 +8,8 @@ environment:
resolution: workspace
dependencies:
# The client side of halkyon_base (ADR 0014), as the server's.
halkyon_base_client:
path: ../../../packages/halkyon_base_client
serverpod_auth_idp_client: 4.0.4
serverpod_client: 4.0.4

View file

@ -1,58 +0,0 @@
# Build from the project root with:
# docker build -f halkyon_todo_server/Dockerfile .
# Build stage
FROM dart:3.12.2 AS build
# Create a minimal workspace that preserves locked server dependency versions.
WORKDIR /app
COPY pubspec.lock .
COPY halkyon_todo_server halkyon_todo_server
RUN printf '%s\n' \
'name: _' \
'environment:' \
' sdk: ^3.12.2' \
'workspace:' \
' - halkyon_todo_server' \
> pubspec.yaml
RUN dart pub get
# Compile the server executable.
WORKDIR /app/halkyon_todo_server
RUN dart build cli --target bin/main.dart --output build
RUN mv build/bundle/bin/main build/bundle/bin/server
# Add a fallback for the copy of possibly missing directories.
RUN mkdir -p config web migrations
# Final stage
FROM alpine:latest
WORKDIR /app
# Environment variables
ENV runmode=production
ENV serverid=default
ENV logging=normal
ENV role=monolith
# Copy runtime dependencies
COPY --from=build /runtime/ /
# Copy compiled server executable and native libraries
COPY --from=build /app/halkyon_todo_server/build/bundle/ ./
# Copy configuration files and resources
COPY --from=build /app/halkyon_todo_server/config/ config/
COPY --from=build /app/halkyon_todo_server/web/ web/
COPY --from=build /app/halkyon_todo_server/migrations/ migrations/
# This file is required to enable the endpoint log filter in Insights.
COPY --from=build /app/halkyon_todo_server/lib/src/generated/protocol.yaml lib/src/generated/protocol.yaml
# Expose ports
EXPOSE 8080
EXPOSE 8081
EXPOSE 8082
# Define the entrypoint command
ENTRYPOINT ./bin/server --mode=$runmode --server-id=$serverid --logging=$logging --role=$role

View file

@ -1,34 +1,21 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
linter:
rules:
unawaited_futures: true
avoid_print: true
# The workspace's lint rules (see ../../../analysis_options.yaml), minus generated code.
include: ../../../analysis_options.yaml
analyzer:
exclude:
- lib/src/generated/**
- test/integration/test_tools/serverpod_test_tools.dart
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
# Serverpod's code generator reads this package with an analyzer that doesn't know
# Dart v3.13's primary constructors yet, and fails on them in endpoint files, so the
# server keeps the classic constructor and class-body syntax, as Learn's does; switch
# these back on once Serverpod parses it.
linter:
rules:
empty_container_bodies: false
unnecessary_type_name_in_constructor: false
use_declaring_parameters: false
use_primary_constructors: false
formatter:
trailing_commas: preserve

View file

@ -1,7 +1,4 @@
import 'package:halkyon_todo_server/server.dart';
/// This is the starting point for your Serverpod server. Typically, there is
/// no need to modify this file.
void main(List<String> args) {
run(args);
}
/// Starts Halkyon Todo's server (lib/server.dart).
Future<void> main(List<String> args) => run(args);

View file

@ -41,8 +41,9 @@ database:
maxRequestSize: 524288 # The maximum size of requests allowed in bytes
# Session logs stay out of the database, for privacy, as Learn's (never re-enable).
sessionLogs:
persistentEnabled: true
persistentEnabled: false
consoleEnabled: true
consoleLogFormat: text # Defaults to "json", options are "text" or "json"

View file

@ -2,3 +2,8 @@ type: server
client_package_path: ../halkyon_todo_client
server_test_tools_path: test/integration/test_tools
modules:
# What every Halkyon server has (ADR 0014): `client.modules.base` in the app.
halkyon_base:
nickname: base

View file

@ -41,9 +41,10 @@ database:
maxRequestSize: 524288 # The maximum size of requests allowed in bytes
# Session logs stay out of the database, for privacy, as Learn's (never re-enable).
sessionLogs:
consoleEnabled: false
# persistentEnabled: true
persistentEnabled: false
# consoleLogFormat: json # Defaults to "json", options are "text" or "json"
#futureCall:

View file

@ -45,8 +45,9 @@ database:
maxRequestSize: 524288 # The maximum size of requests allowed in bytes
# Session logs stay out of the database, for privacy, as Learn's (never re-enable).
sessionLogs:
# persistentEnabled: true
persistentEnabled: false
# consoleEnabled: true
# consoleLogFormat: json # Defaults to "json", options are "text" or "json"

View file

@ -38,8 +38,9 @@ database:
#maxConnectionCount: 10 # Defaults to 10, a negative or null value removes the limit
# Session logs stay out of the database, for privacy, as Learn's (never re-enable).
sessionLogs:
persistentEnabled: true
persistentEnabled: false
consoleEnabled: true
# consoleLogFormat: json # Defaults to "json", options are "text" or "json"

View file

@ -1,30 +0,0 @@
services:
# Development services
postgres:
image: ghcr.io/serverpod/postgres:16
ports:
- "8090:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_DB: halkyon_todo
POSTGRES_PASSWORD: "aKpX2DK-n57tgkIr0XqP0hZSxmcwLQdj"
volumes:
- halkyon_todo_data:/var/lib/postgresql/data
# Test services
postgres_test:
image: ghcr.io/serverpod/postgres:16
ports:
- "9090:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_DB: halkyon_todo_test
POSTGRES_PASSWORD: "ls3BpmIUIv2u_Ud0eZ2h3bunjU5nDzkh"
volumes:
- halkyon_todo_test_data:/var/lib/postgresql/data
volumes:
halkyon_todo_data:
halkyon_todo_test_data:

View file

@ -1,109 +1,25 @@
import 'dart:io';
import 'package:halkyon_base_server/base.dart';
import 'package:halkyon_todo_server/src/generated/serverpod.dart';
import 'package:halkyon_todo_server/src/pruning.dart';
import 'package:halkyon_todo_server/src/todo_base.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:serverpod_auth_idp_server/providers/email.dart';
import 'package:serverpod_cloud_storage/serverpod_cloud_storage.dart';
import 'src/cache_busting.dart';
import 'src/generated/serverpod.dart';
import 'src/web/routes/app_config_route.dart';
/// The starting point of the Serverpod server.
void run(List<String> args) async {
// Initialize Serverpod. The generated Serverpod class is already connected
// with your project's generated code.
/// The starting point of Halkyon Todo's server.
Future<void> run(List<String> args) async {
// The generated Serverpod is already connected with the generated code.
final pod = Serverpod(args);
// Initialize authentication services for the server.
// Token managers will be used to validate and issue authentication keys,
// and the identity providers will be the authentication options available for users.
// What every Halkyon server has (ADR 0014): sign-in, the account and its
// deletion, the pruning, the instance's basics, set up for Todo.
HalkyonBase.configure(pod, todoBase());
// Token managers validate and issue authentication keys; identity
// providers are the ways people can sign in (halkyon_base's
// auth_config.dart).
pod.initializeAuthServices(
tokenManagerBuilders: [
// Server-side sessions: every request looks its session up in the
// database, so signing out or deleting an account takes effect at once.
// A session unused for 90 days expires; one in use never does.
ServerSideSessionsConfigFromPasswords(
defaultSessionInactivityTimeout: const Duration(days: 90),
),
],
identityProviderBuilders: [
// Configure the email identity provider for email/password authentication.
// The default setup works with Serverpod Cloud without configuration. In
// development the verification codes are logged to the console, and in
// staging and production they are sent through the Serverpod Cloud email
// service. If you want to use a custom provider for sending emails, use
// `EmailIdpConfigFromPasswords`.
ServerpodCloudEmailIdpConfig(
appDisplayName: 'halkyon_todo',
),
],
tokenManagerBuilders: tokenManagerBuilders,
identityProviderBuilders: identityProviderBuilders(pod),
);
// Serve all files in the web/static relative directory under /web.
// These are used by the default web page.
pod.webServer.addRoute(
StaticRoute.withCacheBusting(cacheBustingConfig),
cacheBustingConfig.mountPrefix,
);
// Setup the app config route.
// We build this configuration based on the servers api url and serve it to
// the flutter app.
pod.webServer.addRoute(
AppConfigRoute(apiConfig: pod.config.apiServer),
'/assets/assets/config.json',
);
// Checks if the flutter web app has been built and serves it if it has.
final appDir = Directory(Uri(path: 'web/app').toFilePath());
if (appDir.existsSync()) {
// Serve the flutter web app under /.
pod.webServer.addRoute(
FlutterRoute(
appDir,
// If building the Flutter app with WASM, set the below parameter to
// true and add the --wasm flag to the flutter build command.
enableWasmHeaders: false,
),
'/',
);
} else {
// If the flutter web app has not been built, serve the build app page.
final defaultRoute = StaticRoute.file(
File(
Uri(path: 'web/pages/build_flutter_app.html').toFilePath(),
),
);
pod.webServer.addMiddleware(
FallbackMiddleware(
fallback: defaultRoute,
on: (response) => response.statusCode == 404,
).call,
'/',
);
pod.webServer.addRoute(
defaultRoute,
'/**',
);
}
// Configure cloud storage.
// This setup works with Serverpod Cloud without extra configuration.
// If you want to use a custom provider for cloud storage, replace these
// with your preferred provider.
pod.addCloudStorage(
await ServerpodCloudProvider.private(
fallback: () => DatabaseCloudStorage('private'),
),
);
pod.addCloudStorage(
await ServerpodCloudProvider.public(
fallback: () => DatabaseCloudStorage('public'),
),
);
// Start the server.
await pod.start();
await schedulePruning(pod);
}

View file

@ -1,6 +0,0 @@
import 'package:serverpod_auth_idp_server/providers/email.dart';
/// By extending [EmailIdpBaseEndpoint], the email identity provider endpoints
/// are made available on the server and enable the corresponding sign-in widget
/// on the client.
class EmailIdpEndpoint extends EmailIdpBaseEndpoint {}

View file

@ -1,10 +0,0 @@
import 'dart:io';
import 'package:serverpod/serverpod.dart';
/// Cache busting configuration.
/// Used in WidgetRoutes to cache-bust asset paths.
final cacheBustingConfig = CacheBustingConfig(
mountPrefix: '/web',
fileSystemRoot: Directory(Uri(path: 'web/static').toFilePath()),
);

View file

@ -10,239 +10,29 @@
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_server/halkyon_base_server.dart' as _ik8molhd;
import 'package:halkyon_todo_server/src/generated/future_calls.dart'
as _i772x17y;
import 'package:serverpod/serverpod.dart' as _is;
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
as _iacs;
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
as _iais;
import '../auth/email_idp_endpoint.dart' as _iuc1hd5t;
import '../greetings/greeting_endpoint.dart' as _il624ik7;
export 'future_calls.dart' show ServerpodFutureCallsGetter;
class Endpoints extends _is.EndpointDispatch {
@override
void initializeEndpoints(_is.Server server) {
var endpoints = <String, _is.Endpoint>{
'emailIdp': _iuc1hd5t.EmailIdpEndpoint()
..initialize(
server,
'emailIdp',
null,
),
'greeting': _il624ik7.GreetingEndpoint()
..initialize(
server,
'greeting',
null,
),
};
connectors['emailIdp'] = _is.EndpointConnector(
name: 'emailIdp',
endpoint: endpoints['emailIdp']!,
methodConnectors: {
'login': _is.MethodConnector(
name: 'login',
params: {
'email': _is.ParameterDescription(
name: 'email',
type: _is.getType<String>(),
nullable: false,
),
'password': _is.ParameterDescription(
name: 'password',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['emailIdp'] as _iuc1hd5t.EmailIdpEndpoint).login(
session,
email: params['email'],
password: params['password'],
),
),
'startRegistration': _is.MethodConnector(
name: 'startRegistration',
params: {
'email': _is.ParameterDescription(
name: 'email',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async => (endpoints['emailIdp'] as _iuc1hd5t.EmailIdpEndpoint)
.startRegistration(
session,
email: params['email'],
),
),
'verifyRegistrationCode': _is.MethodConnector(
name: 'verifyRegistrationCode',
params: {
'accountRequestId': _is.ParameterDescription(
name: 'accountRequestId',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'verificationCode': _is.ParameterDescription(
name: 'verificationCode',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async => (endpoints['emailIdp'] as _iuc1hd5t.EmailIdpEndpoint)
.verifyRegistrationCode(
session,
accountRequestId: params['accountRequestId'],
verificationCode: params['verificationCode'],
),
),
'finishRegistration': _is.MethodConnector(
name: 'finishRegistration',
params: {
'registrationToken': _is.ParameterDescription(
name: 'registrationToken',
type: _is.getType<String>(),
nullable: false,
),
'password': _is.ParameterDescription(
name: 'password',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async => (endpoints['emailIdp'] as _iuc1hd5t.EmailIdpEndpoint)
.finishRegistration(
session,
registrationToken: params['registrationToken'],
password: params['password'],
),
),
'startPasswordReset': _is.MethodConnector(
name: 'startPasswordReset',
params: {
'email': _is.ParameterDescription(
name: 'email',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async => (endpoints['emailIdp'] as _iuc1hd5t.EmailIdpEndpoint)
.startPasswordReset(
session,
email: params['email'],
),
),
'verifyPasswordResetCode': _is.MethodConnector(
name: 'verifyPasswordResetCode',
params: {
'passwordResetRequestId': _is.ParameterDescription(
name: 'passwordResetRequestId',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'verificationCode': _is.ParameterDescription(
name: 'verificationCode',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async => (endpoints['emailIdp'] as _iuc1hd5t.EmailIdpEndpoint)
.verifyPasswordResetCode(
session,
passwordResetRequestId: params['passwordResetRequestId'],
verificationCode: params['verificationCode'],
),
),
'finishPasswordReset': _is.MethodConnector(
name: 'finishPasswordReset',
params: {
'finishPasswordResetToken': _is.ParameterDescription(
name: 'finishPasswordResetToken',
type: _is.getType<String>(),
nullable: false,
),
'newPassword': _is.ParameterDescription(
name: 'newPassword',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async => (endpoints['emailIdp'] as _iuc1hd5t.EmailIdpEndpoint)
.finishPasswordReset(
session,
finishPasswordResetToken:
params['finishPasswordResetToken'],
newPassword: params['newPassword'],
),
),
'hasAccount': _is.MethodConnector(
name: 'hasAccount',
params: {},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async => (endpoints['emailIdp'] as _iuc1hd5t.EmailIdpEndpoint)
.hasAccount(session),
),
},
);
connectors['greeting'] = _is.EndpointConnector(
name: 'greeting',
endpoint: endpoints['greeting']!,
methodConnectors: {
'hello': _is.MethodConnector(
name: 'hello',
params: {
'name': _is.ParameterDescription(
name: 'name',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['greeting'] as _il624ik7.GreetingEndpoint).hello(
session,
params['name'],
),
),
},
);
modules['halkyon_base'] = _ik8molhd.Endpoints()
..initializeEndpoints(server);
modules['serverpod_auth_idp'] = _iais.Endpoints()
..initializeEndpoints(server);
modules['serverpod_auth_core'] = _iacs.Endpoints()
..initializeEndpoints(server);
}
@override
_is.FutureCallDispatch? get futureCalls {
return _i772x17y.FutureCalls();
}
}

View file

@ -0,0 +1,232 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: depend_on_referenced_packages
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _ida;
import 'package:clock/clock.dart' as _io0w16m8;
import 'package:serverpod/serverpod.dart' as _is;
import '../pruning_future_call.dart' as _ibrplims;
/// Invokes a future call.
typedef _InvokeFutureCall =
Future<void> Function(String name, _is.SerializableModel? object);
extension ServerpodFutureCallsGetter on _is.Serverpod {
/// Generated future calls.
FutureCalls get futureCalls => FutureCalls();
}
class FutureCalls extends _is.FutureCallDispatch<_FutureCallRef> {
FutureCalls._();
factory FutureCalls() {
return _instance;
}
static final FutureCalls _instance = FutureCalls._();
_is.FutureCallManager? _futureCallManager;
String? _serverId;
String get _effectiveServerId {
if (_serverId == null) {
throw StateError('FutureCalls is not initialized.');
}
return _serverId!;
}
_is.FutureCallManager get _effectiveFutureCallManager {
if (_futureCallManager == null) {
throw StateError('FutureCalls is not initialized.');
}
return _futureCallManager!;
}
@override
void initialize(
_is.FutureCallManager futureCallManager,
String serverId,
) {
var registeredFutureCalls = <String, _is.InvokableFutureCall>{
'PruningPruneAnonymousPeopleFutureCall':
PruningPruneAnonymousPeopleFutureCall(),
'PruningForgetSignUpAttemptsFutureCall':
PruningForgetSignUpAttemptsFutureCall(),
};
_futureCallManager = futureCallManager;
_serverId = serverId;
for (final entry in registeredFutureCalls.entries) {
_futureCallManager?.registerFutureCall(entry.value, entry.key);
}
}
@override
_FutureCallRef callAtTime(
DateTime time, {
String? identifier,
}) {
return _FutureCallRef(
(name, object) {
return _effectiveFutureCallManager.scheduleFutureCall(
name,
object,
time,
_effectiveServerId,
identifier,
);
},
);
}
@override
_FutureCallRef callWithDelay(
Duration delay, {
String? identifier,
}) {
return _FutureCallRef(
(name, object) {
return _effectiveFutureCallManager.scheduleFutureCall(
name,
object,
DateTime.now().toUtc().add(delay),
_effectiveServerId,
identifier,
);
},
);
}
@override
_is.RecurringFutureCallDispatch<_FutureCallRef> callRecurring({
String? identifier,
}) {
return _RecurringFutureCallDispatchImpl(
_effectiveFutureCallManager,
_effectiveServerId,
identifier,
);
}
@override
Future<void> cancel(String identifier) async {
await _effectiveFutureCallManager.cancelFutureCall(identifier);
}
}
class _RecurringFutureCallDispatchImpl
extends _is.RecurringFutureCallDispatch<_FutureCallRef> {
_RecurringFutureCallDispatchImpl(
this._futureCallManager,
this._serverId,
this._identifier,
);
final _is.FutureCallManager _futureCallManager;
final String _serverId;
final String? _identifier;
@override
_FutureCallRef cron(String cronExpression) {
return _FutureCallRef(
(name, object) {
return _futureCallManager.scheduleFutureCall(
name,
object,
_is.Cron.parse(cronExpression).nextTime(),
_serverId,
_identifier,
scheduling: _is.CronFutureCallScheduling(cron: cronExpression),
);
},
);
}
@override
_FutureCallRef every(
Duration interval, {
DateTime? start,
}) {
final now = _io0w16m8.clock.now().toUtc();
return _FutureCallRef(
(name, object) {
return _futureCallManager.scheduleFutureCall(
name,
object,
start ?? now.add(interval),
_serverId,
_identifier,
scheduling: _is.IntervalFutureCallScheduling(
interval: interval,
start: start,
),
);
},
);
}
}
class _FutureCallRef {
_FutureCallRef(this._invokeFutureCall);
final _InvokeFutureCall _invokeFutureCall;
late final pruning = _PruningFutureCallDispatcher(_invokeFutureCall);
}
class _PruningFutureCallDispatcher {
_PruningFutureCallDispatcher(this._invokeFutureCall);
final _InvokeFutureCall _invokeFutureCall;
Future<void> pruneAnonymousPeople() {
return _invokeFutureCall(
'PruningPruneAnonymousPeopleFutureCall',
null,
);
}
Future<void> forgetSignUpAttempts() {
return _invokeFutureCall(
'PruningForgetSignUpAttemptsFutureCall',
null,
);
}
}
/// Prunes one batch and logs how many people went.
class PruningPruneAnonymousPeopleFutureCall extends _is.FutureCall
implements _is.InvokableFutureCall {
@override
_ida.Future<void> invoke(
_is.Session session,
_is.SerializableModel? object,
) async {
await _ibrplims.PruningFutureCall().pruneAnonymousPeople(session);
}
}
/// Forgets the sign-up attempts the rate limit is done with, with their IP
/// addresses, and the email sign-in codes past their time, and logs how
/// many.
class PruningForgetSignUpAttemptsFutureCall extends _is.FutureCall
implements _is.InvokableFutureCall {
@override
_ida.Future<void> invoke(
_is.Session session,
_is.SerializableModel? object,
) async {
await _ibrplims.PruningFutureCall().forgetSignUpAttempts(session);
}
}

View file

@ -1,109 +0,0 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
/// A greeting message which can be sent to or from the server.
abstract class Greeting
implements _is.SerializableModel, _is.ProtocolSerialization {
Greeting._({
required this.message,
required this.author,
required this.timestamp,
});
factory Greeting({
required String message,
required String author,
required DateTime timestamp,
}) = _GreetingImpl;
factory Greeting.fromJson(Map<String, dynamic> jsonSerialization) {
return Greeting(
message: jsonSerialization['message'] as String,
author: jsonSerialization['author'] as String,
timestamp: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['timestamp'],
),
);
}
/// The greeting message.
String message;
/// The author of the greeting message.
String author;
/// The time when the message was created.
DateTime timestamp;
/// Returns a shallow copy of this [Greeting]
/// with some or all fields replaced by the given arguments.
@_is.useResult
Greeting copyWith({
String? message,
String? author,
DateTime? timestamp,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'Greeting',
'message': message,
'author': author,
'timestamp': timestamp.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'Greeting',
'message': message,
'author': author,
'timestamp': timestamp.toJson(),
};
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _GreetingImpl extends Greeting {
_GreetingImpl({
required String message,
required String author,
required DateTime timestamp,
}) : super._(
message: message,
author: author,
timestamp: timestamp,
);
/// Returns a shallow copy of this [Greeting]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
Greeting copyWith({
String? message,
String? author,
DateTime? timestamp,
}) {
return Greeting(
message: message ?? this.message,
author: author ?? this.author,
timestamp: timestamp ?? this.timestamp,
);
}
}

View file

@ -11,14 +11,13 @@
// ignore_for_file: dead_code, unnecessary_type_check
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_server/halkyon_base_server.dart' as _ik8molhd;
import 'package:serverpod/protocol.dart' as _isp;
import 'package:serverpod/serverpod.dart' as _is;
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
as _iacs;
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
as _iais;
import 'greetings/greeting.dart' as _izw8z7ou;
export 'greetings/greeting.dart';
class Protocol extends _is.DatabaseSerializationManager {
Protocol._();
@ -28,6 +27,7 @@ class Protocol extends _is.DatabaseSerializationManager {
static final Protocol _instance = Protocol._().._registerHostProtocols();
static List<_isp.TableDefinition> get targetTableDefinitions => [
..._ik8molhd.Protocol.targetTableDefinitions,
..._iais.Protocol.targetTableDefinitions,
..._iacs.Protocol.targetTableDefinitions,
..._isp.Protocol.targetTableDefinitions,
@ -60,12 +60,9 @@ class Protocol extends _is.DatabaseSerializationManager {
}
}
if (t == _izw8z7ou.Greeting) {
return _izw8z7ou.Greeting.fromJson(data) as T;
}
if (t == _is.getType<_izw8z7ou.Greeting?>()) {
return (data != null ? _izw8z7ou.Greeting.fromJson(data) : null) as T;
}
try {
return _ik8molhd.Protocol().deserialize<T>(data, t);
} on _is.DeserializationTypeNotFoundException catch (_) {}
try {
return _iais.Protocol().deserialize<T>(data, t);
} on _is.DeserializationTypeNotFoundException catch (_) {}
@ -80,7 +77,6 @@ class Protocol extends _is.DatabaseSerializationManager {
static String? getClassNameForType(Type type) {
return switch (type) {
_izw8z7ou.Greeting => 'Greeting',
_ => null,
};
}
@ -97,9 +93,9 @@ class Protocol extends _is.DatabaseSerializationManager {
);
}
switch (data) {
case _izw8z7ou.Greeting():
return 'Greeting';
className = _ik8molhd.Protocol().getClassNameForObject(data);
if (className != null) {
return className.contains('.') ? className : 'halkyon_base.$className';
}
className = _iais.Protocol().getClassNameForObject(data);
if (className != null) {
@ -126,8 +122,9 @@ class Protocol extends _is.DatabaseSerializationManager {
if (dataClassName is! String) {
return super.deserializeByClassName(data);
}
if (dataClassName == 'Greeting') {
return deserialize<_izw8z7ou.Greeting>(data['data']);
if (dataClassName.startsWith('halkyon_base.')) {
data['className'] = dataClassName.substring(13);
return _ik8molhd.Protocol().deserializeByClassName(data);
}
if (dataClassName.startsWith('serverpod_auth_idp.')) {
data['className'] = dataClassName.substring(19);
@ -145,12 +142,19 @@ class Protocol extends _is.DatabaseSerializationManager {
}
void _registerHostProtocols() {
_ik8molhd.Protocol().registerHostProtocol('halkyon_todo', this);
_iais.Protocol().registerHostProtocol('halkyon_todo', this);
_iacs.Protocol().registerHostProtocol('halkyon_todo', this);
}
@override
_is.Table? getTableForType(Type t) {
{
var table = _ik8molhd.Protocol().getTableForType(t);
if (table != null) {
return table;
}
}
{
var table = _iais.Protocol().getTableForType(t);
if (table != null) {
@ -188,6 +192,9 @@ class Protocol extends _is.DatabaseSerializationManager {
if (record == null) {
return null;
}
try {
return _ik8molhd.Protocol().mapRecordToJson(record);
} catch (_) {}
try {
return _iais.Protocol().mapRecordToJson(record);
} catch (_) {}

Some files were not shown because too many files have changed in this diff Show more