Saltar al contenido principal

Aurapps-file-storage

Ver en Git


Aurapps File Storage

El File Storage permite a una extensión almacenar archivos bajo keys, para un usuario, lote o campo (scope/alcance).

Un set de endpoints permite hacer el ABM de las keys y le proveerá al cliente las urls para que suba o descargue los achivos directamente del storage.

Esto significa que hay consistencia eventual, es decir, el cliente creará/actualizará las keys en la base de datos pero luego queda a su cargo subir el archivo, quedando una inconsistencia hasta que el archivo esté subido. Podría darse que el cliente no termine de subir el archivo y el sistema quede en estado inconsistente.

API

Objeto Stored-file

{
"file_name": "d5fa71aa-1f35-11ef-a2b9-ab5cffcaca66.pdf",
"file_size_kb": 150.5,
"created_at": "2024-01-01T00:00:00Z",
"url": "..."
}

Objeto POST-URL

{
"url": "....",
"fields": {
"f1": "...",
"f2": "...",
"f3": "..."
}
}

Para la subida de archivos, los endpoints proporcionan al cliente un objeto POST-URL. El mismo deberá realizar un POST a la url indicada, con un Content-Type multipart/form-data y un Body que incluya todos los key-value que se encuentran bajo la key fields del objeto POST-URL. Además, se deberá incluir en el form un campo Content-MD5 con el hash MD5 del archivo a subir. Al final del documento hay un script Python a modo de ejemplo.

La url de subida tiene una vigencia de 10 segundos. El máximo tamaño de archivo está configurado en 10MB.

Para la descarga de archivos sólo se proporciona una URL y se descarga mediante un GET.

La url de descarga tiene una vigencia de 10 segundos.

GET

POST /api/mktplace/filestorage/users ? operation = GET
POST /api/mktplace/filestorage/fields ? operation = GET
POST /api/mktplace/filestorage/farms ? operation = GET

Body: application/json

en el Body:

  • keys: array de keys a consultar - Obligatorio
  • ids: array de ids para filtrar recursos sobre los que buscar las keys
    • según el scope, serán ids de usuarios, lotes o campos.
    • en el scope de users, si no se incluye, se asume que se está consultando por el usuario que realiza la consulta.
    • en los scopes de fields y farms, si no se incluye, se buscan las keys solicitadas en todos los lotes/campos a los que tiene acceso el usuario
  • get_url: bool indica si se incluye o no la url de descarga en la respuesta (en el objeto Stored-file)

Respuesta:

{
"objects": {
"resource_id_1": {
"key_1": {} // objeto Stored-file
}
},
"resource_id_2": {
"key_1": {}, // objeto Stored-file
"key_2": {} // objeto Stored-file
}
}

En caso de solicitar keys para el scope de users y no indicar ids, como la consulta se realiza para el mismo usuario, la respuesta es distinta ya que no se incluye el nivel de recurso.

{
"objects": {
"key_1": {}, // objeto Stored-file
"key_2": {} // objeto Stored-file
}
}

PUT

Para crear una key. Primero se realiza la creación en el endpoint, obteniendose la url para subir el archivo.

Notar que al momento de realizar esta consulta, la nueva key se generará y figurará en el listado de las consultas GET pero hasta que el cliente no suba el nuevo archivo con la URL provista, el mismo no estará disponible. En caso de sobreescribir una key, el archivo reemplazado se eliminará al momento de hacer la consulta.

POST /api/mktplace/filestorage/users ? operation = PUT
POST /api/mktplace/filestorage/fields ? operation = PUT
POST /api/mktplace/filestorage/farms ? operation = PUT

Body: application/json

en el Body:

  • key: string key sobre la que se quiere guardar el archivo
  • original_file_name: string nombre del archivo que se quiere subir
  • md5: string hash MD5 codificado en base64 del archivo a subir. Este es el valor que luego debe enviarse al subir el archivo.
  • file_size_kb: float tamaño en kb del archivo a subir
  • overwrite: boolean indica si sobreescribir la key en caso de ya existe. Por defaulf es false por lo que si la key indicada ya existe, el endpoint responderá con error.
  • user: UID-XXXX id encriptado. sólo obligatorio en el scope users, indicando el id del usuario sobre el cual se desea guardar.
  • field: uuid/int sólo obligatorio en el scope fields, indicando el id del lote sobre el cual se desea guardar.
  • farm: uuid/int sólo obligatorio en el scope farms, indicando el id del campo sobre el cual se desea guardar.

Respuesta:

{
"data": {
"url": {} // objeto POST-URL
}
}

Tras una respuesta correcta, la key ya se encuentra almacenada y el próximo paso es subir el archivo. Para eso se utiliza el objeto POST-URL como se indicó previamente.

Posibles errores:

code
2formato de key inválido
3no se tiene permiso sobre el recurso (usuario/lote/campo) indicado
5la key indicada ya existe para el recurso indicado, y no se envió el flag de overwrite
7el archivo supera el tamaño máximo permitido

PATCH

Para actualizar el archivo almacenado bajo una key. Primero se realiza la modificación en el endpoint, obteniendose la nueva url para subir el nuevo archivo.

