1515
1616from ...common .graph import graph_from_edge_list_v2 , graph_to_dict
1717from ...common .logging import LOGGER
18- from ...db .campaigns_v2 import Campaign , CampaignUpdate , Edge , Node
18+ from ...common .timestamp import element_time
19+ from ...db .campaigns_v2 import Campaign , CampaignUpdate , Edge , Manifest , Node
1920from ...db .manifests_v2 import CampaignManifest
2021from ...db .session import db_session_dependency
2122
@@ -42,10 +43,16 @@ async def read_campaign_collection(
4243 offset : Annotated [int , Query ()] = 0 ,
4344) -> Sequence [Campaign ]:
4445 """A paginated API returning a list of all Campaigns known to the
45- application.
46+ application, from newest to oldest .
4647 """
4748 try :
48- campaigns = await session .exec (select (Campaign ).offset (offset ).limit (limit ))
49+ statement = (
50+ select (Campaign )
51+ .order_by (Campaign .metadata_ ["crtime" ].desc ().nulls_last ())
52+ .offset (offset )
53+ .limit (limit )
54+ )
55+ campaigns = await session .exec (statement )
4956
5057 response .headers ["Next" ] = str (
5158 request .url_for ("read_campaign_collection" ).include_query_params (
@@ -95,6 +102,9 @@ async def read_campaign_resource(
95102 response .headers ["Edges" ] = str (
96103 request .url_for ("read_campaign_edge_collection" , campaign_name = campaign .id )
97104 )
105+ response .headers ["Manifests" ] = str (
106+ request .url_for ("read_campaign_manifest_collection" , campaign_name = campaign .id )
107+ )
98108 return campaign
99109 else :
100110 raise HTTPException (status_code = 404 )
@@ -196,22 +206,60 @@ async def read_campaign_node_collection(
196206
197207 # The input could be a campaign UUID or it could be a literal name.
198208 # TODO this could just as well be a campaign query with a join to nodes
199- s = select (Node )
209+ statement = select (Node ).order_by (Node .metadata_ ["crtime" ].asc ().nulls_last ())
210+
200211 try :
201212 if campaign_id := UUID (campaign_name ):
202- s = s .where (Node .namespace == campaign_id )
213+ statement = statement .where (Node .namespace == campaign_id )
203214 except ValueError :
204215 # FIXME get an id from a name
205216 raise HTTPException (status_code = 422 , detail = "campaign_name must be a uuid" )
206- s = s .offset (offset ).limit (limit )
207- nodes = await session .exec (s )
217+ statement = statement .offset (offset ).limit (limit )
218+ nodes = await session .exec (statement )
208219 response .headers ["Next" ] = str (
209220 request .url_for (
210221 "read_campaign_node_collection" ,
211- campaign_name = campaign_name ,
222+ campaign_name = campaign_id ,
212223 ).include_query_params (offset = (offset + limit ), limit = limit ),
213224 )
214- # TODO Previous
225+ response .headers ["Self" ] = str (request .url_for ("read_campaign_resource" , campaign_name = campaign_id ))
226+ return nodes .all ()
227+
228+
229+ @router .get (
230+ "/{campaign_name}/manifests" ,
231+ summary = "Get campaign Manifests" ,
232+ )
233+ async def read_campaign_manifest_collection (
234+ request : Request ,
235+ response : Response ,
236+ session : Annotated [AsyncSession , Depends (db_session_dependency )],
237+ campaign_name : str ,
238+ limit : Annotated [int , Query (le = 100 )] = 10 ,
239+ offset : Annotated [int , Query ()] = 0 ,
240+ ) -> Sequence [Manifest ]:
241+ """A paginated API returning a list of all Manifests in the namespace of a
242+ single Campaign.
243+ """
244+
245+ # The input could be a campaign UUID or it could be a literal name.
246+ statement = select (Manifest ).order_by (Manifest .metadata_ ["crtime" ].asc ().nulls_last ())
247+
248+ try :
249+ if campaign_id := UUID (campaign_name ):
250+ statement = statement .where (Manifest .namespace == campaign_id )
251+ except ValueError :
252+ # FIXME get an id from a name
253+ raise HTTPException (status_code = 422 , detail = "campaign_name must be a uuid" )
254+ statement = statement .offset (offset ).limit (limit )
255+ nodes = await session .exec (statement )
256+ response .headers ["Next" ] = str (
257+ request .url_for (
258+ "read_campaign_manifest_collection" ,
259+ campaign_name = campaign_id ,
260+ ).include_query_params (offset = (offset + limit ), limit = limit ),
261+ )
262+ response .headers ["Self" ] = str (request .url_for ("read_campaign_resource" , campaign_name = campaign_id ))
215263 return nodes .all ()
216264
217265
@@ -252,14 +300,16 @@ async def read_campaign_edge_collection(
252300 .join_from (Edge , target_nodes , Edge .target == target_nodes .id )
253301 )
254302 else :
255- s = select (Edge )
303+ s = select (Edge ). order_by ( col ( Edge . name ). asc (). nulls_last ())
256304 try :
257305 if campaign_id := UUID (campaign_name ):
258306 s = s .where (Edge .namespace == campaign_id )
259307 except ValueError :
260308 # FIXME get an id from a name
261309 raise HTTPException (status_code = 422 , detail = "campaign_name must be a uuid" )
262310 edges = await session .exec (s )
311+
312+ response .headers ["Self" ] = str (request .url_for ("read_campaign_resource" , campaign_name = campaign_id ))
263313 return edges .all ()
264314
265315
@@ -313,10 +363,12 @@ async def create_campaign_resource(
313363 # Create a campaign spec from the manifest, delegating the creation of new
314364 # dynamic fields to the model validation method, -OR- create new dynamic
315365 # fields here.
366+ campaign_metadata = manifest .metadata_ .model_dump ()
367+ campaign_metadata |= {"crtime" : element_time ()}
316368 campaign = Campaign .model_validate (
317369 dict (
318- name = manifest . metadata_ . name ,
319- metadata_ = manifest . metadata_ . model_dump () ,
370+ name = campaign_metadata . pop ( " name" ) ,
371+ metadata_ = campaign_metadata ,
320372 # owner = ... # TODO Get username from gafaelfawr # noqa: ERA001
321373 )
322374 )
@@ -376,12 +428,8 @@ async def read_campaign_graph(
376428 edges = (await session .exec (statement )).all ()
377429
378430 # Organize the edges into a graph. The graph nodes are annotated with their
379- # current database attributes.
380- # TODO it makes sense for the graph to include expunged Nodes in the meta-
381- # data for campaign processing, but for the purposes of this api route,
382- # only the most relevant information should be associated with each node,
383- # e.g., its name, status, id, and its URL
431+ # current database attributes according to the "simple" node view.
384432 graph = await graph_from_edge_list_v2 (edges = edges , node_type = Node , session = session , node_view = "simple" )
385433
386- response .headers ["Self" ] = ""
434+ response .headers ["Self" ] = str ( request . url_for ( "read_campaign_resource" , campaign_name = campaign_id ))
387435 return graph_to_dict (graph )
0 commit comments