Current section

Files

Jump to
fireball src fireball.gleam
Raw

src/fireball.gleam

//gleam
import gleam/bit_array
import gleam/dynamic
import gleam/http
import gleam/http/request
import gleam/httpc
import gleam/int
import gleam/io
import gleam/json
import gleam/string
//gleamyshell
import gleamyshell.{CommandOutput}
//simplifile
import simplifile
//zero/decode
import decode/zero as decode
///Firestore collection type
pub type Collection {
Collection(location: String, err: String)
}
//Custom error type
pub type FireballError {
//A generic error - will be expanded upon later
FireballError(err: String)
}
///Firestore document type
pub type FireballDocument {
// "data" is the fields section of a document
FireballDocument(data: String)
}
///
pub type FireballStorage {
FireballStorage(path: String)
}
///A type representing the JSON returning when uploading an object to Google Firebase Storage
pub type UploadData {
UploadData(
name: String,
bucket: String,
generation: String,
metageneration: String,
content_type: String,
time_created: String,
updated: String,
storage_class: String,
size: String,
md5_hash: String,
content_encoding: String,
content_disposition: String,
crc32c: String,
etag: String,
download_tokens: String,
)
}
///RTDB wrapper error
pub type RTDBError {
Generic(err: String)
}
///Decode an RTDB error
pub fn decode_error(input input: String) -> Result(RTDBError, json.DecodeError) {
json.decode(
input,
dynamic.decode1(Generic, dynamic.field("error", of: dynamic.string)),
)
}
///put_file function to create a node in Google's Realtime Database
pub fn put_data(
apikey apikey: String,
input input: String,
dbpath dbpath: String,
dburl dburl: String,
) -> Result(String, RTDBError) {
//Init url
let url =
//Firestore RTDB url
dburl
//DB Path
<> dbpath
<> ".json?auth="
//Auth secret
<> apikey
//Init a request object
case request.to(url) {
//
Ok(base_req) -> {
//Add headers
let req =
//Add headers
request.prepend_header(base_req, "Content-type", "application/json")
//Set method
|> request.set_method(http.Put)
//Set body
|> request.set_body(input)
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(resp) -> Ok(resp.body)
//Handle errors
Error(err) -> {
io.debug(err)
Error(Generic(err: "error while sending request"))
}
}
}
//Handle errors
Error(err) -> {
io.debug(err)
Error(Generic(err: "error while creating request"))
}
}
}
///put_file function to update fields in a node in Google's Realtime Database
pub fn patch_data(
apikey apikey: String,
input input: String,
dbpath dbpath: String,
dburl dburl: String,
) -> Result(String, RTDBError) {
//Init url
let url =
//Firestore RTDB url
dburl
//DB Path
<> dbpath
<> ".json?auth="
//Auth secret
<> apikey
//Init a request object
case request.to(url) {
//
Ok(base_req) -> {
//Add headers
let req =
//Add headers
request.prepend_header(base_req, "Content-type", "application/json")
//Set method
|> request.set_method(http.Patch)
//Set body
|> request.set_body(input)
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(resp) -> Ok(resp.body)
//Handle errors
Error(err) -> {
io.debug(err)
Error(Generic(err: "error while sending request"))
}
}
}
//Handle errors
Error(err) -> {
io.debug(err)
Error(Generic(err: "error while creating request"))
}
}
}
///get_data function to get data from a node in Google's Realtime Database
pub fn get_data(
apikey apikey: String,
dbpath dbpath: String,
dburl dburl: String,
) -> Result(String, RTDBError) {
//Init url
let url =
//Firestore RTDB url
dburl
//DB Path
<> dbpath
<> ".json?auth="
//Auth secret
<> apikey
//Init a request object
case request.to(url) {
//
Ok(base_req) -> {
//Add headers
let req =
//Add headers
request.prepend_header(base_req, "Content-type", "application/json")
//Set method
|> request.set_method(http.Get)
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(resp) -> Ok(resp.body)
//Handle errors
Error(err) -> {
io.debug(err)
Error(Generic(err: "error while sending request"))
}
}
}
//Handle errors
Error(err) -> {
io.debug(err)
Error(Generic(err: "error while creating request"))
}
}
}
///get_doc function to retrieve a document from Google's Firestore Database
pub fn get_doc(
apikey apikey: String,
apiver apiver: String,
database database: String,
doc doc: String,
proj_id proj_id: String,
) -> Result(FireballDocument, FireballError) {
//Init a request object
case
request.to(
//Firestore url
"https://firestore.googleapis.com/"
//Firestore Web API version
<> apiver
<> "/projects/"
//Project id
<> proj_id
<> "/databases/"
//Database to use
<> database
<> "/documents/"
//Document path
<> doc
<> "?key="
//API Key for the google cloud project
<> apikey,
)
{
//Check output
Ok(base_req) -> {
//Add headers
let req =
//Add headers
request.prepend_header(base_req, "Content-type", "application/json")
//Set method
|> request.set_method(http.Get)
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(resp) -> Ok(FireballDocument(data: resp.body))
//Handle errors
Error(_) -> Error(FireballError(err: "error while sending request"))
}
}
//Handle errors
Error(_) -> Error(FireballError(err: "error while forming request"))
}
}
///put_file function to create a document in Google's Firestore Database
pub fn post_doc(
apikey apikey: String,
apiver apiver: String,
database database: String,
collection_path collection_path: String,
doc_path doc_path: String,
input_doc input_doc: FireballDocument,
proj_id proj_id: String,
) -> Result(FireballDocument, FireballError) {
//Init url
let url =
//Firestore url
"https://firestore.googleapis.com/"
//Firestore Web API version
<> apiver
<> "/projects/"
//Project id
<> proj_id
<> "/databases/"
//Database to use
<> database
<> "/documents/"
//Collection path
<> collection_path
<> "?documentId="
//Doc path
<> doc_path
//API Key for the google cloud project
<> "&key="
<> apikey
//Init a request object
case request.to(url) {
//
Ok(base_req) -> {
//Add headers
let req =
//Add headers
request.prepend_header(base_req, "Content-type", "application/json")
//Set method
|> request.set_method(http.Post)
|> request.set_body(doc_to_json(input_doc))
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(resp) -> Ok(FireballDocument(data: resp.body))
//Handle errors
Error(_) -> Error(FireballError(err: "error while sending request"))
}
}
//Handle errors
Error(_) -> Error(FireballError(err: "error while creating request"))
}
}
///put_file function to create a document in Google's Firestore Database
pub fn post_doc_from_string(
apikey apikey: String,
apiver apiver: String,
database database: String,
collection_path collection_path: String,
doc_path doc_path: String,
input_data input_data: String,
proj_id proj_id: String,
) -> Result(FireballDocument, FireballError) {
//Init url
let url =
//Firestore url
"https://firestore.googleapis.com/"
//Firestore Web API version
<> apiver
<> "/projects/"
//Project id
<> proj_id
<> "/databases/"
//Database to use
<> database
<> "/documents/"
//Collection path
<> collection_path
<> "?documentId="
//Doc path
<> doc_path
//API Key for the google cloud project
<> "&key="
<> apikey
//Init a request object
case request.to(url) {
//
Ok(base_req) -> {
//Add headers
let req =
//Add headers
request.prepend_header(base_req, "Content-type", "application/json")
//Set method
|> request.set_method(http.Post)
//Set body
|> request.set_body(input_data)
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(resp) -> Ok(FireballDocument(data: resp.body))
//Handle errors
Error(_) -> Error(FireballError(err: "error while sending request"))
}
}
//Handle errors
Error(_) -> Error(FireballError(err: "error while creating request"))
}
}
///put_file function to create a document in Google's Firestore Database
pub fn post_doc_from_file(
apikey apikey: String,
apiver apiver: String,
database database: String,
collection_path collection_path: String,
doc_path doc_path: String,
input_doc input_doc: String,
proj_id proj_id: String,
) -> Result(FireballDocument, FireballError) {
//Init url
let url =
//Firestore url
"https://firestore.googleapis.com/"
//Firestore Web API version
<> apiver
<> "/projects/"
//Project id
<> proj_id
<> "/databases/"
//Database to use
<> database
<> "/documents/"
//Collection path
<> collection_path
<> "?documentId="
//Doc path
<> doc_path
//API Key for the google cloud project
<> "&key="
<> apikey
//Init a request object
case request.to(url) {
//
Ok(base_req) -> {
//Get the data from the json file
case simplifile.read(input_doc) {
//If we found and read the file
Ok(json_data) -> {
//Add headers
let req =
//Add headers
request.prepend_header(base_req, "Content-type", "application/json")
//Set method
|> request.set_method(http.Post)
//Set body
|> request.set_body(json_data)
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(resp) -> Ok(FireballDocument(data: resp.body))
//Handle errors
Error(_) -> Error(FireballError(err: "error while sending request"))
}
}
//Handle errors
Error(_) -> Error(FireballError(err: "failed to read file"))
}
}
//Handle errors
Error(_) -> Error(FireballError(err: "error while creating request"))
}
}
///get_file function is used to retrieve a single file from Google's Firestore Storage
pub fn get_file(
apiver apiver: String,
obj_path obj_path: String,
proj_id proj_id: String,
token token: String,
content_type content_type: String,
) -> Result(FireballStorage, FireballError) {
//Init URL
let url =
//Firestore url
"https://firebasestorage.googleapis.com/"
//Firestore Web API version
<> apiver
<> "/b/"
//Project id
<> proj_id
<> ".appspot.com/o/"
//Object to get without leading slash
<> string.replace(in: obj_path, each: "/", with: "%2F")
<> "?alt=media&token="
//Document path
<> token
//Init a request object
case request.to(url) {
//If we didn't have an error making the request
Ok(base_req) -> {
//Add headers
let req =
request.prepend_header(base_req, "Content-type", content_type)
//Set method
|> request.set_method(http.Get)
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(resp) -> Ok(FireballStorage(path: resp.body))
//Handle errors
Error(_) -> Error(FireballError(err: "error while sending request"))
}
}
//
Error(_) -> Error(FireballError(err: "failed to create request"))
}
}
//post_file function is used to upload a single file to Google's Firestore Storage as a Base64 encoded string
///This will change in the future if a native Multipart Form encoder is written for HTTPC
pub fn post_file(
apiver apiver: String,
apikey apikey: String,
infile infile: String,
outfile outfile: String,
proj_id proj_id: String,
) -> Result(String, FireballError) {
//Init a request object
case
request.to(
//Firestore url
"https://firestore.googleapis.com/"
//Firestore Web API version
<> apiver
<> "/b/"
//Project id
<> proj_id
<> ".appspot.com/o/"
//Filepath without leading slash
<> string.replace(in: outfile, each: "/", with: "%2F"),
)
{
//Check output
Ok(base_req) -> {
//Read in the infile
case simplifile.read_bits(infile) {
//If we didnt have an eror
Ok(raw_file_data) -> {
//Encode as Base64 string
let file_data = bit_array.base64_encode(raw_file_data, True)
//Add headers
let req =
//Add headers
request.prepend_header(base_req, "Content-type", "text/plain")
|> request.set_header("Authorization", apikey)
//Set body
|> request.set_body(file_data)
//Set method
|> request.set_method(http.Post)
//Send the HTTP request to the server
case httpc.send(req) {
//Retrieve successful response
Ok(_) -> Ok("OK")
//Handle errors
Error(_) -> Error(FireballError(err: "error while sending request"))
}
}
//If we have some kind of error while reading in the file
Error(_) ->
Error(FireballError(err: "Failed to read in file: " <> infile))
}
}
//Handle errors
Error(_) -> Error(FireballError(err: "error while forming request"))
}
}
///post_file_erl function is used to call erlang's inets and ssl funtions to send the request over httpc
@external(erlang, "fireball_ffi", "post_file_erl")
pub fn post_file_erl(url url: String, infile infile: String) -> Nil
///post_file, but uses system cURL instead of native libaries
pub fn post_file_external(
apiver apiver: String,
apikey _apikey: String,
infile infile: String,
outfile outfile: String,
proj_id proj_id: String,
external_script external_script: String,
wd wd: String,
) -> Result(String, FireballError) {
//Setup url to send to subprocess
let url =
//Firestore url
"https://firebasestorage.googleapis.com/"
//Firestore Web API version
<> apiver
<> "/b/"
//Project id
<> proj_id
<> ".appspot.com/o/"
//Filepath without leading slash
<> string.replace(in: outfile, each: "/", with: "%2F")
//Run the command with system cURL
case gleamyshell.execute(external_script, in: wd, args: [url, infile]) {
//Handle zero exit
Ok(CommandOutput(0, output)) -> Ok(string.trim(output))
//Handle non-zero exit
Ok(CommandOutput(exit_code, output)) ->
Error(FireballError(
err: "ERROR:" <> int.to_string(exit_code) <> ";" <> output,
))
//Handle erlang / system error
Error(reason) -> Error(FireballError(err: "FATAL_ERROR:" <> reason))
}
}
///Function to transform a document into a semi-colon string
pub fn doc_to_string(doc doc: FireballDocument) -> String {
doc.data
}
///Function to transform a document into a list of strings
pub fn doc_to_list(doc doc: FireballDocument) -> List(String) {
[doc.data]
}
///Function to transform a document into a tuple of strings
pub fn doc_to_tuple(doc doc: FireballDocument) -> #(String) {
#(doc.data)
}
///Function to transform a document into JSON
pub fn doc_to_json(doc doc: FireballDocument) -> String {
json.object([#("name", json.string(doc.data))])
|> json.to_string
}
///A function to turn UploadData into JSON
pub fn upload_data_to_json(upload_data upload_data: UploadData) -> String {
json.object([
#("name", json.string(upload_data.name)),
#("bucket", json.string(upload_data.bucket)),
#("generation", json.string(upload_data.generation)),
#("metageneration", json.string(upload_data.metageneration)),
#("contentType", json.string(upload_data.content_type)),
#("timeCreated", json.string(upload_data.time_created)),
#("updated", json.string(upload_data.updated)),
#("storageClass", json.string(upload_data.storage_class)),
#("size", json.string(upload_data.size)),
#("md5Hash", json.string(upload_data.md5_hash)),
#("contentEncoding", json.string(upload_data.content_encoding)),
#("contentDisposition", json.string(upload_data.content_disposition)),
#("crc32c", json.string(upload_data.crc32c)),
#("etag", json.string(upload_data.etag)),
#("downloadTokens", json.string(upload_data.download_tokens)),
])
|> json.to_string
}
///A function to turn JSON into UploadData - adapted from https://github.com/emergent/decode-json-examples/blob/main/test/decode_json_test.gleam
pub fn json_to_upload_data(
input input: dynamic.Dynamic,
) -> Result(UploadData, List(dynamic.DecodeError)) {
let decoder = {
use name <- decode.field("name", decode.string)
use bucket <- decode.field("bucket", decode.string)
use generation <- decode.field("generation", decode.string)
use metageneration <- decode.field("metageneration", decode.string)
use content_type <- decode.field("contentType", decode.string)
use time_created <- decode.field("timeCreated", decode.string)
use updated <- decode.field("updated", decode.string)
use storage_class <- decode.field("storageClass", decode.string)
use size <- decode.field("size", decode.string)
use md5_hash <- decode.field("md5Hash", decode.string)
use content_encoding <- decode.field("contentEncoding", decode.string)
use content_disposition <- decode.field("contentDisposition", decode.string)
use crc32c <- decode.field("crc32c", decode.string)
use etag <- decode.field("etag", decode.string)
use download_tokens <- decode.field("downloadTokens", decode.string)
decode.success(UploadData(
name:,
bucket:,
generation:,
metageneration:,
content_type:,
time_created:,
updated:,
storage_class:,
size:,
md5_hash:,
content_encoding:,
content_disposition:,
crc32c:,
etag:,
download_tokens:,
))
}
//Run the decoder
decode.run(input, decoder)
}