Notar que al momento de realizar esta consulta, el nuevo valor reemplaza al anterior y el archivo reemplazado se elimina inmediatamente. En las consultas GET se listará el nuevo archivo pero hasta que el cliente no lo suba con la URL provista, el mismo no estará disponible.

POST /api/mktplace/filestorage/users ? operation = PATCH
POST /api/mktplace/filestorage/fields ? operation = PATCH
POST /api/mktplace/filestorage/farms ? operation = PATCH

Body: application/json

en el Body:

  • key: string key sobre la que se quiere guardar el archivo
  • original_file_name: string nombre del archivo que se quiere subir
  • md5: string hash MD5 codificado en base64 del archivo a subir Este es el valor que luego debe enviarse al subir el archivo.
  • file_size_kb: float tamaño en kb del archivo a subir
  • create: boolean indica si crear la key en caso de no existir. Por defaulf es false por lo que si la key indicada no existe, el endpoint responderá con error.
  • user: UID-XXXX id encriptado. sólo obligatorio en el scope users, indicando el id del usuario sobre el cual se desea guardar.
  • field: uuid/int sólo obligatorio en el scope fields, indicando el id del lote sobre el cual se desea guardar.
  • farm: uuid/int sólo obligatorio en el scope farms, indicando el id del campo sobre el cual se desea guardar.

Respuesta:

{
"data": {
"url": {} // objeto POST-URL
}
}

Tras una respuesta correcta, la key ya se encuentra actualizada y el próximo paso es subir el archivo. Para eso se utiliza el objeto POST-URL como se indicó previamente.

Posibles errores:

code
2formato de key inválido
3no se tiene permiso sobre el recurso (usuario/lote/campo) indicado
5la key indicada no existe para el recurso indicado, y no se envió el flag de create
7el archivo supera el tamaño máximo permitido

DELETE

Para borrar keys. Se pueden borrar una key o un grupo de keys para todos los recursos del scope o filtrar por id de recurso.

POST /api/mktplace/filestorage/users ? operation = DELETE
POST /api/mktplace/filestorage/fields ? operation = DELETE
POST /api/mktplace/filestorage/farms ? operation = DELETE

Body: application/json

en el Body:

  • keys: array keys a eliminar - Obligatorio
  • id: string|int id del recurso - Opcional

Respuesta:

{
"data": {
"keys": [] // keys eliminadas
}
}

Posibles errores:

code
2formato de key inválido
3no se tiene permiso sobre el recurso (usuario/lote/campo) indicado

Request a AWS de ejemplo (Python)


# aurapps file storage
import requests
import base64
import hashlib

extension_token = ""
api_url = 'https://api.testing.auravant.com/larry'


def create_file(key, original_file_name, md5, file_size_kb, overwrite):
"""
llamada a modo de ejemplo. Segun la operacion y el scope los parametros cambian

Parameters
----------
key: str
key to store
original_file_name: str
the filename of the file to upload
md5: the md5 in base64 of the file's content
file_size_kb: int|float
size in kb
overwrite: bool

Returns
-------

"""
resp = requests.post(
url=f'{api_url}/api/mktplace/filestorage/users?operation=PUT',
headers={'Authorization': f'Bearer {extension_token}'},
json={
"key": key,
"original_file_name": original_file_name,
"md5": md5,
"file_size_kb": file_size_kb,
"overwrite": overwrite
}
)
if resp.status_code != 200:
print(resp.content)
raise ValueError()

return resp.json()['data']


def list_files(keys: list):
payload = {
"get_url": True # puede ser falso para solo listar los objetos
}
if keys:
payload["keys"] = keys
else:
raise ValueError('es obligatorio indicar key(s)')

resp = requests.post(
url=f'{api_url}/api/mktplace/filestorage/users?operation=GET',
headers={'Authorization': f'Bearer {extension_token}'},
json=payload
)
if resp.status_code != 200:
print(resp.content)
raise ValueError()

return resp.json()['objects']


def calculate_md5_and_size(file_path):
"""
calculate file's md5 and encode it to base64

Parameters
----------
file_path

Returns
-------

"""
with open(file_path, 'rb') as _f:

_hash = hashlib.md5(_f.read()).digest()
end = _f.tell()
return base64.b64encode(_hash).decode(), end / 1000


def main(key):

file_path = "./README.md"
file_md5, file_size_kb = calculate_md5_and_size(file_path)

creation_data = create_file(key, file_path.split('/')[-1], file_md5, file_size_kb, True)

print(creation_data)
url = creation_data['url']
url['fields']['Content-MD5'] = file_md5

with open(file_path) as f:
resp = requests.post(
url['url'],
files={'file': f}, # (file_path, f)},
data=url['fields'],
)
print(resp.content)


main('key1')
a = list_files(['key1'])

print(a)

Request a AWS de ejemplo (JS)

// Esto esta pendiente, pero dejo la parte del md5:

const reader = new FileReader();
reader.readAsBinaryString(blob);
reader.onloadend = () => {
resolve(CryptoJS.enc.Base64.stringify(CryptoJS.MD5(CryptoJS.enc.Latin1.parse(reader.result.toString()))));
};