Each example is the inside of main, written in every language API Maker runs : pick yours in the tabs, and every page shows it from then on. The types a call takes (GetAll, DeepItem…) are those of am : from am import GetAll in Python, use am::GetAll; in Rust, import am.GetAll; in Java and Kotlin ; PHP and C# have them without an import, Go names them am.GetAll.
In TypeScript every g.sys call returns a promise : await it. A call without await runs, but its result and its errors are lost. The same in Python (in async def main) and C#. PHP, Go, Rust, Java and Kotlin wait for the answer of each call.
A g.sys call returns the data and throws the errors of the API.
With true as the second argument in TypeScript, or through full in the other languages (g.sys.db.full, g.Sys.Db.Full), it returns the whole envelope { success, statusCode, data, errors, warnings } and never throws : read success yourself.
An error raised ends the call with success: false, the message in errors and its code as the status. A message listed in the errorList of the API can be translated.
letsaveData=awaitg.sys.db.saveSingleOrMultiple({instance:"INSTANCE_NAME",database:'DB_NAME',collection:"COLLECTION_NAME",saveData:{},headers:{}},true);if(!saveData.success)throw[{code:T.EStatusCode.BAD_REQUEST,message:'Your data is not saved!'}];returnsaveData;
save_data=awaitg.sys.db.full.save_single_or_multiple('INSTANCE_NAME','DB_NAME','COLLECTION_NAME',{},headers={})ifnotsave_data.success:raiseErrors('Your data is not saved!',code=400)returnsave_data
$saveData = $g->sys->db->full->saveSingleOrMultiple('INSTANCE_NAME', 'DB_NAME', 'COLLECTION_NAME', new AmObject(), headers: new AmObject());if (!$saveData->success) { throw new AmErrors('Your data is not saved!', 400);}return $saveData;
varsaveData=awaitg.Sys.Db.Full.SaveSingleOrMultiple(newSave("INSTANCE_NAME","DB_NAME","COLLECTION_NAME",newJsonObject()){Headers=newJsonObject()});if(!saveData.Success)thrownewAmErrors("Your data is not saved!",400);returnsaveData;
saveData,err:=g.Sys.Db.Full.SaveSingleOrMultiple(am.Save{Instance:"INSTANCE_NAME",Database:"DB_NAME",Collection:"COLLECTION_NAME",SaveData:am.M{},Headers:am.M{}})iferr!=nil{returnnil,err}if!saveData.Success{returnnil,am.Fail(400,"Your data is not saved!")}returnsaveData,nil
letsave_data=g.sys.db.full.save_single_or_multiple(Save::new("INSTANCE_NAME","DB_NAME","COLLECTION_NAME",json!({})).headers(json!({})))?;if!save_data.success{returnErr(am::fail(400,"Your data is not saved!"));}Ok(json!({"success":save_data.success,"data":save_data.data.row()}))
ApiResponse<Saved>saveData=g.sys.db.full.saveSingleOrMultiple(newSave("INSTANCE_NAME","DB_NAME","COLLECTION_NAME",Map.of()).headers(Map.of()));if(!saveData.isSuccess())thrownewAmErrors("Your data is not saved!",400);returnsaveData;
valsaveData=g.sys.db.full.saveSingleOrMultiple(Save("INSTANCE_NAME","DB_NAME","COLLECTION_NAME",mapOf<String,Any?>()).headers(mapOf<String,Any?>()))if(!saveData.isSuccess)throwAmErrors("Your data is not saved!",400)returnsaveData
import*asTfrom'types';import*asfsfrom'fs';import*aspathfrom'path';asyncfunctionmain(g:T.IAMGlobal){// always write file in the uploads folder which you want to send to user.// you can write other files in any directory which you don't want to send to user.awaitfs.promises.mkdir(path.join(__dirname,'uploads'),{recursive:true});letfilePath=path.join(__dirname,'uploads','myfile.txt');awaitfs.promises.writeFile(filePath,'file 1 content',{encoding:'utf8'});return{// just provide file name which is written to "uploads" directory__am__downloadFilePath:'myfile.txt',__am__downloadFolderFileName:'newName.txt',// download file will have this name.}};module.exports=main;
importosfromamimportGasyncdefmain(g:G):# Write the files to send in the folder uploads ; the other files anywhere else.os.makedirs('uploads',exist_ok=True)withopen(os.path.join('uploads','myfile.txt'),'w',encoding='utf-8')asfile:file.write('file 1 content')return{'__am__downloadFilePath':'myfile.txt',# its name in the folder uploads'__am__downloadFolderFileName':'newName.txt',# the name the file is saved under}
<?phpfunctionmain(AmG$g){// Write the files to send in the folder uploads ; the other files anywhere else.if(!is_dir('uploads')){mkdir('uploads',0777,true);}file_put_contents('uploads/myfile.txt','file 1 content');return['__am__downloadFilePath'=>'myfile.txt',// its name in the folder uploads'__am__downloadFolderFileName'=>'newName.txt',// the name the file is saved under];}
usingSystem.IO;filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){// Write the files to send in the folder uploads ; the other files anywhere else.Directory.CreateDirectory("uploads");awaitFile.WriteAllTextAsync(Path.Combine("uploads","myfile.txt"),"file 1 content");returnnewJsonObject{["__am__downloadFilePath"]="myfile.txt",// its name in the folder uploads["__am__downloadFolderFileName"]="newName.txt",// the name the file is saved under};}}
packagecodeimport("os""path/filepath""apimaker/am")funcMain(g*am.G)(any,error){// Write the files to send in the folder uploads ; the other files anywhere else.iferr:=os.MkdirAll("uploads",0o755);err!=nil{returnnil,err}iferr:=os.WriteFile(filepath.Join("uploads","myfile.txt"),[]byte("file 1 content"),0o644);err!=nil{returnnil,err}returnam.M{"__am__downloadFilePath":"myfile.txt",// its name in the folder uploads"__am__downloadFolderFileName":"newName.txt",// the name the file is saved under},nil}
useam::{Errors,G,Value,json};pubfnmain(g:&mutG)->Result<Value,Errors>{// Write the files to send in the folder uploads ; the other files anywhere else.std::fs::create_dir_all("uploads")?;std::fs::write("uploads/myfile.txt","file 1 content")?;Ok(json!({"__am__downloadFilePath":"myfile.txt",// its name in the folder uploads"__am__downloadFolderFileName":"newName.txt",// the name the file is saved under}))}
importam.G;importjava.nio.file.Files;importjava.nio.file.Path;importjava.util.Map;classApi{staticObjectmain(Gg)throwsException{// Write the files to send in the folder uploads ; the other files anywhere else.Files.createDirectories(Path.of("uploads"));Files.writeString(Path.of("uploads","myfile.txt"),"file 1 content");returnMap.of("__am__downloadFilePath","myfile.txt",// its name in the folder uploads"__am__downloadFolderFileName","newName.txt");// the name the file is saved under}}
importam.Gimportjava.io.Filefunmain(g:G):Any?{// Write the files to send in the folder uploads ; the other files anywhere else.File("uploads").mkdirs()File("uploads","myfile.txt").writeText("file 1 content")returnmapOf("__am__downloadFilePath"to"myfile.txt",// its name in the folder uploads"__am__downloadFolderFileName"to"newName.txt",// the name the file is saved under)}
import*asTfrom'types';import*asfsfrom'fs';import*aspathfrom'path';asyncfunctionmain(g:T.IAMGlobal){return{__am__downloadFileOrFolderPaths:[{fsSource:path.join('folder1','folder2','folder3','data_folder'),// path into uploads folderarchiveDestination:'data_folder',// path in zip file, so data_folder folder will be directly available in zip file.},],__am__downloadFolderFileName:`output.zip`,// download file will have this name.}};module.exports=main;
fromamimportGasyncdefmain(g:G):return{'__am__downloadFileOrFolderPaths':[{'fsSource':'folder1/folder2/folder3/data_folder',# a path in the folder uploads'archiveDestination':'data_folder',# its path in the zip file},],'__am__downloadFolderFileName':'output.zip',# the name the file is saved under}
<?phpfunctionmain(AmG$g){return['__am__downloadFileOrFolderPaths'=>[['fsSource'=>'folder1/folder2/folder3/data_folder',// a path in the folder uploads'archiveDestination'=>'data_folder',// its path in the zip file],],'__am__downloadFolderFileName'=>'output.zip',// the name the file is saved under];}
filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){returnnewJsonObject{["__am__downloadFileOrFolderPaths"]=newJsonArray(newJsonObject{["fsSource"]="folder1/folder2/folder3/data_folder",// a path in the folder uploads["archiveDestination"]="data_folder",// its path in the zip file}),["__am__downloadFolderFileName"]="output.zip",// the name the file is saved under};}}
packagecodeimport"apimaker/am"funcMain(g*am.G)(any,error){returnam.M{"__am__downloadFileOrFolderPaths":[]any{am.M{"fsSource":"folder1/folder2/folder3/data_folder",// a path in the folder uploads"archiveDestination":"data_folder",// its path in the zip file},},"__am__downloadFolderFileName":"output.zip",// the name the file is saved under},nil}
useam::{Errors,G,Value,json};pubfnmain(g:&mutG)->Result<Value,Errors>{Ok(json!({"__am__downloadFileOrFolderPaths":[{"fsSource":"folder1/folder2/folder3/data_folder",// a path in the folder uploads"archiveDestination":"data_folder",// its path in the zip file},],"__am__downloadFolderFileName":"output.zip",// the name the file is saved under}))}
importam.G;importjava.util.List;importjava.util.Map;classApi{staticObjectmain(Gg){returnMap.of("__am__downloadFileOrFolderPaths",List.of(Map.of("fsSource","folder1/folder2/folder3/data_folder",// a path in the folder uploads"archiveDestination","data_folder")),// its path in the zip file"__am__downloadFolderFileName","output.zip");// the name the file is saved under}}
importam.Gfunmain(g:G):Any?{returnmapOf("__am__downloadFileOrFolderPaths"tolistOf(mapOf("fsSource"to"folder1/folder2/folder3/data_folder",// a path in the folder uploads"archiveDestination"to"data_folder",// its path in the zip file),),"__am__downloadFolderFileName"to"output.zip",// the name the file is saved under)}
import*asTfrom'types';import*asfsfrom'fs';import*aspathfrom'path';asyncfunctionmain(g:T.IAMGlobal){// always write file in the uploads folder which you want to send to user.// you can write other files in any directory which you don't want to send to user.awaitfs.promises.mkdir(path.join(__dirname,'uploads'),{recursive:true});letfilePath=path.join(__dirname,'uploads','myfile.txt');awaitfs.promises.writeFile(filePath,'file 1 content',{encoding:'utf8'});return{// You can provide list of files or folders, it will will make zip and send to user.__am__downloadFileOrFolderPaths:['myfile.txt',// file name'someFolder',// folder name],__am__downloadFolderFileName:'zipFile.zip',// this will be zip file name}};module.exports=main;
importosfromamimportGasyncdefmain(g:G):# Write the files to send in the folder uploads ; the other files anywhere else.os.makedirs('uploads',exist_ok=True)withopen(os.path.join('uploads','myfile.txt'),'w',encoding='utf-8')asfile:file.write('file 1 content')return{# Files and folders of the folder uploads : they go out as one zip file.'__am__downloadFileOrFolderPaths':['myfile.txt','someFolder'],'__am__downloadFolderFileName':'zipFile.zip',# the name of the zip file}
<?phpfunctionmain(AmG$g){// Write the files to send in the folder uploads ; the other files anywhere else.if(!is_dir('uploads')){mkdir('uploads',0777,true);}file_put_contents('uploads/myfile.txt','file 1 content');return[// Files and folders of the folder uploads : they go out as one zip file.'__am__downloadFileOrFolderPaths'=>['myfile.txt','someFolder'],'__am__downloadFolderFileName'=>'zipFile.zip',// the name of the zip file];}
usingSystem.IO;filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){// Write the files to send in the folder uploads ; the other files anywhere else.Directory.CreateDirectory("uploads");awaitFile.WriteAllTextAsync(Path.Combine("uploads","myfile.txt"),"file 1 content");returnnewJsonObject{// Files and folders of the folder uploads : they go out as one zip file.["__am__downloadFileOrFolderPaths"]=newJsonArray("myfile.txt","someFolder"),["__am__downloadFolderFileName"]="zipFile.zip",// the name of the zip file};}}
packagecodeimport("os""path/filepath""apimaker/am")funcMain(g*am.G)(any,error){// Write the files to send in the folder uploads ; the other files anywhere else.iferr:=os.MkdirAll("uploads",0o755);err!=nil{returnnil,err}iferr:=os.WriteFile(filepath.Join("uploads","myfile.txt"),[]byte("file 1 content"),0o644);err!=nil{returnnil,err}returnam.M{// Files and folders of the folder uploads : they go out as one zip file."__am__downloadFileOrFolderPaths":[]string{"myfile.txt","someFolder"},"__am__downloadFolderFileName":"zipFile.zip",// the name of the zip file},nil}
useam::{Errors,G,Value,json};pubfnmain(g:&mutG)->Result<Value,Errors>{// Write the files to send in the folder uploads ; the other files anywhere else.std::fs::create_dir_all("uploads")?;std::fs::write("uploads/myfile.txt","file 1 content")?;Ok(json!({// Files and folders of the folder uploads : they go out as one zip file."__am__downloadFileOrFolderPaths":["myfile.txt","someFolder"],"__am__downloadFolderFileName":"zipFile.zip",// the name of the zip file}))}
importam.G;importjava.nio.file.Files;importjava.nio.file.Path;importjava.util.List;importjava.util.Map;classApi{staticObjectmain(Gg)throwsException{// Write the files to send in the folder uploads ; the other files anywhere else.Files.createDirectories(Path.of("uploads"));Files.writeString(Path.of("uploads","myfile.txt"),"file 1 content");returnMap.of(// Files and folders of the folder uploads : they go out as one zip file."__am__downloadFileOrFolderPaths",List.of("myfile.txt","someFolder"),"__am__downloadFolderFileName","zipFile.zip");// the name of the zip file}}
importam.Gimportjava.io.Filefunmain(g:G):Any?{// Write the files to send in the folder uploads ; the other files anywhere else.File("uploads").mkdirs()File("uploads","myfile.txt").writeText("file 1 content")returnmapOf(// Files and folders of the folder uploads : they go out as one zip file."__am__downloadFileOrFolderPaths"tolistOf("myfile.txt","someFolder"),"__am__downloadFolderFileName"to"zipFile.zip",// the name of the zip file)}
asyncfunctiondownloadFileFromBrowser(){constrequestPayload={};constrequestHeaders={};constresp=<any>awaitthis.http.post(`API Maker custom API endpoint`,requestPayload,{headers:requestHeaders,responseType:<any>'text',observe:'response',}).toPromise();constfileName=resp.headers.get('content-filename');awaitthis.downloadFileFromBlob('application/zip',resp.body,fileName);returnresp;}asyncfunctiondownloadFileFromBlob(contentType,base64Data:any,fileName){consta=document.createElement('a');document.body.appendChild(a);a.style.display='none';letblob;if(base64DatainstanceofBlob){blob=base64Data;}else{constbase64Response=awaitfetch(`data:${contentType};base64,`+base64Data);blob=awaitbase64Response.blob();}consturl=window.URL.createObjectURL(blob);a.href=url;a.download=fileName;a.click();window.URL.revokeObjectURL(url);}
It reads the tenant in g.req.params, and the APIs it calls with g.sys.db and g.sys.system work on the database of that tenant: the header is passed on, and each instance finds the tenant in its own tenants table.
instance: 'crm::globex' names another tenant for one call, and an empty tenant header names the structure database. See Custom APIs of a multi-tenant instance.
import*asTfrom'types';asyncfunctionmain(g:T.IAMGlobal){consttenant=g.req.params.tenantUsername;// 'acme'constcustomers=awaitg.sys.db.getAll({instance:'crm',database:'crm',collection:'customers'});// from the database of acmeconstcustomersOfGlobex=awaitg.sys.db.count({instance:'crm::globex',database:'crm',collection:'customers'});return{tenant,customers,customersOfGlobex};}module.exports=main;
fromamimportGasyncdefmain(g:G):tenant=g.req.params.tenantUsername# 'acme'customers=awaitg.sys.db.get_all('crm','crm','customers')# from the database of acmecustomers_of_globex=awaitg.sys.db.count('crm::globex','crm','customers')return{'tenant':tenant,'customers':customers,'customersOfGlobex':customers_of_globex}
<?phpfunctionmain(AmG$g){$tenant=$g->req->params->tenantUsername;// 'acme'$customers=$g->sys->db->getAll('crm','crm','customers');// from the database of acme$customersOfGlobex=$g->sys->db->count('crm::globex','crm','customers');return['tenant'=>$tenant,'customers'=>$customers,'customersOfGlobex'=>$customersOfGlobex];}
filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){vartenant=g.Req.Params.GetString("tenantUsername");// "acme"varcustomers=awaitg.Sys.Db.GetAll(newGetAll("crm","crm","customers"));// from the database of acmevarcustomersOfGlobex=awaitg.Sys.Db.Count(newCount("crm::globex","crm","customers"));returnnew{tenant,customers,customersOfGlobex};}}
packagecodeimport"apimaker/am"funcMain(g*am.G)(any,error){tenant:=g.Req.Params().String("tenantUsername")// "acme"customers,err:=g.Sys.Db.GetAll(am.GetAll{Instance:"crm",Database:"crm",Collection:"customers"})// from the database of acmeiferr!=nil{returnnil,err}customersOfGlobex,err:=g.Sys.Db.Count(am.Count{Instance:"crm::globex",Database:"crm",Collection:"customers"})iferr!=nil{returnnil,err}returnam.M{"tenant":tenant,"customers":customers,"customersOfGlobex":customersOfGlobex},nil}
useam::{Count,Errors,G,GetAll,Value,json};pubfnmain(g:&mutG)->Result<Value,Errors>{lettenant=g.req.params()["tenantUsername"].clone();// "acme"letcustomers=g.sys.db.get_all(GetAll::new("crm","crm","customers"))?;// from the database of acmeletcustomers_of_globex=g.sys.db.count(Count::new("crm::globex","crm","customers"))?;Ok(json!({"tenant":tenant,"customers":customers,"customersOfGlobex":customers_of_globex}))}
importam.Count;importam.G;importam.GetAll;importam.JsonObject;importjava.util.List;classApi{staticObjectmain(Gg){Stringtenant=g.req.getParams().getString("tenantUsername");// "acme"List<JsonObject>customers=g.sys.db.getAll(newGetAll("crm","crm","customers"));// from the database of acmelongcustomersOfGlobex=g.sys.db.count(newCount("crm::globex","crm","customers"));returnJsonObject.of("tenant",tenant,"customers",customers,"customersOfGlobex",customersOfGlobex);}}
importam.Countimportam.Gimportam.GetAllfunmain(g:G):Any?{valtenant=g.req.params.getString("tenantUsername")// "acme"valcustomers=g.sys.db.getAll(GetAll("crm","crm","customers"))// from the database of acmevalcustomersOfGlobex=g.sys.db.count(Count("crm::globex","crm","customers"))returnmapOf("tenant"totenant,"customers"tocustomers,"customersOfGlobex"tocustomersOfGlobex)}
A new account comes with custom APIs under /default/… which show the features at work. Each one is written below in every language : the settings (always TypeScript) and the code of the language of your tab.
The login of an application : it checks the email and the password with the token providerdefault_db_login, and answers the two tokens every later call sends, x-am-authorization (the app, an API user) and x-am-user-authorization (the person).
import*asTfrom'types';constcustomApi:T.ICustomApiSettingsTypes={name:'Login',path:'/default/login',requestMethod:T.ERequestMethod.POST,apiAccessType:T.EAPIAccessType.IS_PUBLIC,// the caller has no token yeterrorList:['Invalid email or password'],// one message for every failure : it never tells which emails existreqBodySchema:{// checked before the code runsemail:{__type:T.EType.string,validations:{required:true}},password:{__type:T.EType.string,validations:{required:true}},},};module.exports=customApi;
import*asTfrom'types';asyncfunctionmain(g:T.IAMGlobal){constemail=String(g.req.body.email||'').trim().toLowerCase();// as the schema of customers stores itconstpassword=String(g.req.body.password||'');constrefused=[{code:401,message:'Invalid email or password'}];if(!email||!password)throwrefused;// The person, without its password : the token provider checks the password, not this read.constcustomers=awaitg.sys.db.query({instance:'mongodb',database:'shop',collection:'customers',find:{email},select:'-password',limit:1,});if(!customers.length)throwrefused;// The token of the app : the password of the API user is read from the secret, never written in the code.constappPassword=awaitg.sys.system.getSecret('common.apiUserPasswords.default');constapp=awaitg.sys.system.getToken({u:'default',p:appPassword});// The token of the person : default_db_login compares the password with the stored one.letperson;try{person=awaitg.sys.system.getToken({name:'default_db_login',u:email,p:password});}catch(e){throwrefused;}return{amToken:app.token,userToken:person.token,refreshToken:person.refresh_token,customer:customers[0]};}module.exports=main;
fromamimportErrors,Gasyncdefmain(g:G):email=str(g.req.body.emailor'').strip().lower()# as the schema of customers stores itpassword=str(g.req.body.passwordor'')ifnotemailornotpassword:raiseErrors('Invalid email or password',code=401)# The person, without its password : the token provider checks the password, not this read.customers=awaitg.sys.db.query('mongodb','shop','customers',find={'email':email},select='-password',limit=1)ifnotcustomers:raiseErrors('Invalid email or password',code=401)# The token of the app : the password of the API user is read from the secret, never written in the code.app_password=awaitg.sys.system.get_secret('common.apiUserPasswords.default')app=awaitg.sys.system.get_token('default',app_password)# The token of the person : default_db_login compares the password with the stored one.try:person=awaitg.sys.system.get_token(email,password,name='default_db_login')exceptErrors:raiseErrors('Invalid email or password',code=401)return{'amToken':app.token,'userToken':person.token,'refreshToken':person.refresh_token,'customer':customers[0]}
<?phpfunctionmain(AmG$g){$email=strtolower(trim((string)($g->req->body->email??'')));// as the schema of customers stores it$password=(string)($g->req->body->password??'');if($email===''||$password===''){thrownewAmErrors('Invalid email or password',401);}// The person, without its password : the token provider checks the password, not this read.$customers=$g->sys->db->query('mongodb','shop','customers',find:['email'=>$email],select:'-password',limit:1);if(!$customers){thrownewAmErrors('Invalid email or password',401);}// The token of the app : the password of the API user is read from the secret, never written in the code.$appPassword=$g->sys->system->getSecret('common.apiUserPasswords.default');$app=$g->sys->system->getToken('default',$appPassword);// The token of the person : default_db_login compares the password with the stored one.try{$person=$g->sys->system->getToken($email,$password,name:'default_db_login');}catch(AmErrors$refused){thrownewAmErrors('Invalid email or password',401);}return['amToken'=>$app->token,'userToken'=>$person->token,'refreshToken'=>$person->refresh_token,'customer'=>$customers[0]];}
filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){varemail=g.Req.Body.GetString("email","").Trim().ToLowerInvariant();// as the schema of customers stores itvarpassword=g.Req.Body.GetString("password","");if(email==""||password=="")thrownewAmErrors("Invalid email or password",401);// The person, without its password : the token provider checks the password, not this read.varcustomers=awaitg.Sys.Db.Query(newQuery("mongodb","shop","customers"){Find=newJsonObject{["email"]=email},Select="-password",Limit=1,});if(customers.Count==0)thrownewAmErrors("Invalid email or password",401);// The token of the app : the password of the API user is read from the secret, never written in the code.varappPassword=(awaitg.Sys.System.GetSecret("common.apiUserPasswords.default"))?.GetValue<string>();varapp=awaitg.Sys.System.GetToken(newGetToken("default",appPassword));// The token of the person : default_db_login compares the password with the stored one.try{varperson=awaitg.Sys.System.GetToken(newGetToken(email,password){Name="default_db_login"});returnnew{amToken=app.Token,userToken=person.Token,refreshToken=person.RefreshToken,customer=customers[0]};}catch(AmErrors){thrownewAmErrors("Invalid email or password",401);}}}
packagecodeimport("strings""apimaker/am")funcMain(g*am.G)(any,error){body:=g.Req.Body()email:=strings.ToLower(strings.TrimSpace(body.String("email")))// as the schema of customers stores itpassword:=body.String("password")ifemail==""||password==""{returnnil,am.Fail(401,"Invalid email or password")}// The person, without its password : the token provider checks the password, not this read.customers,err:=g.Sys.Db.Query(am.Query{Instance:"mongodb",Database:"shop",Collection:"customers",Find:am.M{"email":email},Select:"-password",Limit:1,})iferr!=nil{returnnil,err}iflen(customers)==0{returnnil,am.Fail(401,"Invalid email or password")}// The token of the app : the password of the API user is read from the secret, never written in the code.secret,err:=g.Sys.System.GetSecret("common.apiUserPasswords.default")iferr!=nil{returnnil,err}appPassword,_:=secret.(string)app,err:=g.Sys.System.GetToken(am.GetToken{U:"default",P:appPassword})iferr!=nil{returnnil,err}// The token of the person : default_db_login compares the password with the stored one.person,err:=g.Sys.System.GetToken(am.GetToken{Name:"default_db_login",U:email,P:password})iferr!=nil{returnnil,am.Fail(401,"Invalid email or password")}returnam.M{"amToken":app.Token,"userToken":person.Token,"refreshToken":person.RefreshToken,"customer":customers[0]},nil}
useam::{Errors,G,GetToken,Query,Value,json};pubfnmain(g:&mutG)->Result<Value,Errors>{letbody=g.req.body();letemail=body["email"].as_str().unwrap_or_default().trim().to_lowercase();// as the schema of customers stores itletpassword=body["password"].as_str().unwrap_or_default().to_owned();ifemail.is_empty()||password.is_empty(){returnErr(am::fail(401,"Invalid email or password"));}// The person, without its password : the token provider checks the password, not this read.letcustomers=g.sys.db.query(Query::new("mongodb","shop","customers").find(json!({"email":email})).select("-password").limit(1),)?;letSome(customer)=customers.first().cloned()else{returnErr(am::fail(401,"Invalid email or password"));};// The token of the app : the password of the API user is read from the secret, never written in the code.letapp_password=g.sys.system.get_secret("common.apiUserPasswords.default",None)?;letapp=g.sys.system.get_token(GetToken::new("default",app_password.as_str().unwrap_or_default()))?;// The token of the person : default_db_login compares the password with the stored one.letperson=g.sys.system.get_token(GetToken::new(&email,&password).name("default_db_login")).map_err(|_|am::fail(401,"Invalid email or password"))?;Ok(json!({"amToken":app.token,"userToken":person.token,"refreshToken":person.refresh_token,"customer":customer}))}
importam.AmErrors;importam.G;importam.GetToken;importam.JsonObject;importam.Query;importam.TokenResponse;importjava.util.List;importjava.util.Map;classApi{staticObjectmain(Gg){JsonObjectbody=g.req.getBody();Stringemail=body.getString("email","").trim().toLowerCase();// as the schema of customers stores itStringpassword=body.getString("password","");if(email.isEmpty()||password.isEmpty())thrownewAmErrors("Invalid email or password",401);// The person, without its password : the token provider checks the password, not this read.List<JsonObject>customers=g.sys.db.query(newQuery("mongodb","shop","customers").find(Map.of("email",email)).select("-password").limit(1));if(customers.isEmpty())thrownewAmErrors("Invalid email or password",401);// The token of the app : the password of the API user is read from the secret, never written in the code.StringappPassword=(String)g.sys.system.getSecret("common.apiUserPasswords.default");TokenResponseapp=g.sys.system.getToken(newGetToken("default",appPassword));// The token of the person : default_db_login compares the password with the stored one.TokenResponseperson;try{person=g.sys.system.getToken(newGetToken(email,password).name("default_db_login"));}catch(AmErrorsrefused){thrownewAmErrors("Invalid email or password",401);}returnMap.of("amToken",app.getToken(),"userToken",person.getToken(),"refreshToken",person.getRefreshToken(),"customer",customers.get(0));}}
importam.AmErrorsimportam.Gimportam.GetTokenimportam.Queryfunmain(g:G):Any?{valemail=g.req.body.getString("email","").trim().lowercase()// as the schema of customers stores itvalpassword=g.req.body.getString("password","")if(email.isEmpty()||password.isEmpty())throwAmErrors("Invalid email or password",401)// The person, without its password : the token provider checks the password, not this read.valcustomers=g.sys.db.query(Query("mongodb","shop","customers").find(mapOf("email"toemail)).select("-password").limit(1))if(customers.isEmpty())throwAmErrors("Invalid email or password",401)// The token of the app : the password of the API user is read from the secret, never written in the code.valappPassword=g.sys.system.getSecret("common.apiUserPasswords.default")asStringvalapp=g.sys.system.getToken(GetToken("default",appPassword))// The token of the person : default_db_login compares the password with the stored one.valperson=try{g.sys.system.getToken(GetToken(email,password).name("default_db_login"))}catch(refused:AmErrors){throwAmErrors("Invalid email or password",401)}returnmapOf("amToken"toapp.token,"userToken"toperson.token,"refreshToken"toperson.refreshToken,"customer"tocustomers[0])}
The API user default and its password path common.apiUserPasswords.default are those of a new account. Use the API user of the application, with only the groups it needs.
The person comes from its token (g.req.auth.authAMDB, the row the token provider read), never from the request : nobody can ask for the orders of somebody else.
import*asTfrom'types';asyncfunctionmain(g:T.IAMGlobal){constme=g.req.auth.authAMDB?.customer_id;// its row of customers, as its token saysif(me===undefined||me===null)throw[{code:401,message:'Please sign in.'}];returnawaitg.sys.db.getAll({instance:'mongodb',database:'shop',collection:'orders',queryParams:{find:{customer_id:me},sort:'-created_at',limit:50},});}module.exports=main;
fromamimportErrors,Gasyncdefmain(g:G):person=g.req.auth.authAMDB# its row of customers, as its token saysifnotpersonorperson.customer_idisNone:raiseErrors('Please sign in.',code=401)returnawaitg.sys.db.get_all('mongodb','shop','orders',find={'customer_id':person.customer_id},sort='-created_at',limit=50)
<?phpfunctionmain(AmG$g){$me=$g->req->auth->authAMDB->customer_id??null;// its row of customers, as its token saysif($me===null){thrownewAmErrors('Please sign in.',401);}return$g->sys->db->getAll('mongodb','shop','orders',find:['customer_id'=>$me],sort:'-created_at',limit:50);}
filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){varme=g.Req.Auth.GetObject("authAMDB")["customer_id"];// its row of customers, as its token saysif(meisnull)thrownewAmErrors("Please sign in.",401);returnawaitg.Sys.Db.GetAll(newGetAll("mongodb","shop","orders"){Find=newJsonObject{["customer_id"]=me.DeepClone()},Sort="-created_at",Limit=50,});}}
packagecodeimport"apimaker/am"funcMain(g*am.G)(any,error){me:=g.Req.Auth().Map("authAMDB").Get("customer_id")// its row of customers, as its token saysifme==nil{returnnil,am.Fail(401,"Please sign in.")}returng.Sys.Db.GetAll(am.GetAll{Instance:"mongodb",Database:"shop",Collection:"orders",Find:am.M{"customer_id":me},Sort:"-created_at",Limit:50,})}
useam::{Errors,G,GetAll,Value,json};pubfnmain(g:&mutG)->Result<Value,Errors>{letme=g.req.auth()["authAMDB"]["customer_id"].clone();// its row of customers, as its token saysifme.is_null(){returnErr(am::fail(401,"Please sign in."));}letorders=g.sys.db.get_all(GetAll::new("mongodb","shop","orders").find(json!({"customer_id":me})).sort("-created_at").limit(50))?;Ok(json!(orders))}
importam.AmErrors;importam.G;importam.GetAll;importjava.util.Map;classApi{staticObjectmain(Gg){Objectme=g.req.getAuth().getObject("authAMDB").get("customer_id");// its row of customers, as its token saysif(me==null)thrownewAmErrors("Please sign in.",401);returng.sys.db.getAll(newGetAll("mongodb","shop","orders").find(Map.of("customer_id",me)).sort("-created_at").limit(50));}}
importam.AmErrorsimportam.Gimportam.GetAllfunmain(g:G):Any?{valme=g.req.auth.getObject("authAMDB")["customer_id"]?:throwAmErrors("Please sign in.",401)// its row of customers, as its token saysreturng.sys.db.getAll(GetAll("mongodb","shop","orders").find(mapOf("customer_id"tome)).sort("-created_at").limit(50))}
The APIs of the tables need the same rule : a row scoping pre hook puts the person into the filter of every API of the table. The samples restrictToOwnerInstance, restrictToOwnerDatabase and restrictToOwnerOrders of a new account are such hooks, inactive.
returnawaitg.sys.db.getAll({instance:'mongodb',database:'shop',collection:'orders',queryParams:{find:{status:'PAID'},select:'order_no,total_cents,status,customer_id',sort:'-created_at',limit:50,getTotalCount:true,deep:[{s_key:'customer_id',t_db:'shop',t_col:'customers',t_key:'customer_id',select:'customer_id,email,country'}],},},true);// true : the whole answer, with totalCount
returnawaitg.sys.db.full.get_all(# full : the whole answer, with totalCount'mongodb','shop','orders',find={'status':'PAID'},select='order_no,total_cents,status,customer_id',sort='-created_at',limit=50,get_total_count=True,deep=[DeepItem('customer_id',t_db='shop',t_col='customers',t_key='customer_id',select='customer_id,email,country')],)
returnawaitg.Sys.Db.Full.GetAll(newGetAll("mongodb","shop","orders")// Full : the whole answer, with totalCount{Find=newJsonObject{["status"]="PAID"},Select="order_no,total_cents,status,customer_id",Sort="-created_at",Limit=50,GetTotalCount=true,Deep=new[]{newDeepItem("customer_id"){TDb="shop",TCol="customers",TKey="customer_id",Select="customer_id,email,country"}},});
returng.Sys.Db.Full.GetAll(am.GetAll{// Full : the whole answer, with totalCountInstance:"mongodb",Database:"shop",Collection:"orders",Find:am.M{"status":"PAID"},Select:"order_no,total_cents,status,customer_id",Sort:"-created_at",Limit:50,GetTotalCount:true,Deep:[]am.DeepItem{am.DeepItem{SKey:"customer_id",TDb:"shop",TCol:"customers",TKey:"customer_id",Select:"customer_id,email,country"}},})
letorders=g.sys.db.full.get_all(// full : the whole answer, with totalCountGetAll::new("mongodb","shop","orders").find(json!({"status":"PAID"})).select("order_no,total_cents,status,customer_id").sort("-created_at").limit(50).get_total_count(true).deep(vec![DeepItem::new("customer_id").t_db("shop").t_col("customers").t_key("customer_id").select("customer_id,email,country")]),)?;returnOk(json!({"success":orders.success,"data":orders.data,"totalCount":orders.total_count}));
varorders=g.sys.db.full.getAll(newGetAll("mongodb","shop","orders")// full : the whole answer, with totalCount.find(Map.of("status","PAID")).select("order_no,total_cents,status,customer_id").sort("-created_at").limit(50).getTotalCount(true).deep(List.of(newDeepItem("customer_id").tDb("shop").tCol("customers").tKey("customer_id").select("customer_id,email,country"))));returnorders;
returng.sys.db.full.getAll(// full : the whole answer, with totalCountGetAll("mongodb","shop","orders").find(mapOf("status"to"PAID")).select("order_no,total_cents,status,customer_id").sort("-created_at").limit(50).getTotalCount(true).deep(listOf(DeepItem("customer_id").tDb("shop").tCol("customers").tKey("customer_id").select("customer_id,email,country"))),)
A deep inside a deep goes one level further : each order item comes with its product, and each product with its category.
t_col, t_key and t_db can be left out when the schema of the column says where it points (database, collection, column of the property).
A filter on a column of another table : find join¶
A column which points to another table (a relation of the schema) is followed in find with a dot : 'customer_id.country' is the country of the customer of the order. It narrows the rows ; deep adds the related row. See find.
returnawaitg.sys.db.query({instance:'mongodb',database:'shop',collection:'orders',find:{status:'PAID','customer_id.country':'IN'},// a column of orders, then one of customersselect:'order_no,total_cents,status,customer_id',limit:50,});
returnawaitg.sys.db.query('mongodb','shop','orders',find={'status':'PAID','customer_id.country':'IN'},# a column of orders, then one of customersselect='order_no,total_cents,status,customer_id',limit=50,)
return $g->sys->db->query( 'mongodb', 'shop', 'orders', find: ['status' => 'PAID', 'customer_id.country' => 'IN'], // a column of orders, then one of customers select: 'order_no,total_cents,status,customer_id', limit: 50,);
returnawaitg.Sys.Db.Query(newQuery("mongodb","shop","orders"){Find=newJsonObject{["status"]="PAID",["customer_id.country"]="IN"},// a column of orders, then one of customersSelect="order_no,total_cents,status,customer_id",Limit=50,});
returng.Sys.Db.Query(am.Query{Instance:"mongodb",Database:"shop",Collection:"orders",Find:am.M{"status":"PAID","customer_id.country":"IN"},// a column of orders, then one of customersSelect:"order_no,total_cents,status,customer_id",Limit:50,})
letorders=g.sys.db.query(Query::new("mongodb","shop","orders").find(json!({"status":"PAID","customer_id.country":"IN"}))// a column of orders, then one of customers.select("order_no,total_cents,status,customer_id").limit(50),)?;returnOk(json!(orders));
varorders=g.sys.db.query(newQuery("mongodb","shop","orders").find(Map.of("status","PAID","customer_id.country","IN"))// a column of orders, then one of customers.select("order_no,total_cents,status,customer_id").limit(50));returnorders;
returng.sys.db.query(Query("mongodb","shop","orders").find(mapOf("status"to"PAID","customer_id.country"to"IN"))// a column of orders, then one of customers.select("order_no,total_cents,status,customer_id").limit(50),)
The same column can filter and populate : the active products of the category audio, each with its category.
The settings keep the answer in Redis until one of the tables it reads is written through API Maker : the code has nothing to do. See Automatic caching.
import*asTfrom'types';constcustomApi:T.ICustomApiSettingsTypes={name:'Caching Example',path:'/default/caching-example',requestMethod:T.ERequestMethod.GET,apiAccessType:T.EAPIAccessType.TOKEN_ACCESS,errorList:[],enableCaching:true,// one answer per URL, body and headers which matterresetCacheOnModificationOf:['DB: mongodb:shop:orders',// forgotten when the orders change'DB: mongodb:shop:orderItems',// or their items],};module.exports=customApi;
constorders=awaitg.sys.db.getAll({instance:'mongodb',database:'shop',collection:'orders',queryParams:{find:{status:'PAID'},select:'order_no,total_cents',sort:'-created_at',limit:20},});return{orders};// the next calls get this answer from the cache, until the orders change
orders=awaitg.sys.db.get_all('mongodb','shop','orders',find={'status':'PAID'},select='order_no,total_cents',sort='-created_at',limit=20,)return{'orders':orders}# the next calls get this answer from the cache, until the orders change
$orders = $g->sys->db->getAll( 'mongodb', 'shop', 'orders', find: ['status' => 'PAID'], select: 'order_no,total_cents', sort: '-created_at', limit: 20,);return ['orders' => $orders]; // the next calls get this answer from the cache, until the orders change
varorders=awaitg.Sys.Db.GetAll(newGetAll("mongodb","shop","orders"){Find=newJsonObject{["status"]="PAID"},Select="order_no,total_cents",Sort="-created_at",Limit=20,});returnnew{orders};// the next calls get this answer from the cache, until the orders change
orders,err:=g.Sys.Db.GetAll(am.GetAll{Instance:"mongodb",Database:"shop",Collection:"orders",Find:am.M{"status":"PAID"},Select:"order_no,total_cents",Sort:"-created_at",Limit:20,})iferr!=nil{returnnil,err}returnam.M{"orders":orders},nil// the next calls get this answer from the cache, until the orders change
letorders=g.sys.db.get_all(GetAll::new("mongodb","shop","orders").find(json!({"status":"PAID"})).select("order_no,total_cents").sort("-created_at").limit(20),)?;returnOk(json!({"orders":orders}));// the next calls get this answer from the cache, until the orders change
varorders=g.sys.db.getAll(newGetAll("mongodb","shop","orders").find(Map.of("status","PAID")).select("order_no,total_cents").sort("-created_at").limit(20));returnMap.of("orders",orders);// the next calls get this answer from the cache, until the orders change
valorders=g.sys.db.getAll(GetAll("mongodb","shop","orders").find(mapOf("status"to"PAID")).select("order_no,total_cents").sort("-created_at").limit(20),)returnmapOf("orders"toorders)// the next calls get this answer from the cache, until the orders change
An answer which must differ for every call (a captcha, a time) needs enableCaching: false.
The sample Get Captcha draws an image with the npm package svg-captcha (add it under Sandbox settings › packages) and keeps only its answer, in the cache, for three minutes :
import*asTfrom'types';constsvgCaptcha=require('svg-captcha');asyncfunctionmain(g:T.IAMGlobal){constcaptcha=svgCaptcha.create({size:6,noise:2});// The key names the captcha, nothing in it gives the answer.constkey=Date.now().toString(36)+'-'+Math.random().toString(36).substring(2,12);awaitg.sys.cache.setKey('captcha:'+key,captcha.text,180);// only the answer, only on the server, 180 secondsreturn{key,svg:captcha.data,expiresInSeconds:180};}module.exports=main;
In another language, draw the image with a package of its registry and keep the answer the same way : g.sys.cache.set_key(key, text, 180) in Python, $g->sys->cache->setKey in PHP, g.Sys.Cache.SetKey in C# and Go, g.sys.cache.set_key(…, Some(180)) in Rust, g.sys.cache.setKey in Java and Kotlin. The login which gets the answer reads it, removes it at once (one try per image : a solved image can not be sent twice), and compares :
constkey='captcha:'+String(g.req.body.captchaKey||'');constexpected=awaitg.sys.cache.getKey(key);if(!expected)throw[{code:400,message:'Captcha expired, please refresh it'}];awaitg.sys.cache.removeKey(key);// one try per imageif(String(expected).toLowerCase()!==String(g.req.body.captcha||'').trim().toLowerCase()){throw[{code:400,message:'Invalid captcha'}];}return{solved:true};// then the login itself : see Sign in
key='captcha:'+str(g.req.body.captchaKeyor'')expected=awaitg.sys.cache.get_key(key)ifnotexpected:raiseErrors('Captcha expired, please refresh it',code=400)awaitg.sys.cache.remove_key(key)# one try per imageifstr(expected).lower()!=str(g.req.body.captchaor'').strip().lower():raiseErrors('Invalid captcha',code=400)return{'solved':True}# then the login itself : see Sign in
varkey="captcha:"+g.Req.Body.GetString("captchaKey","");varexpected=awaitg.Sys.Cache.GetKey(key);if(string.IsNullOrEmpty(expected))thrownewAmErrors("Captcha expired, please refresh it",400);awaitg.Sys.Cache.RemoveKey(key);// one try per imageif(!string.Equals(expected,g.Req.Body.GetString("captcha","").Trim(),StringComparison.OrdinalIgnoreCase)){thrownewAmErrors("Invalid captcha",400);}returnnew{solved=true};// then the login itself : see Sign in
import"strings"key:="captcha:"+g.Req.Body().String("captchaKey")expected,err:=g.Sys.Cache.GetKey(key)iferr!=nil{returnnil,err}ifexpected==nil||*expected==""{returnnil,am.Fail(400,"Captcha expired, please refresh it")}if_,err:=g.Sys.Cache.RemoveKey(key);err!=nil{// one try per imagereturnnil,err}if!strings.EqualFold(*expected,strings.TrimSpace(g.Req.Body().String("captcha"))){returnnil,am.Fail(400,"Invalid captcha")}returnam.M{"solved":true},nil// then the login itself : see Sign in
letkey=format!("captcha:{}",g.req.body()["captchaKey"].as_str().unwrap_or_default());letSome(expected)=g.sys.cache.get_key(&key)?.filter(|text|!text.is_empty())else{returnErr(am::fail(400,"Captcha expired, please refresh it"));};g.sys.cache.remove_key(&key)?;// one try per imageletanswer=g.req.body()["captcha"].as_str().unwrap_or_default().trim().to_lowercase();ifexpected.to_lowercase()!=answer{returnErr(am::fail(400,"Invalid captcha"));}Ok(json!({"solved":true}))// then the login itself : see Sign in
Stringkey="captcha:"+g.req.getBody().getString("captchaKey","");Stringexpected=g.sys.cache.getKey(key);if(expected==null||expected.isEmpty())thrownewAmErrors("Captcha expired, please refresh it",400);g.sys.cache.removeKey(key);// one try per imageif(!expected.equalsIgnoreCase(g.req.getBody().getString("captcha","").trim())){thrownewAmErrors("Invalid captcha",400);}returnMap.of("solved",true);// then the login itself : see Sign in
valkey="captcha:"+g.req.body.getString("captchaKey","")valexpected=g.sys.cache.getKey(key)if(expected.isNullOrEmpty())throwAmErrors("Captcha expired, please refresh it",400)g.sys.cache.removeKey(key)// one try per imageif(!expected.equals(g.req.body.getString("captcha","").trim(),ignoreCase=true)){throwAmErrors("Invalid captcha",400)}returnmapOf("solved"totrue)// then the login itself : see Sign in
Both APIs are IS_PUBLIC (the login screen calls them before anybody has a token) and enableCaching: false.
The messages of the captcha say what failed, so that the screen fetches a new image ; the message of the credentials stays one for every failure.
g.sys.system.emitEventWS sends the data to the clients subscribed to the WebSocket event whose criteria equal it : one message per person, no broadcast room. The event default/orders-updated is a WebSocket event of a new account.
constemail=String(g.req.body.email||'');if(!email)throw[{code:400,message:'Please provide email.'}];// The clients which subscribed with the criteria { email: '<this email>' } get it.awaitg.sys.system.emitEventWS('default/orders-updated',{email,type:'order-updated',message:'Your order was shipped.'});return{emitted:true,email};
email=str(g.req.body.emailor'')ifnotemail:raiseErrors('Please provide email.',code=400)# The clients which subscribed with the criteria { email: '<this email>' } get it.awaitg.sys.system.emit_event_ws('default/orders-updated',{'email':email,'type':'order-updated','message':'Your order was shipped.'})return{'emitted':True,'email':email}
varemail=g.Req.Body.GetString("email","");if(email=="")thrownewAmErrors("Please provide email.",400);// The clients which subscribed with the criteria { email: '<this email>' } get it.awaitg.Sys.System.EmitEventWS("default/orders-updated",newJsonObject{["email"]=email,["type"]="order-updated",["message"]="Your order was shipped."});returnnew{emitted=true,email};
email:=g.Req.Body().String("email")ifemail==""{returnnil,am.Fail(400,"Please provide email.")}// The clients which subscribed with the criteria { email: '<this email>' } get it.if_,err:=g.Sys.System.EmitEventWS("default/orders-updated",am.M{"email":email,"type":"order-updated","message":"Your order was shipped."});err!=nil{returnnil,err}returnam.M{"emitted":true,"email":email},nil
letemail=g.req.body()["email"].as_str().unwrap_or_default().to_owned();ifemail.is_empty(){returnErr(am::fail(400,"Please provide email."));}// The clients which subscribed with the criteria { email: '<this email>' } get it.g.sys.system.emit_event_ws("default/orders-updated",json!({"email":email,"type":"order-updated","message":"Your order was shipped."}))?;Ok(json!({"emitted":true,"email":email}))
Stringemail=g.req.getBody().getString("email","");if(email.isEmpty())thrownewAmErrors("Please provide email.",400);// The clients which subscribed with the criteria { email: '<this email>' } get it.g.sys.system.emitEventWS("default/orders-updated",Map.of("email",email,"type","order-updated","message","Your order was shipped."));returnMap.of("emitted",true,"email",email);
valemail=g.req.body.getString("email","")if(email.isEmpty())throwAmErrors("Please provide email.",400)// The clients which subscribed with the criteria { email: '<this email>' } get it.g.sys.system.emitEventWS("default/orders-updated",mapOf("email"toemail,"type"to"order-updated","message"to"Your order was shipped."))returnmapOf("emitted"totrue,"email"toemail)
The page of the customer subscribes once it is connected :
queryByStream hands the rows to a function one by one : the memory of the API stays the same whatever the size of the table. This API writes the paid orders into a CSV file, then sends it.
import*asTfrom'types';import*asfsfrom'fs';import*aspathfrom'path';asyncfunctionmain(g:T.IAMGlobal){awaitfs.promises.mkdir(path.join(__dirname,'uploads'),{recursive:true});constfile=fs.createWriteStream(path.join(__dirname,'uploads','orders.csv'));constcsv=(value:any)=>'"'+String(value??'').replace(/"/g,'""')+'"';file.write('order_no,status,total_cents\n');awaitg.sys.db.queryByStream({instance:'mongodb',database:'shop',collection:'orders',find:{status:'PAID'},select:'order_no,status,total_cents',},(order:any)=>{file.write([order.order_no,order.status,order.total_cents].map(csv).join(',')+'\n');});awaitnewPromise(done=>file.end(done));return{__am__downloadFilePath:'orders.csv',// its name in the folder uploads__am__downloadFolderFileName:'paid-orders.csv',// the name the file is saved under__am__cleanupFileOrFolderPaths:'orders.csv',// removed once it is sent};}module.exports=main;
importcsvimportosfromamimportG,Queryasyncdefmain(g:G):os.makedirs('uploads',exist_ok=True)withopen(os.path.join('uploads','orders.csv'),'w',newline='',encoding='utf-8')asfile:out=csv.writer(file)out.writerow(['order_no','status','total_cents'])defwrite(order):out.writerow([order.get('order_no'),order.get('status'),order.get('total_cents')])awaitg.sys.db.query_by_stream(Query('mongodb','shop','orders',find={'status':'PAID'},select='order_no,status,total_cents'),write,)return{'__am__downloadFilePath':'orders.csv',# its name in the folder uploads'__am__downloadFolderFileName':'paid-orders.csv',# the name the file is saved under'__am__cleanupFileOrFolderPaths':'orders.csv',# removed once it is sent}
<?phpfunctionmain(AmG$g){if(!is_dir('uploads')){mkdir('uploads',0777,true);}$file=fopen('uploads/orders.csv','w');fputcsv($file,['order_no','status','total_cents']);$g->sys->db->queryByStream(newQuery('mongodb','shop','orders',find:['status'=>'PAID'],select:'order_no,status,total_cents'),function($order)use($file){fputcsv($file,[$order->order_no??'',$order->status??'',$order->total_cents??'']);},);fclose($file);return['__am__downloadFilePath'=>'orders.csv',// its name in the folder uploads'__am__downloadFolderFileName'=>'paid-orders.csv',// the name the file is saved under'__am__cleanupFileOrFolderPaths'=>'orders.csv',// removed once it is sent];}
usingSystem.IO;filestaticclassApi{publicstaticasyncTask<object?>Main(Gg){Directory.CreateDirectory("uploads");staticstringCsv(JsonNode?value)=>"\""+(value?.ToString()??"").Replace("\"","\"\"")+"\"";using(varfile=newStreamWriter(Path.Combine("uploads","orders.csv"))){file.WriteLine("order_no,status,total_cents");awaitg.Sys.Db.QueryByStream(newQuery("mongodb","shop","orders"){Find=newJsonObject{["status"]="PAID"},Select="order_no,status,total_cents",},order=>file.WriteLine(string.Join(",",Csv(order["order_no"]),Csv(order["status"]),Csv(order["total_cents"]))));}returnnewJsonObject{["__am__downloadFilePath"]="orders.csv",// its name in the folder uploads["__am__downloadFolderFileName"]="paid-orders.csv",// the name the file is saved under["__am__cleanupFileOrFolderPaths"]="orders.csv",// removed once it is sent};}}
packagecodeimport("encoding/csv""fmt""os""path/filepath""apimaker/am")funcMain(g*am.G)(any,error){iferr:=os.MkdirAll("uploads",0o755);err!=nil{returnnil,err}file,err:=os.Create(filepath.Join("uploads","orders.csv"))iferr!=nil{returnnil,err}deferfile.Close()out:=csv.NewWriter(file)text:=func(valueany)string{ifvalue==nil{return""}returnfmt.Sprint(value)}iferr:=out.Write([]string{"order_no","status","total_cents"});err!=nil{returnnil,err}iferr:=g.Sys.Db.QueryByStream(am.Query{Instance:"mongodb",Database:"shop",Collection:"orders",Find:am.M{"status":"PAID"},Select:"order_no,status,total_cents",},func(orderam.M)error{returnout.Write([]string{text(order.Get("order_no")),text(order.Get("status")),text(order.Get("total_cents"))})});err!=nil{returnnil,err}out.Flush()iferr:=out.Error();err!=nil{returnnil,err}returnam.M{"__am__downloadFilePath":"orders.csv",// its name in the folder uploads"__am__downloadFolderFileName":"paid-orders.csv",// the name the file is saved under"__am__cleanupFileOrFolderPaths":"orders.csv",// removed once it is sent},nil}
usestd::io::Write;useam::{Errors,G,Query,Value,json};fncsv(value:&Value)->String{lettext=matchvalue{Value::Null=>String::new(),Value::String(text)=>text.clone(),other=>other.to_string(),};format!("\"{}\"",text.replace('"',"\"\""))}pubfnmain(g:&mutG)->Result<Value,Errors>{std::fs::create_dir_all("uploads")?;letmutfile=std::io::BufWriter::new(std::fs::File::create("uploads/orders.csv")?);writeln!(file,"order_no,status,total_cents")?;g.sys.db.query_by_stream(Query::new("mongodb","shop","orders").find(json!({"status":"PAID"})).select("order_no,status,total_cents"),|order|{writeln!(file,"{},{},{}",csv(&order["order_no"]),csv(&order["status"]),csv(&order["total_cents"]))?;Ok(())},)?;file.flush()?;Ok(json!({"__am__downloadFilePath":"orders.csv",// its name in the folder uploads"__am__downloadFolderFileName":"paid-orders.csv",// the name the file is saved under"__am__cleanupFileOrFolderPaths":"orders.csv",// removed once it is sent}))}
importam.G;importam.Query;importjava.io.PrintWriter;importjava.nio.file.Files;importjava.nio.file.Path;importjava.util.Map;classApi{staticStringcsv(Objectvalue){return"\""+(value==null?"":value.toString()).replace("\"","\"\"")+"\"";}staticObjectmain(Gg)throwsException{Files.createDirectories(Path.of("uploads"));try(PrintWriterfile=newPrintWriter(Files.newBufferedWriter(Path.of("uploads","orders.csv")))){file.println("order_no,status,total_cents");g.sys.db.queryByStream(newQuery("mongodb","shop","orders").find(Map.of("status","PAID")).select("order_no,status,total_cents"),order->file.println(csv(order.get("order_no"))+","+csv(order.get("status"))+","+csv(order.get("total_cents"))));}returnMap.of("__am__downloadFilePath","orders.csv",// its name in the folder uploads"__am__downloadFolderFileName","paid-orders.csv",// the name the file is saved under"__am__cleanupFileOrFolderPaths","orders.csv");// removed once it is sent}}
importam.Gimportam.Queryimportjava.io.Filefuncsv(value:Any?)="\""+(value?.toString()?:"").replace("\"","\"\"")+"\""funmain(g:G):Any?{File("uploads").mkdirs()File("uploads","orders.csv").printWriter().use{file->file.println("order_no,status,total_cents")g.sys.db.queryByStream(Query("mongodb","shop","orders").find(mapOf("status"to"PAID")).select("order_no,status,total_cents"),){order->file.println(csv(order["order_no"])+","+csv(order["status"])+","+csv(order["total_cents"]))}}returnmapOf("__am__downloadFilePath"to"orders.csv",// its name in the folder uploads"__am__downloadFolderFileName"to"paid-orders.csv",// the name the file is saved under"__am__cleanupFileOrFolderPaths"to"orders.csv",// removed once it is sent)}
The samples Collection Copy Stream and Database Copy Stream of a new account copy a table, or every table of a database, the same way : g.sys.db.gen.queryByStreamGen reads the rows without a schema, and the function saves them by batches of 1,000 with g.sys.db.gen.saveSingleOrMultipleGen, waiting for each batch before the next rows come.
The samples S3 Upload, S3 Download, S3 List and Image Upload use the npm package @aws-sdk/client-s3 (and sharp to make the images smaller), with the keys of the bucket in the secret (s3.bucket, s3.region, s3.accessKeyId, s3.secretAccessKey, and s3.endpoint for MinIO, R2 or Spaces). The parts of API Maker are the same in every language : the keys from g.sys.system.getSecret('s3'), the uploaded file in g.req.body.files (see Files), and a download through the folder uploads :
import*asTfrom'types';import*asfsfrom'fs';import*aspathfrom'path';import{S3Client,GetObjectCommand}from'@aws-sdk/client-s3';asyncfunctionmain(g:T.IAMGlobal):Promise<T.IDownloadResponse>{consts3=awaitg.sys.system.getSecret('s3');if(!s3||!s3.bucket)thrownewError('S3 secret is not configured.');constclient=newS3Client({region:s3.region,endpoint:s3.endpoint||undefined,forcePathStyle:!!s3.endpoint,credentials:{accessKeyId:s3.accessKeyId,secretAccessKey:s3.secretAccessKey},});constkey=String(g.req.query?.key||'');if(!key)thrownewError('Query param "key" is required.');// The object is written into the folder uploads, the one folder files are sent from.constobj=awaitclient.send(newGetObjectCommand({Bucket:s3.bucket,Key:key}));constname=path.basename(key);awaitnewPromise<void>((resolve,reject)=>{constout=fs.createWriteStream(path.join(__dirname,'uploads',name));(obj.BodyasNodeJS.ReadableStream).pipe(out);out.on('finish',()=>resolve());out.on('error',reject);});return{__am__downloadFilePath:name,// in the folder uploads__am__downloadFolderFileName:name,// the name the file is saved under__am__cleanupFileOrFolderPaths:name,// removed once it is sent};}module.exports=main;
In another language : the SDK of AWS for it (boto3 for Python, aws/aws-sdk-php, AWSSDK.S3 for C#, github.com/aws/aws-sdk-go-v2/service/s3, aws-sdk-s3 for Rust, software.amazon.awssdk:s3 for Java and Kotlin), added to the packages of its language in Sandbox settings.