4import Prelude hiding ((/))
6import Auth.Handler (authHandler, readAllowedTokens)
7import Auth.Types (Token(..))
8import Control.Monad.Cont (ContT(..), callCC, lift, runContT)
9import Control.Parallel (parOneOf, parTraverse)
10import Data.Argonaut (jsonParser)
11import Data.Argonaut.Core (Json)
12import Data.Argonaut.Core as Json
13import Data.Argonaut.Core as Json
14import Data.Argonaut.Decode (class DecodeJson, decodeJson, printJsonDecodeError)
15import Data.Argonaut.Decode.Class (class DecodeJson)
16import Data.Argonaut.Decode.Decoders (decodeString)
17import Data.Argonaut.Encode (encodeJson)
18import Data.Array (elem, (..))
19import Data.Bifunctor (lmap)
20import Data.Either (Either(..))
21import Data.Either (hush)
22import Data.Int (fromString)
23import Data.Maybe (Maybe(..), fromMaybe)
24import Data.Newtype (un)
25import Data.Time.Duration (class Duration, Seconds(..), fromDuration)
26import Data.Traversable (traverse)
29import Effect.AVar (AVar)
30import Effect.Aff (Aff, attempt, delay, error, launchAff_, message)
31import Effect.Aff.AVar as AVar
32import Effect.Aff.Class (liftAff)
33import Effect.Class (class MonadEffect, liftEffect)
34import Effect.Class.Console (info, log)
35import Effect.Exception (Error)
36import Foreign (unsafeToForeign)
37import HTTPurple.Json.Yoga (jsonDecoder, jsonEncoder) as YogaJson
38import JobQueue (EnqueueResult(..), Job, NewJob(..), Queue, ResourcePool(..))
40import Middleware.JsonBodyParser (jsonBodyParser)
41import Node.Buffer (Buffer)
42import Node.Buffer as Buffer
43import Node.ChildProcess (ExecResult)
44import Node.Encoding (Encoding(..))
45import Node.Express.App (App, listenHttp, listenHttps, use, useExternal)
46import Node.Express.App as E
47import Node.Express.Handler (Handler, HandlerM(..), next)
48import Node.Express.Middleware.Static (static)
49import Node.Express.Request (getBody', getRequestHeader)
50import Node.Express.Response (end, setStatus)
51import Node.Express.Response as Response
52import Node.FS.Aff (readTextFile)
53import Node.OS (numCpus)
54import Node.Process (lookupEnv)
55import Playground.Playground (Folder(..), copy)
56import PscIdeClient (PscIdeConnection, compileCode, getFolder, mkConnection, execCommand)
57import Shared.Json (readAff, readJsonAff)
58import Shared.Models.Body (CompileRequest, RunResult, CompileResult)
59import Shared.Models.Body as Body
60import Unsafe.Coerce (unsafeCoerce)
62toBody ∷ ∀ r m. MonadEffect m => { stdout ∷ Buffer, stderr ∷ Buffer | r } -> m RunResult
66 stdout <- Buffer.toString UTF8 result.stdout
67 stderr <- Buffer.toString UTF8 result.stderr
68 let (code ∷ Maybe Int) = asErrorWithCode result >>= _.code
69 in { code, stdout, stderr } ∷ RunResult
74asErrorWithCode ∷ ∀ a. a -> Maybe ErrorWithCode
75asErrorWithCode = unsafeCoerce >>> decodeJson >>> hush
77runCode ∷ ∀ d. Duration d => d -> Folder -> Aff (Maybe ExecResult)
78runCode timeout folder =
80 [ Just <$> execCommand folder "exec node run.mjs"
81 , Nothing <$ delay (timeout # fromDuration)
86srcFolder = "../playground"
89destFolder = "../playgrounds/"
94 tokens <- readAllowedTokens
95 cpus <- numCpus # liftEffect
97 -- poolSize = max 2 (cpus / 2) -- use at least 2
99 mkFolder = Folder <<< (destFolder <> _) <<< show
102 # parTraverse \n -> do
106 log $ "Copying to folder " <> (un Folder folder)
107 copy srcFolder (un Folder folder)
108 mkConnection folder port
109 let pool = ResourcePool connections
113 , timeout: 10.0 # Seconds # fromDuration
116 void $ liftEffect (serverSetup q)
118-- makeApp ∷ Array Token -> Queue PscIdeConnection -> App
119-- makeApp tokens q = do
120-- use $ static "assets"
121-- useExternal jsonBodyParser
122-- use (authHandler tokens)
123-- E.post "/compileAndRun" (compileAndRunHandler q)
125-- serverSetup ∷ App -> Aff Server
126-- serverSetup app = do
127-- maybePortString <- lookupEnv "PORT" # liftEffect
128-- let port = maybePortString >>= fromString # fromMaybe 14188
129-- liftEffect $ (listenHttp app port) \_ -> info $ "psfp server started on port " <> show port
131-- makeHttpsOptions = do
132-- key <- readTextFile UTF8 "server.key"
133-- cert <- readTextFile UTF8 "server.cert"
136data Route = CompileAndRun
137derive instance Generic Route _
139route :: RouteDuplex' Route
141 { "CompileAndRun": "compileAndRun" / noArgs
144serverSetup :: Queue _ -> Effect Unit
145serverSetup queue = do
146 maybePortString <- lookupEnv "PORT" # liftEffect
147 let port = maybePortString >>= fromString # fromMaybe 14188
148 --liftEffect $ (listenHttp app port) \_ ->
149 info $ "psfp server started on port " <> show port
151 void $ serve { port } { route, router }
153 router { route: CompileAndRun, method: Post, body } = usingCont do
154 { code } :: { code :: String } <- fromJson YogaJson.jsonDecoder body -- parse the json input
155 -- jobId <- compileAndRunJob code
156 compileAndRunJobCont queue { code } -- do your business logic
157 -- whatever queue body
160 makeHttpsOptions = do
161 key <- readTextFile UTF8 "server.key"
162 cert <- readTextFile UTF8 "server.cert"
165compileAndRunJobCont :: Queue _ -> CompileRequest -> ContT Response Aff Response
166compileAndRunJobCont queue { code } = do
167 resultVar <- lift AVar.empty
170 job = NewJob \jobId conn -> do
171 stringOrErr ∷ Either Error String <- attempt $ compileCode code conn
172 jsonOrErr ∷ Either Error (Either Error CompileResult) <- attempt $ readJsonAff `traverse` stringOrErr
173 let folder = getFolder conn
174 AVar.put { jsonOrErr, stringOrErr, folder } resultVar
175 enqueueResult <- lift $ liftEffect $ Q.enqueue job queue
176 case enqueueResult of
177 QueueFull -> notFound
179 { jsonOrErr, stringOrErr, folder } <- AVar.take resultVar # lift
180 case jsonOrErr ∷ Either Error (Either Error CompileResult) of
182 lift $ log $ "Aff failed with " <> message e
183 internalServerError' jsonHeaders $ toJson YogaJson.jsonEncoder { error: show e }
185 | res.resultType == "error" -> do
187 Right (Right res) -> do
188 runResult <- runCode timeout folder # lift
191 requestTimeout' jsonHeaders -- $ toJson YogaJson.jsonEncoder { error: "Timed out after running for " <> show timeout }
193 resultBody <- toBody rr # lift
194 ok' jsonHeaders $ toJson YogaJson.jsonEncoder{ resultBody }
195 Right (Left errs) -> do
196 log $ "Could not decode: " <> show (lmap (const "no way") stringOrErr) <> "\nErrors: " <> show errs
197 internalServerError' jsonHeaders $ toJson YogaJson.jsonEncoder { error: show errs }
199 -- Left err -> internalServerError' jsonHeaders $ toJson YogaJson.jsonEncoder { error: show err }
200 -- Right result -> ok' jsonHeaders $ toJson YogaJson.jsonEncoder result
204 timeout = 1.0 # Seconds
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list