aws-academy-grade-exporter (empty) → 0.1.0.0
raw patch · 8 files changed
+287/−0 lines, 8 filesdep +aesondep +basedep +bytestring
Dependencies added: aeson, base, bytestring, cassava, optparse-applicative, postgresql-simple, req, text, vector
Files
- CHANGELOG.md +5/−0
- LICENSE +20/−0
- aws-academy-grade-exporter.cabal +56/−0
- src/Exporter.hs +21/−0
- src/Main.hs +26/−0
- src/Options.hs +42/−0
- src/Pg.hs +57/−0
- src/Row.hs +60/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for aws-academy-grade-exporter++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2025 Alexander Goussas++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ aws-academy-grade-exporter.cabal view
@@ -0,0 +1,56 @@+cabal-version: 3.0+name: aws-academy-grade-exporter+version: 0.1.0.0+synopsis: Export grades from AWS Academy to different formats+description:+ A CLI tool for exporting grades from AWS Academy courses to various formats and sinks.++homepage: https://github.com/aloussase/aws-academy-grade-exporter-hs+license: MIT+license-file: LICENSE+author: Alexander Goussas+maintainer: goussasalexander@gmail.com++-- copyright:+category: Text+build-type: Simple+extra-doc-files: CHANGELOG.md++-- extra-source-files:++source-repository head+ type: git+ location:+ https://github.com/disel-espol/aws-academy-grades-exporter-hs++common warnings+ ghc-options: -Wall++executable aws-academy-grade-exporter+ import: warnings+ main-is: Main.hs+ other-modules:+ Exporter+ Options+ Pg+ Row++ -- other-extensions:+ build-depends:+ , aeson >=2.2.3 && <2.3+ , base ^>=4.17.2.1+ , bytestring >=0.11.5 && <0.12+ , cassava >=0.5.3 && <0.6+ , optparse-applicative >=0.18.1 && <0.19+ , postgresql-simple >=0.7.0 && <0.8+ , req >=3.13.4 && <3.14+ , text >=2.0.2 && <2.1+ , vector >=0.13.2 && <0.14++ hs-source-dirs: src+ default-language: Haskell2010+ default-extensions:+ DeriveAnyClass+ DeriveGeneric+ DerivingStrategies+ OverloadedStrings
+ src/Exporter.hs view
@@ -0,0 +1,21 @@+module Exporter where++import Control.Exception+import qualified Pg+import Row++data ExporterHandle = ExporterHandle+ { ehExport :: [Row] -> IO ()+ }+++stdoutExporter :: ExporterHandle+stdoutExporter = ExporterHandle print++postgresExporter :: ExporterHandle+postgresExporter = ExporterHandle $ \rows -> do+ bracket+ (Pg.connect)+ (Pg.disconnect)+ (\conn -> Pg.insertRows conn rows)+
+ src/Main.hs view
@@ -0,0 +1,26 @@+module Main where++import qualified Data.ByteString.Lazy.Char8 as BL8+import Data.Csv+import qualified Data.Vector as V+import Exporter+import qualified Options+import Row+import System.IO (readFile')++readRows :: FilePath -> IO [Row]+readRows fileName = do+ contents <- readFile' fileName+ case decode HasHeader $ BL8.pack contents of+ Left err -> error err+ Right v -> return $ V.toList v++doExport :: Options.Dest -> [Row] -> IO ()+doExport Options.Postgres rows = Exporter.ehExport postgresExporter $ rows+doExport Options.Stdout rows = Exporter.ehExport stdoutExporter $ rows++main :: IO ()+main = do+ opts <- Options.parseOpts+ rows <- readRows (Options.optFile opts)+ doExport (Options.optDest opts) rows
+ src/Options.hs view
@@ -0,0 +1,42 @@+module Options (parseOpts, Opt (..), Dest (..)) where++import Options.Applicative++data Dest = Postgres | Stdout+ deriving Show++data Opt = MkOpt+ { optDest :: Dest+ , optFile :: FilePath+ }+ deriving Show++opt :: Parser Opt+opt = MkOpt+ <$> option readDest+ ( long "destination"+ <> short 'd'+ <> help "Where to export the data"+ )+ <*> strOption+ ( long "file"+ <> short 'f'+ <> help "The file to be processed"+ )++ where+ readDest = eitherReader $ \s ->+ case s of+ "postgres" -> pure Postgres+ "stdout" -> pure Stdout+ _ -> Left "Invalid exporter"++parseOpts :: IO Opt+parseOpts = execParser opts++opts :: ParserInfo Opt+opts = info (opt <**> helper)+ ( fullDesc+ <> progDesc "Export grade from AWS Academy to various different formats"+ <> header "aws-academy-grade-exporter - a tool for exporting grades from AWS Academy to various formats"+ )
+ src/Pg.hs view
@@ -0,0 +1,57 @@+module Pg where++import Control.Monad (forM, void)+import Data.Aeson (FromJSON)+import qualified Data.ByteString.Char8 as BL8+import Data.List (isSuffixOf)+import qualified Data.Text as T+import Database.PostgreSQL.Simple+import GHC.Generics (Generic)+import Network.HTTP.Req+import Row+import System.Environment++connect :: IO Connection+connect = do+ connStr <- getEnv "DB_URL"+ conn <- connectPostgreSQL $ BL8.pack connStr+ return conn++data StudentData = MkStudentData+ { nombres :: !String+ , apellidos :: !String+ }+ deriving stock (Show, Generic)+ deriving anyclass (FromJSON)++insertRows :: Connection -> [Row] -> IO ()+insertRows conn rows = do+ void $ executeMany+ conn+ "insert into grades values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"+ rows++ students <- forM rows $ \row -> do+ -- TODO: Should do this in parallel.+ let studentEmail = getStudentEmail row++ runReq defaultHttpConfig $ do+ r <- req+ GET+ (https "wsarchivos.espol.edu.ec" /: "api" /: "consultas" /: "persona" /: (T.pack studentEmail))+ NoReqBody+ jsonResponse+ mempty++ return $ head $ map (\sd -> (studentEmail, nombres sd, apellidos sd)) (responseBody r :: [StudentData])++ void $ executeMany conn "insert into students values (?,?,?)" (students :: [(String, String, String)])++getStudentEmail :: Row -> String+getStudentEmail row+ | not (null $ rLoginID row) = rLoginID row+ | "@espol.edu.ec" `isSuffixOf` rStudent row = rStudent row+ | otherwise = rStudent row ++ "@espol.edu.ec"++disconnect :: Connection -> IO ()+disconnect conn = close conn
+ src/Row.hs view
@@ -0,0 +1,60 @@+module Row where+++import Data.Csv+import Database.PostgreSQL.Simple.ToRow+import GHC.Generics (Generic)++data Row = MkRow+ { rStudent :: !String+ , rId :: !Int+ , rLoginID :: !String+ , rSection :: !String+ , rModule1KC :: !(Maybe Double)+ , rModule2KC :: !(Maybe Double)+ , rModule3KC :: !(Maybe Double)+ , rModule4KC :: !(Maybe Double)+ , rModule5KC :: !(Maybe Double)+ , rModule6KC :: !(Maybe Double)+ , rModule7KC :: !(Maybe Double)+ , rModule8KC :: !(Maybe Double)+ , rModule9KC :: !(Maybe Double)+ , rModule10KC :: !(Maybe Double)+ , rCourseAssesment :: !(Maybe Double)+ , rLab1 :: !(Maybe Double)+ , rLab2 :: !(Maybe Double)+ , rLab3 :: !(Maybe Double)+ , rActivityAwsLambda :: !(Maybe Double)+ , rActivityElasticBeanstalk :: !(Maybe Double)+ , rLab4 :: !(Maybe Double)+ , rLab5 :: !(Maybe Double)+ , rLab6 :: !(Maybe Double)+ , rAssignmentsCurrentPoints :: !(Maybe Double)+ , rAssignmentsFinalPoints :: !(Maybe Double)+ , rAssignmentsCurrentScore :: !(Maybe Double)+ , rAssignmentsUnpostedCurrentScore :: !(Maybe Double)+ , rAssignmentsFinalScore :: !(Maybe Double)+ , rAssignmentsUnpostedFinalScore :: !(Maybe Double)+ , rKCCurrentPoints :: !(Maybe Double)+ , rKCFinalPoints :: !(Maybe Double)+ , rKCCurrentScore :: !(Maybe Double)+ , rKCUnpostedCurrentScore :: !(Maybe Double)+ , rKCFinalScore :: !(Maybe Double)+ , rKCUnpostedFinalScore :: !(Maybe Double)+ , rLabsCurrentPoints :: !(Maybe Double)+ , rLabsFinalPoints :: !(Maybe Double)+ , rLabsCurrentScore :: !(Maybe Double)+ , rLabsUnpostedCurrentScore :: !(Maybe Double)+ , rLabsFinalScore :: !(Maybe Double)+ , rLabsUnpostedFinalScore :: !(Maybe Double)+ , rCurrentPoints :: !(Maybe Double)+ , rFinalPoints :: !(Maybe Double)+ , rCurrentScore :: !(Maybe Double)+ , rUnpostedCurrentScore :: !(Maybe Double)+ , rFinalScore :: !(Maybe Double)+ , rUnpostedFinalScore :: !(Maybe Double)+ }+ deriving stock (Show, Generic)+ deriving anyclass (ToRow)++instance FromRecord Row