@@ -29,7 +29,7 @@ use spacetimedb::messages::control_db::{Database, HostType};
2929use spacetimedb_client_api_messages:: name:: { self , DatabaseName , DomainName , PublishOp , PublishResult } ;
3030use spacetimedb_lib:: db:: raw_def:: v9:: RawModuleDefV9 ;
3131use spacetimedb_lib:: identity:: AuthCtx ;
32- use spacetimedb_lib:: sats;
32+ use spacetimedb_lib:: { sats, Timestamp } ;
3333
3434use super :: subscribe:: handle_websocket;
3535
@@ -371,7 +371,7 @@ fn mime_ndjson() -> mime::Mime {
371371 "application/x-ndjson" . parse ( ) . unwrap ( )
372372}
373373
374- async fn worker_ctx_find_database (
374+ pub ( crate ) async fn worker_ctx_find_database (
375375 worker_ctx : & ( impl ControlStateDelegate + ?Sized ) ,
376376 database_identity : & Identity ,
377377) -> axum:: response:: Result < Option < Database > > {
@@ -704,6 +704,18 @@ pub async fn set_names<S: ControlStateDelegate>(
704704 ) ) ;
705705 }
706706
707+ for name in & validated_names {
708+ if ctx. lookup_identity ( name. as_str ( ) ) . unwrap ( ) . is_some ( ) {
709+ return Ok ( (
710+ StatusCode :: BAD_REQUEST ,
711+ axum:: Json ( name:: SetDomainsResult :: OtherError ( format ! (
712+ "Cannot rename to {} because it already is in use." ,
713+ name. as_str( )
714+ ) ) ) ,
715+ ) ) ;
716+ }
717+ }
718+
707719 let response = ctx
708720 . replace_dns_records ( & database_identity, & database. owner_identity , & validated_names)
709721 . await
@@ -720,6 +732,33 @@ pub async fn set_names<S: ControlStateDelegate>(
720732 Ok ( ( status, axum:: Json ( response) ) )
721733}
722734
735+ #[ derive( serde:: Deserialize ) ]
736+ pub struct TimestampParams {
737+ name_or_identity : NameOrIdentity ,
738+ }
739+
740+ /// Returns the database's view of the current time,
741+ /// as a SATS-JSON encoded [`Timestamp`].
742+ ///
743+ /// Takes a particular database's [`NameOrIdentity`] as an argument
744+ /// because in a clusterized SpacetimeDB-cloud deployment,
745+ /// this request will be routed to the node running the requested database.
746+ async fn get_timestamp < S : ControlStateDelegate > (
747+ State ( worker_ctx) : State < S > ,
748+ Path ( TimestampParams { name_or_identity } ) : Path < TimestampParams > ,
749+ ) -> axum:: response:: Result < impl IntoResponse > {
750+ let db_identity = name_or_identity. resolve ( & worker_ctx) . await ?;
751+
752+ let _database = worker_ctx_find_database ( & worker_ctx, & db_identity)
753+ . await ?
754+ . ok_or_else ( || {
755+ log:: error!( "Could not find database: {}" , db_identity. to_hex( ) ) ;
756+ NO_SUCH_DATABASE
757+ } ) ?;
758+
759+ Ok ( axum:: Json ( sats:: serde:: SerdeWrapper ( Timestamp :: now ( ) ) ) . into_response ( ) )
760+ }
761+
723762/// This struct allows the edition to customize `/database` routes more meticulously.
724763pub struct DatabaseRoutes < S > {
725764 /// POST /database
@@ -748,6 +787,9 @@ pub struct DatabaseRoutes<S> {
748787 pub logs_get : MethodRouter < S > ,
749788 /// POST: /database/:name_or_identity/sql
750789 pub sql_post : MethodRouter < S > ,
790+
791+ /// GET: /database/: name_or_identity/unstable/timestamp
792+ pub timestamp_get : MethodRouter < S > ,
751793}
752794
753795impl < S > Default for DatabaseRoutes < S >
@@ -770,6 +812,7 @@ where
770812 schema_get : get ( schema :: < S > ) ,
771813 logs_get : get ( logs :: < S > ) ,
772814 sql_post : post ( sql :: < S > ) ,
815+ timestamp_get : get ( get_timestamp :: < S > ) ,
773816 }
774817 }
775818}
@@ -791,7 +834,8 @@ where
791834 . route ( "/call/:reducer" , self . call_reducer_post )
792835 . route ( "/schema" , self . schema_get )
793836 . route ( "/logs" , self . logs_get )
794- . route ( "/sql" , self . sql_post ) ;
837+ . route ( "/sql" , self . sql_post )
838+ . route ( "/unstable/timestamp" , self . timestamp_get ) ;
795839
796840 axum:: Router :: new ( )
797841 . route ( "/" , self . root_post )
0 commit comments