Feature/service api#3
Conversation
…ructured logging, health check, CORS
… Families; newtype guards
…inline invariant comments
- Add jose, websockets, wai-websockets dependencies to Surypus.cabal - Add websockets to extra-deps in stack.yaml - Remove Service submodules (placeholders) that don't compile yet - Keep Service.hs as placeholder for future implementation
- AccountingService: transaction processing, account balances, ledger generation - InventoryService: stock receipts, issues, transfers - PayrollService: salary calculation, payroll reports - ReportService: sales, inventory, financial reports - AuditService: audit logging All services have stub implementations with proper type signatures.
- Replace placeholder token generation with HMAC-SHA256 JWT - Use jose library for encode/decode operations - Add jose to stack.yaml extra-deps - Maintain backward compatible API with TokenPair, generateAccessToken, validateAccessToken, generateRefreshToken, validateRefreshToken
- Integrate JWT validation into WebSocket connections - Add path-based routing for /ws and /ws/:path endpoints - Token extraction from query parameters - Role-based message filtering with broadcastToRole - Store JWTPayload in connection context for authorization
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (40)
WalkthroughThis pull request introduces a comprehensive data access layer with Hasql-based queries, refactors the repository pattern with improved error handling, implements multiple services (accounting, audit, inventory, payroll, reporting), enhances JWT authentication, adds WebSocket authentication support, and expands the library's public API surface with numerous domain modules and database connectivity abstractions. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
- AccountingService: transaction processing, account balances, turnovers, ledger - InventoryService: stock receipts, issues, transfers with balance validation - PayrollService: salary calculation, NDFL, payroll reports - ReportService: sales, inventory, financial, payroll, tax reports - AuditService: audit logging with entity/user filtering All services use Hasql for type-safe PostgreSQL queries with proper encoders/decoders for Int64 cents/kopecks storage.
Review Summary by QodoService API Implementation with JWT Authentication, Repository Pattern Refactoring, and Comprehensive Service Layer
WalkthroughsDescription• **JWT Authentication & WebSocket Support**: Implemented proper JWT-based authentication using the jose library with HS256 algorithm, replacing simple string tokens. Added WebSocket support with role-based access control and message filtering through jwtWebSocketApp and runWebSocketServerWithAuth functions. • **Repository Pattern Refactoring**: Refactored the Repository typeclass to use monadic error handling with ExceptT, standardized CRUD operation signatures across all repository implementations (AccPlan, Bill, Currency, Goods, Location, Person, AccTurn, Order, Payment, Tax), and added dependency injection support through HasRepository typeclass. • **Service Layer Implementation**: Created comprehensive service modules for Accounting, Audit, Payroll, Inventory, and Report services with stub implementations accepting Pool for database operations. Added support for audit logging with role-based filtering and multiple report types. • **Type-Level Programming Utilities**: Introduced new TypeLevel.hs module with type families (EntityId, EntityFilter, EntitySort) and GADTs (DocumentType, Document) for type-safe entity and document operations. • **Database Query Layer**: Implemented comprehensive DAL with 60+ query functions using Hasql library, including row decoders for all business entities, CRUD operations, pagination with filtering/sorting, and specialized analytics queries. • **Domain Type Enhancements**: Added utility functions for type conversions (ppidToInt64, toMoney, fromMoney), introduced Flags32 newtype with bitwise operations, and added accessor functions for Pagination. • **Test Simplification**: Simplified domain tests (Bill, Goods, Person, Location) to focus on basic entity creation, removing complex property-based tests. Added new tests for newtype guard implementations. • **Configuration & Dependencies**: Extended connection pool configuration with tuning parameters, added new dependencies (mtl, scientific, jose, wai-websockets, websockets, warp, wai, http-types), and created Prometheus monitoring configuration. • **Bug Fixes**: Fixed Repository typeclass implementations in AccTurn, Order, Payment, and Tax repositories to return proper types (Int64 from create, Maybe entity from update/delete). • **Documentation**: Added comprehensive module documentation with usage examples to APIServer, Bill, Currency, Goods, Location, and Person repositories. Diagramflowchart LR
JWT["JWT Authentication<br/>jose library HS256"]
WS["WebSocket Support<br/>Role-based Access"]
Repo["Repository Pattern<br/>ExceptT Monadic"]
Services["Service Layer<br/>Accounting Audit Payroll"]
DAL["Database Query Layer<br/>60+ Hasql Functions"]
Types["Type-Level Utils<br/>Type Families GADTs"]
JWT --> WS
Repo --> Services
DAL --> Repo
Types --> Repo
Services --> DAL
File Changes1. src/Surypus/WebSocket.hs
|
Code Review by Qodo
1. undefined in Haddock example
|
| -- { scHost = "127.0.0.1" | ||
| -- , scPort = 8080 | ||
| -- , scPool = pool | ||
| -- , scWebSocketHub = undefined -- TODO: Initialize WebSocket hub |
There was a problem hiding this comment.
1. undefined in haddock example 📘 Rule violation ⛯ Reliability
undefined appears in newly added Haddock/example code, which violates the rule prohibiting undefined in committed code and can normalize unsafe patterns. Remove or rewrite the examples to avoid undefined placeholders.
Agent Prompt
## Issue description
`undefined` is present in newly added Haddock/example snippets, which violates the project rule disallowing `undefined` in committed code.
## Issue Context
These are documentation examples; they should demonstrate safe/complete patterns (e.g., using a named `hub` value, or omitting the field and explaining how to obtain it) rather than `undefined` placeholders.
## Fix Focus Areas
- src/APIServer.hs[14-26]
- src/DAL/Repository/AccPlan.hs[15-70]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| let p = Person | ||
| { personId = Nothing | ||
| , personCode = Just "001" | ||
| , personName = "Test Company" | ||
| , personFlags = 0 | ||
| , personINN = Just "1234567890" | ||
| , personKPP = Nothing | ||
| , personKind = 1 | ||
| , personStatus = 0 | ||
| , personPhone = Nothing | ||
| , personEmail = Nothing | ||
| , personAddress = Nothing | ||
| , personCredit = 0 | ||
| , personDiscount = 0 |
There was a problem hiding this comment.
2. Tests missing t.pack 📘 Rule violation ⚙ Maintainability
New test code uses raw string literals where Text is expected instead of T.pack, violating the testing standard. This can cause type ambiguity/mismatches and reduces consistency across tests.
Agent Prompt
## Issue description
Tests are using raw string literals for `Text` fields instead of `T.pack "..."`, violating the test text-handling standard.
## Issue Context
Update tests to explicitly pack string literals into `Text` by importing `qualified Data.Text as T` and replacing literals with `T.pack`.
## Fix Focus Areas
- test/Domain/PersonSpec.hs[1-25]
- test/Domain/GoodsSpec.hs[1-27]
- test/Domain/BillSpec.hs[1-27]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| import DAL.Mutations (createAccPlan, deleteAccPlan, updateAccPlan) | ||
| import DAL.Queries (getAccPlanById, getAccPlans) | ||
| import DAL.Repository | ||
| import DAL.Types | ||
| import Data.Int (Int64) | ||
| import Domain.Accounting (AccAccount (..)) | ||
| import Data.Text (Text) | ||
| import qualified Data.Text as T | ||
| import Hasql.Pool (Pool) |
There was a problem hiding this comment.
3. data.text import order wrong 📘 Rule violation ⚙ Maintainability
Imports in DAL.Repository.AccPlan do not follow the documented grouping/ordering (qualified imports should precede explicit imports, and groups should be ordered consistently). This reduces readability and violates the import convention rule.
Agent Prompt
## Issue description
`src/DAL/Repository/AccPlan.hs` imports are not ordered/grouped per the documented convention (qualified imports should come before explicit imports).
## Issue Context
Reorder the `Data.Text` imports (and, if needed, regroup local vs external imports) to match the project's standard.
## Fix Focus Areas
- src/DAL/Repository/AccPlan.hs[83-93]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| ["ws", subPath] -> | ||
| websocketsOr | ||
| WS.defaultConnectionOptions | ||
| (jwtWebSocketAppWithPath hub mConfig (T.intercalate "/" subPath)) | ||
| (\_ respond' -> respond' (responseLBS status400 [] "WebSocket endpoint expected")) |
There was a problem hiding this comment.
4. Websocket subpath type mismatch 🐞 Bug ✓ Correctness
In Surypus.WebSocket, the route case ["ws", subPath] binds subPath as a single Text, but the code passes it to T.intercalate "/" which expects [Text]. This prevents the module from compiling.
Agent Prompt
### Issue description
`T.intercalate "/" subPath` is applied to a `Text` path segment, causing a type error.
### Issue Context
The `Wai.pathInfo` branch `["ws", subPath]` binds `subPath :: Text`.
### Fix Focus Areas
- src/Surypus/WebSocket.hs[90-101]
### Suggested fix
Either:
- Change the match to capture multiple segments: `("ws" : subPathSegments)` and use `T.intercalate "/" subPathSegments`, or
- Keep `["ws", subPath]` and pass `subPath` directly without `intercalate`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| handleMessage :: WebSocketHub -> Int -> Text -> WS.DataMessage -> IO () | ||
| handleMessage hub clientId path msg = do | ||
| clients <- readTVarIO (wshClients hub) | ||
| let filteredClients = filter (\(_, _, mPayload) -> canReceive path mPayload) clients | ||
| forM_ filteredClients $ \(_, connection, _) -> | ||
| WS.sendTextData connection (WS.DataMessage msg) `catch` \(_ :: SomeException) -> pure () |
There was a problem hiding this comment.
5. Websocket relay won't compile 🐞 Bug ✓ Correctness
Surypus.WebSocket.handleMessage uses forM_ without importing it and attempts `WS.sendTextData connection (WS.DataMessage msg)`, which does not typecheck as written. This prevents compilation and breaks message relay.
Agent Prompt
### Issue description
`handleMessage` doesn't compile due to missing `forM_` import and incorrect use of `WS.sendTextData`.
### Issue Context
`msg` is already a `WS.DataMessage` from `receiveDataMessage`.
### Fix Focus Areas
- src/Surypus/WebSocket.hs[15-25]
- src/Surypus/WebSocket.hs[167-173]
### Suggested fix
- Import `forM_` (e.g., `import Data.Foldable (forM_)` or add it to the existing import list), and
- Forward the message using the correct WebSockets API (e.g., `WS.sendDataMessage connection msg`) or pattern-match `msg` and send appropriate text/binary payloads.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| module Surypus.JWT | ||
| ( JWTPayload (..), | ||
| JWTConfig (..), | ||
| TokenPair (..), | ||
| defaultJWTConfig, | ||
| generateTokenPair, | ||
| generateAccessToken, | ||
| validateAccessToken, | ||
| generateRefreshToken, | ||
| validateRefreshToken, | ||
| refreshAccessToken, | ||
| ) |
There was a problem hiding this comment.
6. Removed token api breaks build 🐞 Bug ✓ Correctness
Surypus.JWT no longer exports/defines generateSimpleToken, but APIServer still imports and calls it. This causes a compile-time failure.
Agent Prompt
### Issue description
`generateSimpleToken` is referenced by APIServer but is not exported/implemented by Surypus.JWT anymore.
### Issue Context
This is a hard compile error due to missing symbol.
### Fix Focus Areas
- src/Surypus/JWT.hs[4-15]
- src/APIServer.hs[60-72]
- src/APIServer.hs[313-323]
### Suggested fix
Either:
- Re-introduce `generateSimpleToken` in `Surypus.JWT` as a compatibility wrapper (and export it), or
- Update APIServer to use `generateAccessToken`/`generateTokenPair` and handle the `Either` error path (and update the import list accordingly).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| validateRefreshToken :: JWTConfig -> Text -> AppResult (Int, UTCTime) | ||
| validateRefreshToken config token = do | ||
| case T.splitOn ":" token of | ||
| [_uuid, uid, expStr] -> | ||
| case reads (T.unpack uid) of | ||
| [(uId, "")] -> | ||
| case reads (T.unpack expStr) of | ||
| [(exp, "")] -> Right (uId, exp) | ||
| _ -> Left (AuthError "Invalid expiration") | ||
| _ -> Left (AuthError "Invalid user id") | ||
| _ -> Left (AuthError "Invalid refresh token format") | ||
| let secret = TE.encodeUtf8 (jwtSecret config) | ||
| case jwtDecode secret token of | ||
| Right claims -> case sub claims of | ||
| Nothing -> Left (AuthError "Missing subject") | ||
| Just subClaim -> | ||
| case reads (T.unpack subClaim) of | ||
| [(uId, "")] -> | ||
| case exp claims of | ||
| Nothing -> Left (AuthError "Missing expiration") | ||
| Just expClaim -> Right (uId, read "1970-01-01 00:00:00 UTC" :: UTCTime) | ||
| _ -> Left (AuthError "Invalid subject format") |
There was a problem hiding this comment.
7. Refresh expiry discarded 🐞 Bug ⛨ Security
Surypus.JWT.validateRefreshToken ignores the parsed exp claim and always returns the Unix epoch as the expiration time. Any refresh-token expiry logic using this result will be incorrect.
Agent Prompt
### Issue description
`validateRefreshToken` discards `expClaim` and returns a constant UTCTime.
### Issue Context
The function signature promises to return the refresh token's expiration time.
### Fix Focus Areas
- src/Surypus/JWT.hs[141-154]
### Suggested fix
Convert the numeric `exp` claim to `UTCTime` (e.g., using `posixSecondsToUTCTime`) and return it. Also consider validating `iss` (e.g., require `iss == "surypus-refresh"`) so access tokens cannot be used as refresh tokens.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| case (jwtEncode (secret, HS256) accessClaims, jwtEncode (secret, HS256) refreshClaims) of | ||
| (Right accessToken, Right refreshToken) -> pure $ TokenPair accessToken refreshToken accessExpiration | ||
| (Left err, _) -> error (show err) | ||
| (_, Left err) -> error (show err) |
There was a problem hiding this comment.
8. Jwt encode uses error 🐞 Bug ⛯ Reliability
Surypus.JWT.generateTokenPair and refreshAccessToken call error when jwtEncode fails, crashing the server process. Token generation should surface failures as values (e.g., Either/AppResult) instead of terminating.
Agent Prompt
### Issue description
JWT encoding failures crash the entire process via `error`.
### Issue Context
Even if failures are rare, this turns a recoverable error into a hard outage.
### Fix Focus Areas
- src/Surypus/JWT.hs[94-105]
- src/Surypus/JWT.hs[156-164]
### Suggested fix
Change these functions to return `IO (Either Text ...)` (or `AppResult ...`) and propagate the encoding error to callers. Update callers to handle the Left case (HTTP 500 / auth error response as appropriate).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary by CodeRabbit
New Features
Documentation