@@ -8,7 +8,7 @@ defmodule SentinelCp.Services do
88
99 import Ecto.Query , warn: false
1010 alias SentinelCp.Repo
11- alias SentinelCp.Services . { Service , ServiceTemplate , ProjectConfig , UpstreamGroup , UpstreamTarget , Certificate , AuthPolicy }
11+ alias SentinelCp.Services . { Service , ServiceTemplate , ProjectConfig , UpstreamGroup , UpstreamTarget , Certificate , AuthPolicy , OpenApiSpec }
1212
1313 ## Services
1414
@@ -375,4 +375,120 @@ defmodule SentinelCp.Services do
375375 def delete_template ( % ServiceTemplate { } = template ) do
376376 Repo . delete ( template )
377377 end
378+
379+ ## OpenAPI Specs
380+
381+ @ doc """
382+ Lists OpenAPI specs for a project, most recent first.
383+ """
384+ def list_openapi_specs ( project_id ) do
385+ from ( s in OpenApiSpec ,
386+ where: s . project_id == ^ project_id ,
387+ order_by: [ desc: s . inserted_at ]
388+ )
389+ |> Repo . all ( )
390+ end
391+
392+ @ doc """
393+ Gets a single OpenAPI spec by ID.
394+ """
395+ def get_openapi_spec ( id ) , do: Repo . get ( OpenApiSpec , id )
396+
397+ @ doc """
398+ Gets a single OpenAPI spec by ID, raises if not found.
399+ """
400+ def get_openapi_spec! ( id ) , do: Repo . get! ( OpenApiSpec , id )
401+
402+ @ doc """
403+ Creates an OpenAPI spec record.
404+ """
405+ def create_openapi_spec ( attrs ) do
406+ % OpenApiSpec { }
407+ |> OpenApiSpec . changeset ( attrs )
408+ |> Repo . insert ( )
409+ end
410+
411+ @ doc """
412+ Deletes an OpenAPI spec.
413+ """
414+ def delete_openapi_spec ( % OpenApiSpec { } = spec ) do
415+ Repo . delete ( spec )
416+ end
417+
418+ @ doc """
419+ Finds an OpenAPI spec by checksum within a project, for dedup on re-upload.
420+ """
421+ def get_openapi_spec_by_checksum ( project_id , checksum ) do
422+ from ( s in OpenApiSpec ,
423+ where: s . project_id == ^ project_id and s . checksum == ^ checksum
424+ )
425+ |> Repo . one ( )
426+ end
427+
428+ @ doc """
429+ Imports services (and optionally auth policies) from an OpenAPI spec.
430+
431+ Runs in a transaction: creates auth policies first, then services with
432+ spec linkage and resolved auth_policy_ids.
433+ """
434+ def import_from_openapi ( project_id , spec_id , selected_services , opts \\ [ ] ) do
435+ import_auth = Keyword . get ( opts , :import_auth_policies , false )
436+ auth_policy_attrs = Keyword . get ( opts , :auth_policy_attrs , [ ] )
437+
438+ Repo . transaction ( fn ->
439+ # Create auth policies if requested
440+ policy_map =
441+ if import_auth do
442+ auth_policy_attrs
443+ |> Enum . reduce ( % { } , fn attrs , acc ->
444+ attrs_with_project = Map . put ( attrs , :project_id , project_id )
445+
446+ case create_auth_policy ( attrs_with_project ) do
447+ { :ok , policy } -> Map . put ( acc , attrs . name , policy . id )
448+ { :error , changeset } -> Repo . rollback ( { :auth_policy_error , changeset } )
449+ end
450+ end )
451+ else
452+ % { }
453+ end
454+
455+ # Create services
456+ services =
457+ Enum . map ( selected_services , fn svc_attrs ->
458+ auth_policy_id = resolve_auth_policy_id ( svc_attrs , policy_map )
459+
460+ attrs =
461+ % {
462+ name: svc_attrs . name ,
463+ route_path: svc_attrs . route_path ,
464+ upstream_url: svc_attrs . upstream_url ,
465+ description: svc_attrs [ :description ] ,
466+ openapi_spec_id: spec_id ,
467+ openapi_path: svc_attrs . openapi_path ,
468+ project_id: project_id
469+ }
470+ |> maybe_put ( :auth_policy_id , auth_policy_id )
471+
472+ case create_service ( attrs ) do
473+ { :ok , service } -> service
474+ { :error , changeset } -> Repo . rollback ( { :service_error , changeset } )
475+ end
476+ end )
477+
478+ % {
479+ services: services ,
480+ services_count: length ( services ) ,
481+ auth_policies_count: map_size ( policy_map )
482+ }
483+ end )
484+ end
485+
486+ defp resolve_auth_policy_id ( % { security_refs: refs } , policy_map ) when is_list ( refs ) do
487+ Enum . find_value ( refs , fn ref -> Map . get ( policy_map , ref ) end )
488+ end
489+
490+ defp resolve_auth_policy_id ( _ , _ ) , do: nil
491+
492+ defp maybe_put ( map , _key , nil ) , do: map
493+ defp maybe_put ( map , key , value ) , do: Map . put ( map , key , value )
378494end
0 commit comments