summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMateus Cruz <mateuscolvr@gmail.com>2024-02-05 03:31:35 -0300
committerMateus Cruz <mateuscolvr@gmail.com>2024-02-05 03:31:35 -0300
commit5a2f5bf9a12b270e15757df48afeb1c1db5b34b3 (patch)
tree2f1797364d78d935b4eb79269fd6b100fa77ec21
parentf0142aa1c3b4d7c2fd0b70c9e62c1ec033e445c2 (diff)
add db queries
-rw-r--r--bin/dune10
-rw-r--r--bin/main.ml14
-rw-r--r--lib/client.ml4
-rw-r--r--lib/dune4
-rw-r--r--lib/operation.ml49
-rw-r--r--lib/query.ml37
-rw-r--r--lib/router.ml46
-rw-r--r--lib/router.mli1
-rw-r--r--lib/utils.ml121
-rw-r--r--nginx.conf2
-rw-r--r--script.sql12
11 files changed, 266 insertions, 34 deletions
diff --git a/bin/dune b/bin/dune
index a186150..9c34236 100644
--- a/bin/dune
+++ b/bin/dune
@@ -5,17 +5,9 @@
(:standard -cclib -static -cclib -no-pie))
(libraries
rinha
-
piaf
routes
eio_main
- caqti-driver-postgresql
logs.fmt
fmt.tty
- logs.threaded
- caqti-eio
- caqti
- ppx_rapper_eio)
-
- (preprocess
- (pps ppx_rapper)))
+ logs.threaded))
diff --git a/bin/main.ml b/bin/main.ml
index 7e537a7..99cc0e0 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -4,9 +4,9 @@ open! StdLabels
open Eio
open Piaf
-let request_handler Server.{ request; _ } =
+let request_handler ~db_pool Server.{ request; _ } =
match Rinha.Router.match_route request.meth request.target with
- | Some handler -> handler request
+ | Some handler -> handler db_pool request
| None -> Response.create `Not_found
;;
@@ -17,10 +17,16 @@ let () =
@@ fun sw ->
let config =
let interface = Net.Ipaddr.V4.any in
- let port = 3000 in
+ let port = 8080 in
`Tcp (interface, port)
in
let config = Server.Config.create config in
- let server = Server.create ~config request_handler in
+ let db_uri =
+ Uri.make ~scheme:"postgres" ~userinfo:"admin:123" ~host:"db" ~path:"rinha" ()
+ in
+ let db_pool =
+ Result.get_ok @@ Caqti_eio.connect_pool ~sw ~stdenv:(env :> Caqti_eio.stdenv) db_uri
+ in
+ let server = Server.create ~config (request_handler ~db_pool) in
ignore @@ Server.Command.start ~sw env server
;;
diff --git a/lib/client.ml b/lib/client.ml
new file mode 100644
index 0000000..80a11b7
--- /dev/null
+++ b/lib/client.ml
@@ -0,0 +1,4 @@
+type t =
+ { id : int
+ ; mov_limit : int
+ }
diff --git a/lib/dune b/lib/dune
index 837c7b7..6c8a064 100644
--- a/lib/dune
+++ b/lib/dune
@@ -1,3 +1,5 @@
(library
(name rinha)
- (libraries piaf routes yojson))
+ (libraries piaf routes yojson caqti caqti-driver-postgresql caqti-eio ppx_rapper_eio)
+ (preprocess
+ (pps ppx_rapper)))
diff --git a/lib/operation.ml b/lib/operation.ml
new file mode 100644
index 0000000..1fb1dd5
--- /dev/null
+++ b/lib/operation.ml
@@ -0,0 +1,49 @@
+module TransactionType = struct
+ type t =
+ | Credit
+ | Debit
+
+ let t =
+ let encode = function
+ | Credit -> "credit"
+ | Debit -> "debit"
+ in
+ let decode = function
+ | "credit" -> Ok Credit
+ | "debit" -> Ok Debit
+ | _ -> Error "Invalid transaction type"
+ in
+ Caqti_type.(enum ~encode ~decode "transaction_type")
+ ;;
+end
+
+type t =
+ | Transaction of
+ { transaction_type : TransactionType.t
+ ; value : int
+ ; description : string
+ }
+ | Balance of { client_id : int }
+
+type transaction =
+ { id : int
+ ; client_id : int
+ ; value : int
+ ; transaction_type : TransactionType.t
+ ; description : string
+ ; created_at : Ptime.t
+ }
+
+let decoder =
+ let open Utils.Decoder in
+ let open Syntax in
+ let transaction_type_decoder =
+ literal "c" *> return TransactionType.Credit
+ <|> literal "d" *> return TransactionType.Debit
+ in
+ (fun value transaction_type description ->
+ Transaction { value; description; transaction_type })
+ <$> ("valor" <: int)
+ <*> ("tipo" <: transaction_type_decoder)
+ <*> ("descricao" <: string)
+;;
diff --git a/lib/query.ml b/lib/query.ml
new file mode 100644
index 0000000..0fb6f7e
--- /dev/null
+++ b/lib/query.ml
@@ -0,0 +1,37 @@
+type pool = ((module Rapper_helper.CONNECTION), Caqti_error.t) Caqti_eio.Pool.t
+
+let transaction_query =
+ [%rapper
+ execute
+ {sql|
+ INSERT INTO transactions (client_id, value, type, description)
+ VALUES (%int{client_id}, %int{value}, %Operation.TransactionType{transaction_type}, %string{description})
+ |sql}]
+;;
+
+let client_query =
+ let open Client in
+ [%rapper
+ get_opt
+ {sql|
+ SELECT @int{id}, @int{mov_limit} FROM clients WHERE id = %int{id}
+ |sql}
+ record_out]
+;;
+
+let execute_operation ~client_id ~(op : Operation.t) pool =
+ match op with
+ | Transaction data ->
+ Caqti_eio.Pool.use
+ (fun conn ->
+ transaction_query
+ ~client_id
+ ~value:data.value
+ ~transaction_type:data.transaction_type
+ ~description:data.description
+ conn)
+ pool
+ | _ -> failwith "TODO"
+;;
+
+let find_client id pool = Caqti_eio.Pool.use (fun conn -> client_query ~id conn) pool
diff --git a/lib/router.ml b/lib/router.ml
index cbff9fb..35cef34 100644
--- a/lib/router.ml
+++ b/lib/router.ml
@@ -14,16 +14,35 @@ module R = Map.Make (struct
;;
end)
-let transactions_route =
- let handler client_id (request : Request.t) =
- let json : Yojson.Safe.t = `Assoc [ "limite", `Int 100000; "saldo", `Int (-9098) ] in
- Response.of_string ~body:(Yojson.Safe.to_string json) `OK
- in
- (s "clientes" / int / s "transacoes" /? nil) @--> handler
-;;
+module Handler = struct
+ let transaction client_id (db_pool : Query.pool) (request : Request.t) =
+ let client_opt =
+ Option.join @@ Result.to_option @@ Query.find_client client_id db_pool
+ in
+ match client_opt with
+ | Some client ->
+ let insert_result =
+ let body = Result.to_option @@ Body.to_string request.body in
+ let json = Option.map Yojson.Safe.from_string body in
+ let decoded_op = Option.bind json (Utils.Decoder.decode Operation.decoder) in
+ match decoded_op with
+ | Some op ->
+ (match Query.execute_operation ~client_id ~op db_pool with
+ | Ok _ as ok -> ok
+ | Error e -> Error (`DB e))
+ | None -> Error (`Decoder "Invalid operation")
+ in
+ (match insert_result with
+ | Ok () ->
+ let json : Yojson.Safe.t =
+ `Assoc [ "limite", `Int 100000; "saldo", `Int (-9098) ]
+ in
+ Response.of_string ~body:(Yojson.Safe.to_string json) `OK
+ | Error _ -> Response.create (`Code 422))
+ | None -> Response.create `Not_found
+ ;;
-let balance_route =
- let handler client_id (request : Request.t) =
+ let balance client_id (db_pool : Query.pool) (request : Request.t) =
let json : Yojson.Safe.t =
let balance =
let total = `Int (-9098) in
@@ -35,15 +54,16 @@ let balance_route =
`Assoc [ "saldo", balance; "ultimas_transacoes", last_transactions ]
in
Response.of_string ~body:(Yojson.Safe.to_string json) `OK
- in
- (s "clientes" / int / s "extrato" /? nil) @--> handler
-;;
+ ;;
+end
let routes =
List.fold_left
~f:(fun acc (v, r) -> R.add_to_list v r acc)
- [ `GET, balance_route; `POST, transactions_route; `POST, balance_route ]
~init:R.empty
+ [ `GET, (s "clientes" / int / s "extrato" /? nil) @--> Handler.balance
+ ; `POST, (s "clientes" / int / s "transacoes" /? nil) @--> Handler.transaction
+ ]
;;
let router = R.map one_of routes
diff --git a/lib/router.mli b/lib/router.mli
deleted file mode 100644
index c0b5915..0000000
--- a/lib/router.mli
+++ /dev/null
@@ -1 +0,0 @@
-val match_route : Piaf.Method.t -> string -> (Piaf.Request.t -> Piaf.Response.t) option
diff --git a/lib/utils.ml b/lib/utils.ml
new file mode 100644
index 0000000..00e003a
--- /dev/null
+++ b/lib/utils.ml
@@ -0,0 +1,121 @@
+module Decoder : sig
+ type 'a decoder
+
+ val decode : 'a decoder -> Yojson.Safe.t -> 'a option
+ val return : 'a -> 'a decoder
+ val fail : 'a decoder
+ val bind : 'a decoder -> ('a -> 'b decoder) -> 'b decoder
+ val map : ('a -> 'b) -> 'a decoder -> 'b decoder
+ val string : string decoder
+ val literal : string -> string decoder
+ val int : int decoder
+ val field : string -> 'a decoder -> 'a decoder
+ val list : 'a decoder -> 'a list decoder
+ val one_of : 'a decoder list -> 'a decoder
+
+ module Syntax : sig
+ val ( *> ) : 'a decoder -> 'b decoder -> 'b decoder
+ val ( <* ) : 'a decoder -> 'b decoder -> 'a decoder
+ val ( >>= ) : 'a decoder -> ('a -> 'b decoder) -> 'b decoder
+ val ( >>| ) : 'a decoder -> ('a -> 'b) -> 'b decoder
+ val ( <*> ) : ('a -> 'b) decoder -> 'a decoder -> 'b decoder
+ val ( <$> ) : ('a -> 'b) -> 'a decoder -> 'b decoder
+ val ( <: ) : string -> 'a decoder -> 'a decoder
+ val ( <|> ) : 'a decoder -> 'a decoder -> 'a decoder
+ end
+end = struct
+ type 'a decoder = Yojson.Safe.t -> 'a option
+
+ let decode decoder json = decoder json
+ let return v _ = Some v
+ let fail _ = None
+
+ let map f decoder json =
+ match decoder json with
+ | Some d -> Some (f d)
+ | None -> None
+ ;;
+
+ let bind decoder f json =
+ match decoder json with
+ | Some x -> f x `Null
+ | None -> None
+ ;;
+
+ let rec one_of decoders json =
+ match decoders with
+ | [] -> None
+ | decoder :: tl ->
+ (match decoder json with
+ | Some _ as x -> x
+ | None -> one_of tl json)
+ ;;
+
+ let string = function
+ | `String str -> Some str
+ | _ -> None
+ ;;
+
+ let literal str = function
+ | `String s when String.equal s str -> Some s
+ | _ -> None
+ ;;
+
+ let int = function
+ | `Int int -> Some int
+ | _ -> None
+ ;;
+
+ let field key decoder json =
+ try decoder @@ Yojson.Safe.Util.member key json with
+ | _ -> None
+ ;;
+
+ let list decoder json =
+ let rec helper acc = function
+ | [] -> Some []
+ | hd :: tl ->
+ (match decoder hd with
+ | Some _ as x -> helper (x :: acc) tl
+ | None -> None)
+ in
+ match json with
+ | `List list -> Option.map List.rev (helper [] list)
+ | _ -> None
+ ;;
+
+ module Syntax = struct
+ let ( *> ) decoder_a decoder_b yojson =
+ match decoder_a yojson with
+ | Some _ -> decoder_b yojson
+ | None -> None
+ ;;
+
+ let ( <* ) decoder_a decoder_b yojson =
+ match decoder_a yojson with
+ | Some value -> Option.map (fun _ -> value) (decoder_b yojson)
+ | None -> None
+ ;;
+
+ let ( >>= ) = bind
+ let ( >>| ) decoder f yojson = map f decoder yojson
+
+ let ( <*> ) decoder_a decoder_b input =
+ match decoder_a input with
+ | Some f ->
+ (match decoder_b input with
+ | Some x -> Some (f x)
+ | None -> None)
+ | None -> None
+ ;;
+
+ let ( <$> ) f decoder = decoder >>| f
+ let ( <: ) = field
+
+ let ( <|> ) decoder_a decoder_b input =
+ match decoder_a input with
+ | Some _ as value -> value
+ | None -> decoder_b input
+ ;;
+ end
+end
diff --git a/nginx.conf b/nginx.conf
index 60b677f..fc8933d 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -12,7 +12,7 @@ http {
}
server {
- listen 9999; # Lembra da porta 9999 obrigatória?
+ listen 9999;
location / {
proxy_pass http://api;
diff --git a/script.sql b/script.sql
index f5da315..25e5ffe 100644
--- a/script.sql
+++ b/script.sql
@@ -1,14 +1,14 @@
CREATE TABLE clients (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL,
- limit INTEGER NOT NULL
+ mov_limit INTEGER NOT NULL
);
-CREATE TYPE transaction_type AS ENUM ('c', 'd');
+CREATE TYPE transaction_type AS ENUM ('credit', 'debit');
CREATE TABLE transactions (
id SERIAL PRIMARY KEY,
- client_id REFERENCES clients,
+ client_id INTEGER REFERENCES clients,
value INTEGER NOT NULL,
type transaction_type NOT NULL,
description VARCHAR(10) NOT NULL,
@@ -17,12 +17,13 @@ CREATE TABLE transactions (
CREATE TABLE balances (
id SERIAL PRIMARY KEY,
- client_id REFERENCES clients,
+ client_id INTEGER REFERENCES clients,
value INTEGER NOT NULL
);
+DO $$
BEGIN
- INSERT INTO clients (name, limit)
+ INSERT INTO clients (name, mov_limit)
VALUES
('naruto', 1000 * 100),
('mob', 800 * 100),
@@ -31,3 +32,4 @@ BEGIN
INSERT INTO balances (client_id, value)
SELECT id, 0 FROM clients;
END;
+$$