Commit 502228f

mo khan <mo@mokhan.ca>
2025-03-05 18:10:34
feat: add a REST API service
1 parent 98512fe
bin/idp
@@ -320,5 +320,5 @@ if __FILE__ == $0
     run IdentityProvider.new
   end.to_app
 
-  Rackup::Server.start(app: app, Port: 8282)
+  Rackup::Server.start(app: app, Port: ENV.fetch('PORT', 8282).to_i)
 end
bin/rest-api
@@ -0,0 +1,106 @@
+#!/usr/bin/env ruby
+
+require 'bundler/inline'
+
+gemfile do
+  source 'https://rubygems.org'
+
+  gem "erb", "~> 4.0"
+  gem "json", "~> 2.0"
+  gem "rack", "~> 3.0"
+  gem "rackup", "~> 2.0"
+  gem "securerandom", "~> 0.1"
+  gem "webrick", "~> 1.0"
+end
+
+class Project
+  class << self
+    def all
+      @projects ||= []
+    end
+
+    def create!(attributes)
+      new({ id: SecureRandom.uuid }.merge(attributes)).tap do |item|
+        all << item
+      end
+    end
+  end
+
+  def initialize(attributes = {})
+    @attributes = attributes
+  end
+
+  def to_h
+    @attributes
+  end
+end
+
+class RESTAPI
+  def initialize
+    @storage = {}
+  end
+
+  def call(env)
+    request = Rack::Request.new(env)
+    path = env['PATH_INFO']
+    case env['REQUEST_METHOD']
+    when 'GET'
+      case path
+      when '/projects.json'
+        return json_ok(Project.all.map(&:to_h))
+      else
+        return json_not_found
+      end
+    when 'POST'
+      case path
+      when "/projects"
+        if authorized?(request, :create_project)
+          return json_created(Project.create!(JSON.parse(request.body.read, symbolize_names: true)))
+        else
+          return json_unauthorized(:create_project)
+        end
+      else
+        return json_not_found
+      end
+    end
+    json_not_found
+  end
+
+  private
+
+  def authorized?(request, permission)
+    # TODO:: Check the JWT for the appropriate claim
+    # Connect to the Authz RPC endpoint Ability.allowed?(subject, permission, resource)
+    true
+  end
+
+  def json_not_found
+    [404, { 'X-Backend-Server' => 'REST', 'Content-Type' => 'application/json' }, []]
+  end
+
+  def json_ok(body)
+    [200, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate(body)]]
+  end
+
+  def json_created(body)
+    [201, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate(body.to_h)]]
+  end
+
+  def json_unauthorized(permission)
+    [401, { 'Content-Type' => 'application/json' }, [JSON.pretty_generate({
+      error: {
+        code: 401,
+        message: "`#{permission}` is required",
+      }
+    })]]
+  end
+end
+
+if __FILE__ == $0
+  app = Rack::Builder.new do
+    use Rack::Reloader
+    run RESTAPI.new
+  end.to_app
+
+  Rackup::Server.start(app: app, Port: ENV.fetch('PORT', 8284).to_i)
+end
bin/sp
@@ -159,5 +159,5 @@ if __FILE__ == $0
     run ServiceProvider.new
   end.to_app
 
-  Rackup::Server.start(app: app, Port: 8283)
+  Rackup::Server.start(app: app, Port: ENV.fetch('PORT', 8283).to_i)
 end
magefile.go
@@ -30,6 +30,11 @@ func RunGateway() error {
 	return sh.RunV("go", "run", "./cmd/gtwy/main.go")
 }
 
+// Run the REST API
+func RunApi() error {
+	return sh.RunV("ruby", "./bin/rest-api")
+}
+
 // Open a web browser to the login page
 func Browser() error {
 	if runtime.GOOS == "linux" {
@@ -41,5 +46,5 @@ func Browser() error {
 
 // Run All the servers
 func Run(ctx context.Context) {
-	mg.CtxDeps(ctx, RunIdp, RunSp, RunGateway, Browser)
+	mg.CtxDeps(ctx, RunIdp, RunSp, RunApi, RunGateway)
 }