From eef545ecaa978e732c85eda46f876085a5eb4724 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Tue, 1 Oct 2024 20:03:08 -0300 Subject: [PATCH 01/57] migrate from local repo --- .gitignore | 2 ++ backend/main.mo | 56 ++++++++++++++++++++++++++++++++++++++ backend/types.mo | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ dfx.json | 15 ++++++++--- mops.toml | 3 +++ 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 backend/main.mo create mode 100644 backend/types.mo create mode 100644 mops.toml diff --git a/.gitignore b/.gitignore index a4aeb2c..52f386c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ src/declarations # Azle .azle + +.mops diff --git a/backend/main.mo b/backend/main.mo new file mode 100644 index 0000000..af5dfbe --- /dev/null +++ b/backend/main.mo @@ -0,0 +1,56 @@ +import Map "mo:map/Map"; +import { phash } "mo:map/Map"; +import Types "types"; + +actor { + + type User = Types.User; + type UserKind = Types.UserKind; + type ResultSignUp = { #Ok : User; #Err : User }; + + stable let users = Map.new(); + + ///////////////////////////////////// Update functions //////////////////////////////////////// + + public shared ({ caller }) func signUp(data: Types.SignUpData) : async ResultSignUp { + let user = Map.get(users, phash, caller); + switch user { + case (?User) { #Err(User) }; + case null { + let newUser: User = { + userKind: [UserKind] = []; + name = data.name; + email = data.email; + verified = false; + score = 0; + avatar = data.avatar; + }; + ignore Map.put(users, phash, caller, newUser); + #Ok(newUser); + }; + }; + }; + + public shared query ({ caller }) func logIn(): async {#Ok: User; #Err} { + let user = Map.get(users, phash, caller); + switch user { + case null { #Err }; + case ( ?u ) { #Ok(u)} + }; + }; + + //////////////////////////////// CRUD Data User /////////////////////////////////// + // TODO + /////////////////////////////////////////////////////////////////////////////////// + + /////////////////////////////// Verification process ////////////////////////////// + // TODO + /////////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////// CRUD Listing ///////////////////////////////////// + + + + + +}; diff --git a/backend/types.mo b/backend/types.mo new file mode 100644 index 0000000..1d47c82 --- /dev/null +++ b/backend/types.mo @@ -0,0 +1,70 @@ +import Map "mo:map/Map"; + +module { + ////////////////////////////// Users ///////////////////////////////////// + + public type SignUpData = { + name: Text; + email: ?Text; + avatar: ?Blob; + }; + + public type User = { + // id: Nat; + userKind: [UserKind]; //Un user Host puede ser ademas un User + name: Text; + email: ?Text; + verified: Bool; + score: Nat; + avatar: ?Blob; + + }; + + public type UserKind = { + #Initial; + #Guest: [ReviewsId]; + #Host: [ListingId]; + }; + + ///////////////////////////////// Listing ///////////////////////////////// + + public type Listing = { + calendarId: Nat; + id: Text; // Example L234324 + address: Text; + price: Price; + kind: ListingKind; + }; + + type ListingId = Text; + + public type ListingKind = { + #House; + #Hotel_room; + #RoomWithSharedSpaces: [Rules]; //Hostels/Pensiones + }; + + public type Price = { + #PerNight: Nat; + #PerWeek: Nat; + #CustomPeriod: Map.Map // Cantidad de dias/Precio. Aplicable a partir de 3 por ejemplo + }; + + public type Rules = { // Ejemplo de Rule: {key = "Horarios"; value = "Sin ruidos molestos entre las 22:00 y las 8:00"} + key: Text; + value: Text + }; + + ///////////////////////////////// Reservations ///////////////////////////// + + public type Reservations = { + + }; + + + + type ReviewsId = Text; + +} + + \ No newline at end of file diff --git a/dfx.json b/dfx.json index a02af53..bdf0e7d 100644 --- a/dfx.json +++ b/dfx.json @@ -11,11 +11,15 @@ } }, "frontend": { - "dependencies": ["test"], + "dependencies": [ + "test" + ], "frontend": { "entrypoint": "frontend/build/index.html" }, - "source": ["frontend/build"], + "source": [ + "frontend/build" + ], "type": "assets" }, "internet-identity": { @@ -29,5 +33,10 @@ } } } + }, + "defaults": { + "build": { + "packtool": "mops sources" + } } -} +} \ No newline at end of file diff --git a/mops.toml b/mops.toml new file mode 100644 index 0000000..bca6fc7 --- /dev/null +++ b/mops.toml @@ -0,0 +1,3 @@ +[dependencies] +base = "0.11.1" +map = "9.0.1" \ No newline at end of file From 4172f663a260064b5e5948a3e5fc180e7000da2f Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 3 Oct 2024 16:59:17 -0300 Subject: [PATCH 02/57] Anotaciones, publishListing --- Anotaciones.md | 6 ++++ backend/main.mo | 72 ++++++++++++++++++++++++++++++++++++++++++++---- backend/types.mo | 54 +++++++++++++++++++++++++++++++----- 3 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 Anotaciones.md diff --git a/Anotaciones.md b/Anotaciones.md new file mode 100644 index 0000000..9313a34 --- /dev/null +++ b/Anotaciones.md @@ -0,0 +1,6 @@ +### Verificacion de usuarios: + +Se deja implementada una funcion para evaluar el estado de verificacion de un usuario **`userIsVerificated()`** mediante la cual, en un contexto de produccion, se permitirá la publicacion de espacios de alojamiento solo cuando el usuario publicante esté verificado mediante algun tipo de procedimiento KYC. +En un contexto de MVP todos los usuarios serán inicializados por defecto como verificados. + +--- diff --git a/backend/main.mo b/backend/main.mo index af5dfbe..4ec56f6 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -1,21 +1,37 @@ import Map "mo:map/Map"; -import { phash } "mo:map/Map"; +import { phash; nhash } "mo:map/Map"; +import Principal "mo:base/Principal"; import Types "types"; actor { type User = Types.User; type UserKind = Types.UserKind; - type ResultSignUp = { #Ok : User; #Err : User }; + type SignUpResult = Types.SignUpResult; + type Calendar = Types.Calendar; + type ListingId = Nat; + type ListingDataInit = Types.ListingDataInit; + type Listing = Types.Listing; + + type PublishResult = {#Ok: ListingId; #Err: Text}; + stable let users = Map.new(); + stable let listings = Map.new(); + // stable let calendars = Map.new(); + + stable var lastListingId = 0; + ///////////////////////////////////// Update functions //////////////////////////////////////// - public shared ({ caller }) func signUp(data: Types.SignUpData) : async ResultSignUp { + public shared ({ caller }) func signUp(data: Types.SignUpData) : async SignUpResult { + if(Principal.isAnonymous(caller)){ + return #Err("User not autenthicated") + }; let user = Map.get(users, phash, caller); switch user { - case (?User) { #Err(User) }; + case (?User) { #Err("User already exists") }; case null { let newUser: User = { userKind: [UserKind] = []; @@ -45,11 +61,57 @@ actor { /////////////////////////////// Verification process ////////////////////////////// // TODO + + func userIsVerificated(u: Principal): Bool { + let user = Map.get(users, phash, u); + switch user{ + case null { false }; + case (?user) { user.verified}; + }; + }; /////////////////////////////////////////////////////////////////////////////////// //////////////////////////////// CRUD Listing ///////////////////////////////////// - + public shared ({ caller }) func publishListing(data: ListingDataInit): async PublishResult { + + let user = Map.get(users, phash, caller); + switch user { + case null {return #Err("Usuario no registrado")}; + case (?User){ + if(not userIsVerificated(caller)){ return #Err("Usuario no verificado") }; + lastListingId += 1; + let newListing: Listing = { + owner = caller; + id = lastListingId; + calendar: Calendar = {reservations = []}; + address = data.address; + price = data.price; + kind = data.kind; + }; + + ignore Map.put(listings, nhash, lastListingId, newListing); + + + } + }; + + + + + + /* init + public type ListingDataInit = { + owner: Principal; + address: Text; + price: Price; + kind: ListingKind; + }; + */ + + + + } diff --git a/backend/types.mo b/backend/types.mo index 1d47c82..9c5f352 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -26,17 +26,36 @@ module { #Host: [ListingId]; }; + public type SignUpResult = { #Ok : User; #Err : Text }; + // type GetProfileError = { + // #userNotAuthenticated; + // #profileNotFound; + // }; + + // type CreateProfileError = { + // #profileAlreadyExists; + // #userNotAuthenticated; + // }; + ///////////////////////////////// Listing ///////////////////////////////// + public type ListingDataInit = { + // owner: Principal; + address: Text; + price: Price; + kind: ListingKind; + }; + public type Listing = { - calendarId: Nat; - id: Text; // Example L234324 + id: Nat; // Example L234324 + owner: Principal; + calendar: Calendar; address: Text; price: Price; kind: ListingKind; }; - type ListingId = Text; + public type ListingId = Text; public type ListingKind = { #House; @@ -47,7 +66,7 @@ module { public type Price = { #PerNight: Nat; #PerWeek: Nat; - #CustomPeriod: Map.Map // Cantidad de dias/Precio. Aplicable a partir de 3 por ejemplo + #CustomPeriod: [{dais: Nat; price: Nat}]; }; public type Rules = { // Ejemplo de Rule: {key = "Horarios"; value = "Sin ruidos molestos entre las 22:00 y las 8:00"} @@ -57,13 +76,34 @@ module { ///////////////////////////////// Reservations ///////////////////////////// - public type Reservations = { + public type Reservation = { + checkIn: Int; //Timestamp + checkOut: Int; + applicant: Principal; + guest: Text; + }; + type ReviewsId = Text; + + type Node = { + value: T; + rigth: ?Node; + left: ?Node; }; - + // public func initTree(value: T): Node { + // {value; rigth = null; left = null}; + // }; - type ReviewsId = Text; + // public func put(value: T, f: (T,T) -> {#before; #after}): {#before; #after} { + + // }; + + public type Calendar = { + //LinstingId: Nat; + reservations: [Reservation]; //TODO La lista debe estar ordenada y sin solapamientos ver metodos de inserción + + } } From 3c8b5381a6fb2b7bcecdae39f9f074bc0f66879a Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 3 Oct 2024 17:12:40 -0300 Subject: [PATCH 03/57] UpdatePrices --- backend/main.mo | 87 ++++++++++++++++++++++++++++++++++-------------- backend/types.mo | 9 +++-- 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 4ec56f6..dca2a3a 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -1,3 +1,4 @@ +import Prim "mo:⛔"; import Map "mo:map/Map"; import { phash; nhash } "mo:map/Map"; import Principal "mo:base/Principal"; @@ -9,9 +10,10 @@ actor { type UserKind = Types.UserKind; type SignUpResult = Types.SignUpResult; type Calendar = Types.Calendar; - type ListingId = Nat; + type ListingId = Types.ListingId; type ListingDataInit = Types.ListingDataInit; type Listing = Types.Listing; + type UpdateResult = Types.UpdateResult; type PublishResult = {#Ok: ListingId; #Err: Text}; @@ -73,13 +75,16 @@ actor { //////////////////////////////// CRUD Listing ///////////////////////////////////// - public shared ({ caller }) func publishListing(data: ListingDataInit): async PublishResult { - + public shared ({ caller }) func publishListing(data: ListingDataInit): async PublishResult { let user = Map.get(users, phash, caller); switch user { - case null {return #Err("Usuario no registrado")}; - case (?User){ - if(not userIsVerificated(caller)){ return #Err("Usuario no verificado") }; + case null { + return #Err("Usuario no registrado"); + }; + case (?user){ + if(not userIsVerificated(caller)){ + return #Err("Usuario no verificado"); + }; lastListingId += 1; let newListing: Listing = { owner = caller; @@ -89,29 +94,61 @@ actor { price = data.price; kind = data.kind; }; - + var updateListingArray: [ListingId] = []; + var notPrevious = true; + var position = 0; + var i = 0; + while(i < user.userKind.size()){ + switch (user.userKind[i]){ + case(#Host(listingIdArray)){ + notPrevious := false; + position := i; + updateListingArray := Prim.Array_tabulate( + listingIdArray.size() + 1, + func x { + if(x != listingIdArray.size()){ + listingIdArray[x]; + } + else {newListing.id} + } + ) + }; + case(_){}; + }; + i += 1; + }; + if(notPrevious){ updateListingArray := [newListing.id] }; + let updateKinds = Prim.Array_tabulate( + user.userKind.size() + (if(notPrevious){ 1 } else { 0 }), + func i { if(i == position) { + #Host(updateListingArray) + } + else { + user.userKind[i] + } + } + ); ignore Map.put(listings, nhash, lastListingId, newListing); - - + ignore Map.put(users, phash, caller, {user with userKind = updateKinds}); + return #Ok(newListing.id) } }; + }; - - - - - /* init - public type ListingDataInit = { - owner: Principal; - address: Text; - price: Price; - kind: ListingKind; - }; - */ - - - - } + public shared ({ caller }) func updatePrices(id: ListingId, updatedPrices: Types.Price): async UpdateResult{ + let listing = Map.get(listings, nhash, id); + switch listing { + case null { + return #Err("Error Listing ID"); + }; + case (?listing) { + if(listing.owner != caller){ return #Err("Unauthorized caller") }; + ignore Map.put(listings, nhash, id, {listing with prices = updatedPrices}); + return #Ok + }; + } + }; + diff --git a/backend/types.mo b/backend/types.mo index 9c5f352..61d0d87 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -1,4 +1,3 @@ -import Map "mo:map/Map"; module { ////////////////////////////// Users ///////////////////////////////////// @@ -46,6 +45,7 @@ module { kind: ListingKind; }; + public type Listing = { id: Nat; // Example L234324 owner: Principal; @@ -55,7 +55,12 @@ module { kind: ListingKind; }; - public type ListingId = Text; + public type ListingId = Nat; + + public type UpdateResult = { + #Ok; + #Err: Text; + }; public type ListingKind = { #House; From 7a3d177fcdb72b695abda33bd0f57119f63d3d92 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 3 Oct 2024 17:57:59 -0300 Subject: [PATCH 04/57] getListingPreviews --- backend/main.mo | 18 ++++++++++++++++++ backend/types.mo | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/backend/main.mo b/backend/main.mo index dca2a3a..cce4520 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -14,6 +14,7 @@ actor { type ListingDataInit = Types.ListingDataInit; type Listing = Types.Listing; type UpdateResult = Types.UpdateResult; + type ListingPreview = Types.ListingPreview; type PublishResult = {#Ok: ListingId; #Err: Text}; @@ -90,6 +91,7 @@ actor { owner = caller; id = lastListingId; calendar: Calendar = {reservations = []}; + photos = data.photos; address = data.address; price = data.price; kind = data.kind; @@ -135,6 +137,22 @@ actor { }; }; + public func getListingPreviews(): async [ListingPreview] { + let values = Map.toArray(listings); + Prim.Array_tabulate( + values.size(), + func x { + { + id = values[x].1.id; + address = values[x].1.address; + photos = values[x].1.photos; + price =values[x].1.price; + } + } + + ); + }; + public shared ({ caller }) func updatePrices(id: ListingId, updatedPrices: Types.Price): async UpdateResult{ let listing = Map.get(listings, nhash, id); switch listing { diff --git a/backend/types.mo b/backend/types.mo index 61d0d87..741e752 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -43,6 +43,7 @@ module { address: Text; price: Price; kind: ListingKind; + photos: [Blob]; }; @@ -51,10 +52,18 @@ module { owner: Principal; calendar: Calendar; address: Text; + photos: [Blob]; price: Price; kind: ListingKind; }; + public type ListingPreview = { + id: Nat; + address: Text; + photos: [Blob]; + price: Price; + }; + public type ListingId = Nat; public type UpdateResult = { From 760d03e840913592e6d6e5b7269561b6fe633332 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 3 Oct 2024 18:02:19 -0300 Subject: [PATCH 05/57] getListingById --- backend/main.mo | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/main.mo b/backend/main.mo index cce4520..b9f5a75 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -153,6 +153,16 @@ actor { ); }; + public func getListingById(id: ListingId): async {#Ok: Listing; #Err: Text} { + let listing = Map.get(listings, nhash, id); + return switch listing { + case null { #Err("Error Listing ID")}; + case (?listing) { + #Ok(listing); + }; + } + }; + public shared ({ caller }) func updatePrices(id: ListingId, updatedPrices: Types.Price): async UpdateResult{ let listing = Map.get(listings, nhash, id); switch listing { From 254a233ecf197e4c004351c055d8db23c687619a Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 21 Oct 2024 16:26:07 -0300 Subject: [PATCH 06/57] CRUD Housing, getPaginateHousing, getHousingById --- .vscode/settings.json | 5 +- backend/main.mo | 191 +++++++++++++++++++++++++++++------------- backend/types.mo | 30 +++---- comandosCLI.md | 14 ++++ dfx.json | 8 ++ 5 files changed, 173 insertions(+), 75 deletions(-) create mode 100644 comandosCLI.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 65a1965..0574f25 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[motoko]": { + "editor.defaultFormatter": "dfinity-foundation.vscode-motoko" + } } diff --git a/backend/main.mo b/backend/main.mo index b9f5a75..41c5e78 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -1,29 +1,36 @@ import Prim "mo:⛔"; import Map "mo:map/Map"; +import Set "mo:map/Set"; import { phash; nhash } "mo:map/Map"; import Principal "mo:base/Principal"; +import Buffer "mo:base/Buffer"; +import Blob "mo:base/Blob"; import Types "types"; -actor { +shared ({ caller }) actor class Triourism () = this { type User = Types.User; type UserKind = Types.UserKind; type SignUpResult = Types.SignUpResult; type Calendar = Types.Calendar; - type ListingId = Types.ListingId; - type ListingDataInit = Types.ListingDataInit; - type Listing = Types.Listing; + type HousingId = Types.HousingId; + type HousingDataInit = Types.HousingDataInit; + type Housing = Types.Housing; type UpdateResult = Types.UpdateResult; - type ListingPreview = Types.ListingPreview; + type HousingPreview = Types.HousingPreview; + type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; next: Bool}; #Err: Text}; - type PublishResult = {#Ok: ListingId; #Err: Text}; - + type PublishResult = {#Ok: HousingId; #Err: Text}; + stable let DEPLOYER = caller; + + stable let admins = Set.new(); + ignore Set.put(admins, phash, caller); stable let users = Map.new(); - stable let listings = Map.new(); - // stable let calendars = Map.new(); + stable let housings = Map.new(); + // stable let calendars = Map.new(); - stable var lastListingId = 0; + stable var lastHousingId = 0; ///////////////////////////////////// Update functions //////////////////////////////////////// @@ -40,7 +47,7 @@ actor { userKind: [UserKind] = []; name = data.name; email = data.email; - verified = false; + verified = true; score = 0; avatar = data.avatar; }; @@ -62,8 +69,35 @@ actor { // TODO /////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////// Verification process ////////////////////////////// - // TODO + ///////////////////////////// Private functions /////////////////////////////////// + func isAdmin(p: Principal): Bool { Set.has(admins, phash, p) }; + + func isUser(p: Principal): Bool { Map.has(users, phash, p)}; + + + /////////////////////////// Manage admins functions ///////////////////////////////// + + public shared ({ caller }) func addAdmin(p: Principal): async {#Ok; #Err} { + if(not isAdmin(caller)){ + #Err + } else{ + ignore Set.put(admins, phash, p); + #Ok + } + }; + + public shared ({ caller }) func removeAdmin(p: Principal): async {#Ok; #Err} { + if(caller != DEPLOYER){ + #Err; + } else { + ignore Set.remove(admins, phash, p); + #Ok + } + }; + + + /////////////////////////////// Verification process ////////////////////////////// + // TODO actualmente todos los usuarios se inicializan como verificados func userIsVerificated(u: Principal): Bool { let user = Map.get(users, phash, u); @@ -72,11 +106,10 @@ actor { case (?user) { user.verified}; }; }; - /////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////// CRUD Listing ///////////////////////////////////// + //////////////////////////////// CRUD Housing ///////////////////////////////////// - public shared ({ caller }) func publishListing(data: ListingDataInit): async PublishResult { + public shared ({ caller }) func publishHousing(data: HousingDataInit): async PublishResult { let user = Map.get(users, phash, caller); switch user { case null { @@ -86,32 +119,33 @@ actor { if(not userIsVerificated(caller)){ return #Err("Usuario no verificado"); }; - lastListingId += 1; - let newListing: Listing = { + lastHousingId += 1; + let newHousing: Housing = { owner = caller; - id = lastListingId; + id = lastHousingId; calendar: Calendar = {reservations = []}; - photos = data.photos; + photos: [Blob] = []; + thumbnail: Blob = ""; address = data.address; - price = data.price; + prices = data.prices; kind = data.kind; }; - var updateListingArray: [ListingId] = []; + var updateHousingArray: [HousingId] = []; var notPrevious = true; var position = 0; var i = 0; while(i < user.userKind.size()){ switch (user.userKind[i]){ - case(#Host(listingIdArray)){ + case(#Host(housingIdArray)){ notPrevious := false; position := i; - updateListingArray := Prim.Array_tabulate( - listingIdArray.size() + 1, + updateHousingArray := Prim.Array_tabulate( + housingIdArray.size() + 1, func x { - if(x != listingIdArray.size()){ - listingIdArray[x]; + if(x != housingIdArray.size()){ + housingIdArray[x]; } - else {newListing.id} + else {newHousing.id} } ) }; @@ -119,64 +153,103 @@ actor { }; i += 1; }; - if(notPrevious){ updateListingArray := [newListing.id] }; + if(notPrevious){ updateHousingArray := [newHousing.id] }; let updateKinds = Prim.Array_tabulate( user.userKind.size() + (if(notPrevious){ 1 } else { 0 }), func i { if(i == position) { - #Host(updateListingArray) + #Host(updateHousingArray) } else { user.userKind[i] } } ); - ignore Map.put(listings, nhash, lastListingId, newListing); + ignore Map.put(housings, nhash, lastHousingId, newHousing); ignore Map.put(users, phash, caller, {user with userKind = updateKinds}); - return #Ok(newListing.id) + return #Ok(newHousing.id) } }; }; - public func getListingPreviews(): async [ListingPreview] { - let values = Map.toArray(listings); - Prim.Array_tabulate( - values.size(), - func x { - { - id = values[x].1.id; - address = values[x].1.address; - photos = values[x].1.photos; - price =values[x].1.price; - } + public shared ({ caller }) func addPhotoToHousing({id: HousingId; photo: Blob}): async {#Ok; #Err: Text} { + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + #Err("Invalid Housing Id") + }; + case (?housing) { + if(housing.owner != caller){ + return #Err("The caller is not the owner of the housing") + }; + let photos = Prim.Array_tabulate( + housing.photos.size() +1, + func i = if(i < housing.photos.size()) {housing.photos[i]} else {photo} + ); + ignore Map.put(housings, nhash, id, {housing with photos}); + #Ok } - - ); + } }; - public func getListingById(id: ListingId): async {#Ok: Listing; #Err: Text} { - let listing = Map.get(listings, nhash, id); - return switch listing { - case null { #Err("Error Listing ID")}; - case (?listing) { - #Ok(listing); + public shared ({ caller }) func addThumbnailToHousing({id: HousingId; thumbnail: Blob}): async {#Ok; #Err: Text} { + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + #Err("Invalid Housing Id") }; + case (?housing) { + if(housing.owner != caller){ + return #Err("The caller is not the owner of the housing") + }; + ignore Map.put(housings, nhash, id, {housing with thumbnail}); + #Ok + } } }; - public shared ({ caller }) func updatePrices(id: ListingId, updatedPrices: Types.Price): async UpdateResult{ - let listing = Map.get(listings, nhash, id); - switch listing { + public shared ({ caller }) func updatePrices({id: HousingId; prices: [Types.Price]}): async UpdateResult{ + let housing = Map.get(housings, nhash, id); + switch housing { case null { - return #Err("Error Listing ID"); + return #Err("Error Housing ID"); }; - case (?listing) { - if(listing.owner != caller){ return #Err("Unauthorized caller") }; - ignore Map.put(listings, nhash, id, {listing with prices = updatedPrices}); + case (?housing) { + if(housing.owner != caller){ return #Err("Unauthorized caller") }; + ignore Map.put(housings, nhash, id, {housing with prices}); return #Ok }; } }; - + + + ////////////////////////////////// Getters /////////////////////////////////////////// + + public query func getHousingPaginate(page: Nat): async ResultHousingPaginate { + if(Map.size(housings) < page * 10){ + return #Err("Pagination index out of range") + }; + let values = Map.toArray(housings); + let bufferHousingPreview = Buffer.fromArray([]); + var index = page * 10; + while (index < values.size() and index < (page + 1) * 10){ + bufferHousingPreview.add(values[index].1); + index += 1; + }; + #Ok{ + array = Buffer.toArray(bufferHousingPreview); + next = ((page + 1) * 10 < values.size()) + } + }; + + public query func getHousingById(id: HousingId): async {#Ok: Housing; #Err: Text} { + let housing = Map.get(housings, nhash, id); + return switch housing { + case null { #Err("Error Housing ID")}; + case (?housing) { + #Ok(housing); + }; + } + }; diff --git a/backend/types.mo b/backend/types.mo index 741e752..b1200ff 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -22,7 +22,7 @@ module { public type UserKind = { #Initial; #Guest: [ReviewsId]; - #Host: [ListingId]; + #Host: [HousingId]; }; public type SignUpResult = { #Ok : User; #Err : Text }; @@ -36,42 +36,42 @@ module { // #userNotAuthenticated; // }; - ///////////////////////////////// Listing ///////////////////////////////// + ///////////////////////////////// Housing ///////////////////////////////// - public type ListingDataInit = { + public type HousingDataInit = { // owner: Principal; address: Text; - price: Price; - kind: ListingKind; - photos: [Blob]; + prices: [Price]; + kind: HousingKind; }; - public type Listing = { + public type Housing = { id: Nat; // Example L234324 owner: Principal; calendar: Calendar; address: Text; photos: [Blob]; - price: Price; - kind: ListingKind; + thumbnail: Blob; // Se recomienda la foto principal en tamaño reducido + prices: [Price]; + kind: HousingKind; }; - public type ListingPreview = { + public type HousingPreview = { id: Nat; address: Text; - photos: [Blob]; - price: Price; + thumbnail: Blob; + prices: [Price]; }; - public type ListingId = Nat; + public type HousingId = Nat; public type UpdateResult = { #Ok; #Err: Text; }; - public type ListingKind = { + public type HousingKind = { #House; #Hotel_room; #RoomWithSharedSpaces: [Rules]; //Hostels/Pensiones @@ -114,7 +114,7 @@ module { // }; public type Calendar = { - //LinstingId: Nat; + //HousingId: Nat; reservations: [Reservation]; //TODO La lista debe estar ordenada y sin solapamientos ver metodos de inserción } diff --git a/comandosCLI.md b/comandosCLI.md new file mode 100644 index 0000000..3a26bcf --- /dev/null +++ b/comandosCLI.md @@ -0,0 +1,14 @@ +``` +dfx deploy backend + +``` +registro de usuario sin foto: + +``` +backend signUp '(record { + name="Usuario de Prueba"; + email=opt "usuario_prueba@gmail.com"; + avatar= null} +)' + +``` diff --git a/dfx.json b/dfx.json index bdf0e7d..95b44aa 100644 --- a/dfx.json +++ b/dfx.json @@ -2,6 +2,14 @@ "version": 1, "dfx": "0.20.1", "canisters": { + "backend": { + "main": "backend/main.mo", + "type": "motoko", + "declarations": { + "output": "frontend/src/declarations/backend" + } + + }, "test": { "type": "motoko", "main": "backend/test/main.mo", From b28abc61d4c0c4aeec2d62f88b7c310f733ffa27 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 21 Oct 2024 16:35:00 -0300 Subject: [PATCH 07/57] Edit Prices, addThumbnail housting --- comandosCLI.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/comandosCLI.md b/comandosCLI.md index 3a26bcf..1b519ad 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -5,10 +5,63 @@ dfx deploy backend registro de usuario sin foto: ``` -backend signUp '(record { +dfx canister call backend signUp '(record { name="Usuario de Prueba"; email=opt "usuario_prueba@gmail.com"; avatar= null} )' +``` + +registro de usuario con foto: + +``` +dfx canister call backend signUp '(record { + name="Usuario de Prueba"; + email=opt "usuario_prueba2@gmail.com"; + avatar= opt blob "11/44/67/87/45/34/09/87/56"} +)' +``` + +publicacion de hosting + +``` +dfx canister call backend publishHousing '(record { + address = "San Mattin 555"; + prices = vec { + variant {PerNight = 50}; + variant {PerWeek = 550} + }; + kind = variant {House}} +)' +``` + +agregar foto a publicacion ``` +dfx canister call backend addPhotoToHousing '(record { + id = 1; + photo = blob "00/11/22/33/44/"} +)' +``` + +agregar foto miniatura (foto principal de tamaño reducido) + +``` +dfx canister call backend addThumbnailToHousing '(record { + id = 1; + thumbnail = blob "00/66/88/44/45/98/45/98"} +)' +``` + +actualizacion de precios + +``` +dfx canister call backend updatePrices '(record { + id = 1; + prices = vec { + variant {PerNight = 400}; + variant {PerWeek = 2800}; + } +})' +``` + From 7323e45cac4a27ce9b00d7abdef936658834b8f8 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 21 Oct 2024 17:51:06 -0300 Subject: [PATCH 08/57] loadAvatar --- backend/main.mo | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/main.mo b/backend/main.mo index 41c5e78..7ffcbf1 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -66,6 +66,17 @@ shared ({ caller }) actor class Triourism () = this { }; //////////////////////////////// CRUD Data User /////////////////////////////////// + + public shared ({ caller }) func loadAvatar(avatar: Blob): async {#Ok; #Err: Text} { + let user = Map.get(users, phash, caller); + switch user { + case null {#Err("There is no user associated with the caller")}; + case(?user) { + ignore Map.put(users, phash, caller, {user with avatar = ?avatar}); + #Ok + } + } + }; // TODO /////////////////////////////////////////////////////////////////////////////////// From 33e50de7ddf63c43a5d8046a75d35d526809953b Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Tue, 22 Oct 2024 15:43:31 -0300 Subject: [PATCH 09/57] requestReservation, calendar, etc --- Anotaciones.md | 13 ++++++++ backend/main.mo | 80 +++++++++++++++++++++++++++++++++++++++++++----- backend/types.mo | 44 ++++++++++++++------------ mops.toml | 3 +- 4 files changed, 111 insertions(+), 29 deletions(-) diff --git a/Anotaciones.md b/Anotaciones.md index 9313a34..080ee1f 100644 --- a/Anotaciones.md +++ b/Anotaciones.md @@ -4,3 +4,16 @@ Se deja implementada una funcion para evaluar el estado de verificacion de un us En un contexto de MVP todos los usuarios serán inicializados por defecto como verificados. --- + +1: Solicitud de reserva. + A: Se evalua si el la fecha de la reserva es mayor al tiempo actual mas el tiempo minimo fijado por el host, o sea si el host dice que se puede reservar con 24 horas de anticipación y el usuario quiere reserva para dentro de 10 horas, se devuelve un error. + B: Si todo va bien, el backend devuelve los datos de la solicitud mas un codigo de pago. + C: Cuando se arma la transacción en el front, el codigo de pago se pone en el campo Memo y se hace la transacción. +2: Tiempo de bloqueo configurable (40 minutos por ejemplo): + A: Durante este plaso de tiempo se marca como no disponible o pre reservado todo el rango de tiempo correspondiente a la reserva. + B: El usuario tiene tiempo en este plaso (40 minutos segun ejemplo), de proceder con el pago de confirmación. + C: Si el plaso finaliza sin que se haya concretado la confirmacion, se vuuelve a marcar como disponible. +2: Confirmación de reserva + A: Si el usuario realiza la transaccián, la cual devuelve un transaction hash, se llama a otra funcion de backend enviando el id de la reserva mas el transaction hash. + B: Mediante una consulta al Ledger correspondiente a la moneda de pago, desde el bakend se envia el transaction hash, confirmando que los datos de retorno sean los correspondientes a la transaccion solicitada (Campo memo). + C: Luego de la confirmacion y verificación se marca como ocupado en el calendario el rango de tiempo de alojamineto diff --git a/backend/main.mo b/backend/main.mo index 7ffcbf1..57e824e 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -6,23 +6,44 @@ import Principal "mo:base/Principal"; import Buffer "mo:base/Buffer"; import Blob "mo:base/Blob"; import Types "types"; - +import Int "mo:base/Int"; +import { now } "mo:base/Time"; +import Rand "mo:random/Rand"; + shared ({ caller }) actor class Triourism () = this { type User = Types.User; type UserKind = Types.UserKind; type SignUpResult = Types.SignUpResult; - type Calendar = Types.Calendar; + type CalendaryPart = Types.CalendaryPart; + type Reservation = Types.Reservation; type HousingId = Types.HousingId; type HousingDataInit = Types.HousingDataInit; type Housing = Types.Housing; - type UpdateResult = Types.UpdateResult; + type ShareableHousing = Types.ShareableHousing; type HousingPreview = Types.HousingPreview; - type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; next: Bool}; #Err: Text}; + type UpdateResult = Types.UpdateResult; + type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; next: Bool}; #Err: Text}; type PublishResult = {#Ok: HousingId; #Err: Text}; + type ReservationResult = { + #Ok: { + houstingId: HousingId; + data: Reservation; + paymentCode: Nat; + // msg: Text; + }; + #Err: Text; + }; + + // TODO revisar day, actualemnte es el timestamp de una hora especifica que delimita el comienzo del dia + + stable let DEPLOYER = caller; + let ramdomGenerator = Rand.Rand(); + + // stable var minReservationLeadTime = 24 * 60 * 60 * 1_000_000_000; // 24 horas en nanosegundos stable let admins = Set.new(); ignore Set.put(admins, phash, caller); @@ -80,11 +101,22 @@ shared ({ caller }) actor class Triourism () = this { // TODO /////////////////////////////////////////////////////////////////////////////////// - ///////////////////////////// Private functions /////////////////////////////////// + ///////////////////////////// Private functions /////////////////////////////////// func isAdmin(p: Principal): Bool { Set.has(admins, phash, p) }; func isUser(p: Principal): Bool { Map.has(users, phash, p)}; + func initCalendary(): [var CalendaryPart]{ + Prim.Array_init( + 30, + {day= 0; available = true; reservation = null} + ) + }; + + func freezeCalendar(c: [var CalendaryPart]): [CalendaryPart]{ + Prim.Array_tabulate(c.size(), func i = c[i]) + }; + /////////////////////////// Manage admins functions ///////////////////////////////// @@ -106,6 +138,8 @@ shared ({ caller }) actor class Triourism () = this { } }; + /////////////////////////// Admin functions + /////////////////////////////// Verification process ////////////////////////////// // TODO actualmente todos los usuarios se inicializan como verificados @@ -133,8 +167,9 @@ shared ({ caller }) actor class Triourism () = this { lastHousingId += 1; let newHousing: Housing = { owner = caller; + minReservationLeadTimeNanoSeg = data.minReservationLeadTime * 60 * 60 * 1_000_000_000; id = lastHousingId; - calendar: Calendar = {reservations = []}; + calendar: [var CalendaryPart] = initCalendary(); photos: [Blob] = []; thumbnail: Blob = ""; address = data.address; @@ -252,16 +287,45 @@ shared ({ caller }) actor class Triourism () = this { } }; - public query func getHousingById(id: HousingId): async {#Ok: Housing; #Err: Text} { + public query func getHousingById(id: HousingId): async {#Ok: ShareableHousing; #Err: Text} { let housing = Map.get(housings, nhash, id); return switch housing { case null { #Err("Error Housing ID")}; case (?housing) { - #Ok(housing); + #Ok({housing with calendar = freezeCalendar(housing.calendar)}); }; } }; + ///////////////////////////////// Reservations ///////////////////////////////////////// + + public shared ({ caller }) func requestReservation({id: HousingId; data: Reservation}):async ReservationResult { + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + #Err("No hay un housing asociado al id proporcionado"); + }; + case (?housing) { + if(now() + housing.minReservationLeadTimeNanoSeg < data.checkIn){ + return #Err("Las reservas se solicitan con un minimo de anticipacion de " # + Int.toText(housing.minReservationLeadTimeNanoSeg /(60 * 60 * 1_000_000_000)) # + " horas"); + }; + let responseReservation = { + houstingId = id; + data; + paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) + }; + + #Ok( responseReservation ) + }; + } + }; + + public shared ({ caller }) func confirmReservation(){}; + + + }; diff --git a/backend/types.mo b/backend/types.mo index b1200ff..c614188 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -40,6 +40,7 @@ module { public type HousingDataInit = { // owner: Principal; + minReservationLeadTime: Int; //valor en horas de anticipación para efectuar una reserva address: Text; prices: [Price]; kind: HousingKind; @@ -49,7 +50,8 @@ module { public type Housing = { id: Nat; // Example L234324 owner: Principal; - calendar: Calendar; + calendar: [var CalendaryPart]; + minReservationLeadTimeNanoSeg: Int; // valor en nanosegundos de anticipacion para efectuar una reserva address: Text; photos: [Blob]; thumbnail: Blob; // Se recomienda la foto principal en tamaño reducido @@ -57,6 +59,16 @@ module { kind: HousingKind; }; + public type ShareableHousing = { + id: Nat; + owner: Principal; + calendar: [CalendaryPart]; + photos: [Blob]; + thumbnail: Blob; + prices: [Price]; + kind: HousingKind; + }; + public type HousingPreview = { id: Nat; address: Text; @@ -88,36 +100,28 @@ module { value: Text }; + public type ReviewsId = Text; ///////////////////////////////// Reservations ///////////////////////////// public type Reservation = { - checkIn: Int; //Timestamp - checkOut: Int; + checkIn: Int; //Timestamp NanoSeg + checkOut: Int; //Temestamp NanoSeg applicant: Principal; guest: Text; }; - type ReviewsId = Text; - - type Node = { - value: T; - rigth: ?Node; - left: ?Node; - }; + // La primer posicion es siempre el dia actual con lo cual cada vez que se consulta se tiene que actualizar antes + // Para facilitar la implementacion inicial se considera una lista de Disponibility mutable de 30 posiciones + public type Disponibility = {day: Int; available: Bool}; + // public type Calendar = [var Disponibility]; - // public func initTree(value: T): Node { - // {value; rigth = null; left = null}; - // }; + // El rango de no disponibilidad se establece con el campo day y el campo checkOut de reservation + public type CalendaryPart = Disponibility and {reservation: ?Reservation}; + // public type Calendary = [var CalendaryPart]; - // public func put(value: T, f: (T,T) -> {#before; #after}): {#before; #after} { - - // }; + // public type FrozenCalendar = [CalendaryPart] - public type Calendar = { - //HousingId: Nat; - reservations: [Reservation]; //TODO La lista debe estar ordenada y sin solapamientos ver metodos de inserción - } } diff --git a/mops.toml b/mops.toml index bca6fc7..1e121a4 100644 --- a/mops.toml +++ b/mops.toml @@ -1,3 +1,4 @@ [dependencies] base = "0.11.1" -map = "9.0.1" \ No newline at end of file +map = "9.0.1" +random = "1.0.1" \ No newline at end of file From b817840191d5c959c3d2b039e6edacde6e0113ef Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sat, 26 Oct 2024 09:57:33 -0300 Subject: [PATCH 10/57] Paginate getHousingById --- backend/main.mo | 61 ++++++++++++++++++++++++++++++++++++++++++------ backend/types.mo | 25 +++++++++++++------- comandosCLI.md | 17 +++++++++++++- 3 files changed, 86 insertions(+), 17 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 57e824e..fc58404 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -9,6 +9,9 @@ import Types "types"; import Int "mo:base/Int"; import { now } "mo:base/Time"; import Rand "mo:random/Rand"; + +////////////// Debug imports //////////////// +import { print } "mo:base/Debug"; shared ({ caller }) actor class Triourism () = this { @@ -20,7 +23,7 @@ shared ({ caller }) actor class Triourism () = this { type HousingId = Types.HousingId; type HousingDataInit = Types.HousingDataInit; type Housing = Types.Housing; - type ShareableHousing = Types.ShareableHousing; + type HousingResponse = Types.HousingResponse; type HousingPreview = Types.HousingPreview; type UpdateResult = Types.UpdateResult; @@ -29,6 +32,7 @@ shared ({ caller }) actor class Triourism () = this { type ReservationResult = { #Ok: { + reservationId: Nat; houstingId: HousingId; data: Reservation; paymentCode: Nat; @@ -166,6 +170,7 @@ shared ({ caller }) actor class Triourism () = this { }; lastHousingId += 1; let newHousing: Housing = { + reservationRequests = Map.new(); owner = caller; minReservationLeadTimeNanoSeg = data.minReservationLeadTime * 60 * 60 * 1_000_000_000; id = lastHousingId; @@ -267,6 +272,27 @@ shared ({ caller }) actor class Triourism () = this { } }; + public shared ({ caller }) func setMinReservationLeadTime({id: HousingId; hours: Nat}):async {#Ok; #Err: Text} { + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + return #Err("No hay un housing asociado al ID porporcionado"); + }; + case (?housing) { + if(housing.owner != caller){ + return #Err("El caller no es dueño del housing"); + }; + ignore Map.put( + housings, + nhash, + id, + {housing with minReservationLeadTimeNanoSeg = hours * 60 * 60 * 1_000_000_000}); + #Ok; + } + + } + }; + ////////////////////////////////// Getters /////////////////////////////////////////// @@ -287,17 +313,31 @@ shared ({ caller }) actor class Triourism () = this { } }; - public query func getHousingById(id: HousingId): async {#Ok: ShareableHousing; #Err: Text} { - let housing = Map.get(housings, nhash, id); + public query func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { + let housing = Map.get(housings, nhash, housingId); return switch housing { case null { #Err("Error Housing ID")}; case (?housing) { - #Ok({housing with calendar = freezeCalendar(housing.calendar)}); + if(photoIndex == 0){ + let housingResponse: HousingResponse = #Start({ + housing with + calendar = freezeCalendar(housing.calendar); + photo = housing.photos[photoIndex]; + hasNextPhoto = photoIndex < housing.photos.size() + }); + #Ok(housingResponse); + } else { + let housingResponse: HousingResponse = #OnlyPhoto({ + housing with + photo = housing.photos[photoIndex]; + hasNextPhoto = photoIndex < housing.photos.size() + }); + #Ok(housingResponse) + } }; } }; - ///////////////////////////////// Reservations ///////////////////////////////////////// public shared ({ caller }) func requestReservation({id: HousingId; data: Reservation}):async ReservationResult { @@ -307,17 +347,24 @@ shared ({ caller }) actor class Triourism () = this { #Err("No hay un housing asociado al id proporcionado"); }; case (?housing) { - if(now() + housing.minReservationLeadTimeNanoSeg < data.checkIn){ + print("Momento actual en NanoSeg: " # Int.toText(now()/(60*60*1000000000))); + print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSeg /(60*60*1000000000))); + print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSeg)/(60*60*1000000000))); + print("Fecha de ingreso silicitada " # Int.toText(data.checkIn/(60*60*1000000000))); + if(now() + housing.minReservationLeadTimeNanoSeg > data.checkIn){ return #Err("Las reservas se solicitan con un minimo de anticipacion de " # Int.toText(housing.minReservationLeadTimeNanoSeg /(60 * 60 * 1_000_000_000)) # " horas"); }; + let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); let responseReservation = { houstingId = id; + reservationId; data; paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) }; - + + ignore Map.put(housing.reservationRequests, nhash, reservationId, data ); #Ok( responseReservation ) }; } diff --git a/backend/types.mo b/backend/types.mo index c614188..efcab67 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -1,4 +1,4 @@ - +import Map "mo:map/Map"; module { ////////////////////////////// Users ///////////////////////////////////// @@ -51,6 +51,7 @@ module { id: Nat; // Example L234324 owner: Principal; calendar: [var CalendaryPart]; + reservationRequests: Map.Map; minReservationLeadTimeNanoSeg: Int; // valor en nanosegundos de anticipacion para efectuar una reserva address: Text; photos: [Blob]; @@ -59,14 +60,20 @@ module { kind: HousingKind; }; - public type ShareableHousing = { - id: Nat; - owner: Principal; - calendar: [CalendaryPart]; - photos: [Blob]; - thumbnail: Blob; - prices: [Price]; - kind: HousingKind; + public type HousingResponse = { + #Start : {id: Nat; + owner: Principal; + calendar: [CalendaryPart]; + photo: Blob; + thumbnail: Blob; + prices: [Price]; + kind: HousingKind; + hasNextPhoto: Bool; + }; + #OnlyPhoto :{ + photo: Blob; + hasNextPhoto: Bool; + } }; public type HousingPreview = { diff --git a/comandosCLI.md b/comandosCLI.md index 1b519ad..6104d65 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -26,7 +26,8 @@ publicacion de hosting ``` dfx canister call backend publishHousing '(record { - address = "San Mattin 555"; + address = "San Martin 555"; + minReservationLeadTime = 24; prices = vec { variant {PerNight = 50}; variant {PerWeek = 550} @@ -65,3 +66,17 @@ dfx canister call backend updatePrices '(record { })' ``` +Solicitud de reserva + +``` +dfx canister call backend requestReservation '(record { + id = 1 : nat; + data = record { + applicant = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; + checkIn = 1_729_636_239_000_000_000 : int; + guest = "Ariel"; + checkOut = 1_729_722_639_000_000_000 : int; + }; +})' +``` + From 0c38bed45f2128840ef3d7a7c8bec63d9e85fb95 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sat, 26 Oct 2024 11:04:36 -0300 Subject: [PATCH 11/57] Update calendar --- backend/main.mo | 55 ++++++++++++++++++++++++++++++++++++++++++++---- backend/types.mo | 2 +- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index fc58404..d7b6595 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -45,6 +45,7 @@ shared ({ caller }) actor class Triourism () = this { stable let DEPLOYER = caller; + let NANO_SEG_PER_HOUR = 60 * 60 * 1_000_000_000; let ramdomGenerator = Rand.Rand(); // stable var minReservationLeadTime = 24 * 60 * 60 * 1_000_000_000; // 24 horas en nanosegundos @@ -121,6 +122,23 @@ shared ({ caller }) actor class Triourism () = this { Prim.Array_tabulate(c.size(), func i = c[i]) }; + func updateCalendar(c: [var CalendaryPart]): [var CalendaryPart] { + var indexDay = 0; + var displace = 0; + while(now() + NANO_SEG_PER_HOUR * 24 > c[indexDay].day){ + displace += 1; + indexDay += 1; + }; + let outPutArray = c; + var index = 0; + while(index + displace <= c.size()){ + outPutArray[index] := c[index + displace]; + outPutArray[index + displace] := {day = 0; available = true; reservation = null}; + index += 1; + }; + outPutArray; + }; + /////////////////////////// Manage admins functions ///////////////////////////////// @@ -172,7 +190,7 @@ shared ({ caller }) actor class Triourism () = this { let newHousing: Housing = { reservationRequests = Map.new(); owner = caller; - minReservationLeadTimeNanoSeg = data.minReservationLeadTime * 60 * 60 * 1_000_000_000; + minReservationLeadTimeNanoSeg = data.minReservationLeadTimeHours * NANO_SEG_PER_HOUR; id = lastHousingId; calendar: [var CalendaryPart] = initCalendary(); photos: [Blob] = []; @@ -347,10 +365,14 @@ shared ({ caller }) actor class Triourism () = this { #Err("No hay un housing asociado al id proporcionado"); }; case (?housing) { + + ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// print("Momento actual en NanoSeg: " # Int.toText(now()/(60*60*1000000000))); print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSeg /(60*60*1000000000))); print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSeg)/(60*60*1000000000))); print("Fecha de ingreso silicitada " # Int.toText(data.checkIn/(60*60*1000000000))); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if(now() + housing.minReservationLeadTimeNanoSeg > data.checkIn){ return #Err("Las reservas se solicitan con un minimo de anticipacion de " # Int.toText(housing.minReservationLeadTimeNanoSeg /(60 * 60 * 1_000_000_000)) # @@ -370,9 +392,34 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller }) func confirmReservation(){}; - - + func paymentVerification(txHash: Nat):async Bool{ + // TODO protocolo de verificacion de pago + true + }; + // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId; txHash: Nat}): async {#Ok; #Err: Text}{ + // let housing = Map.get(housings, nhash, hostId); + // switch housing { + // case null { #Err("Incorrect housing ID") }; + // case ( ?housing ) { + // let reserv = Map.remove(housing.reservationRequests, nhash, reservId); + // switch reserv { + // case null { #Err("Incorrect reservation ID") }; + // case ( ?reserv ) { + // if(caller != reserv.applicant) { + // #Err("The caller does not match the reservation requester") + // }; + // // TODO Verificacion datos de pago a traves del txHhahs + // if (paymentVerification(txHash)){ + + // } + + // } + // } + // } + // } + + + // }; }; diff --git a/backend/types.mo b/backend/types.mo index efcab67..5f03c96 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -40,7 +40,7 @@ module { public type HousingDataInit = { // owner: Principal; - minReservationLeadTime: Int; //valor en horas de anticipación para efectuar una reserva + minReservationLeadTimeHours: Int; //valor en horas de anticipación para efectuar una reserva address: Text; prices: [Price]; kind: HousingKind; From 3f8fabbd9f68e1d312603b020f79fefe046d41ed Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sat, 26 Oct 2024 18:41:31 -0300 Subject: [PATCH 12/57] requestReservation, confirmReservation --- backend/constants.mo | 14 ++++ backend/main.mo | 193 ++++++++++++++++++++++++++++--------------- 2 files changed, 140 insertions(+), 67 deletions(-) create mode 100644 backend/constants.mo diff --git a/backend/constants.mo b/backend/constants.mo new file mode 100644 index 0000000..6b191d6 --- /dev/null +++ b/backend/constants.mo @@ -0,0 +1,14 @@ +module { + + public let PayRequest = "Please have 60 minutes to complete the payment process and secure your reservation. After this period, if the transaction has not been completed, the reservation request will be cancelled. Please do not proceed with payment after the deadline has expired."; + public let NotAvalableAllDays = "At least one of the requested days is not available"; + public let NotHosting = "There is no hosting associated with the ID provided"; + public let NotReservation = "There is no reservation associated with the ID provided"; + public let PaginationOutOfRange = "Pagination index out of range"; + public let CallerNotHousingOwner = "The caller is not the owner of the hosting"; + public let UnauthorizedCaller = "Unauthorized user"; + public let NotVerifiedUser = "The user is not verified"; + public let NotUser = "Unregistered user"; + public let CallerIsNotrequester = "The caller does not match the reservation requester"; + +}; diff --git a/backend/main.mo b/backend/main.mo index d7b6595..e8b9083 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -9,6 +9,7 @@ import Types "types"; import Int "mo:base/Int"; import { now } "mo:base/Time"; import Rand "mo:random/Rand"; +import msg "constants"; ////////////// Debug imports //////////////// import { print } "mo:base/Debug"; @@ -36,14 +37,14 @@ shared ({ caller }) actor class Triourism () = this { houstingId: HousingId; data: Reservation; paymentCode: Nat; - // msg: Text; + msg: Text; }; #Err: Text; }; // TODO revisar day, actualemnte es el timestamp de una hora especifica que delimita el comienzo del dia - + stable let DEPLOYER = caller; let NANO_SEG_PER_HOUR = 60 * 60 * 1_000_000_000; let ramdomGenerator = Rand.Rand(); @@ -59,11 +60,11 @@ shared ({ caller }) actor class Triourism () = this { stable var lastHousingId = 0; - ///////////////////////////////////// Update functions //////////////////////////////////////// + ///////////////////////////////////// Update functions //////////////////////////////////////// public shared ({ caller }) func signUp(data: Types.SignUpData) : async SignUpResult { if(Principal.isAnonymous(caller)){ - return #Err("User not autenthicated") + return #Err(msg.NotUser) }; let user = Map.get(users, phash, caller); switch user { @@ -96,7 +97,7 @@ shared ({ caller }) actor class Triourism () = this { public shared ({ caller }) func loadAvatar(avatar: Blob): async {#Ok; #Err: Text} { let user = Map.get(users, phash, caller); switch user { - case null {#Err("There is no user associated with the caller")}; + case null {#Err(msg.NotUser)}; case(?user) { ignore Map.put(users, phash, caller, {user with avatar = ?avatar}); #Ok @@ -139,8 +140,47 @@ shared ({ caller }) actor class Triourism () = this { outPutArray; }; + func availableAllDaysResquest(checkin: Int, checkout: Int): Bool{ + // Consultar caledario del Hosting y reservationRequests + true + }; - /////////////////////////// Manage admins functions ///////////////////////////////// + func intToNat(x: Int): Nat{ + Prim.nat64ToNat(Prim.intToNat64Wrap(x)) + }; + /////////// Probar //////////// + func insertReservationToCalendar(_calendar: [var CalendaryPart], reserv: Reservation): {#Ok: [var CalendaryPart]; #Err }{ + let calendar = updateCalendar(_calendar); // Nos aseguramos de que el primer elemnto del array sea el dia actual + let checkInDay = intToNat((reserv.checkIn - now())) / 24 * NANO_SEG_PER_HOUR; + let daysQty = intToNat(reserv.checkOut - reserv.checkIn) / 24 * NANO_SEG_PER_HOUR; + var index = checkInDay; + var okBaby = true; + while (index < checkInDay + daysQty){ + if (not calendar[index].available) { + okBaby := false; + index += daysQty; + }; + index += 1; + }; + if( okBaby ) { + index := checkInDay; + while (index < checkInDay + daysQty){ + let calendaryPart: CalendaryPart = { + reservation = ?reserv; + day = index * 24 * NANO_SEG_PER_HOUR; + available = false; + }; + calendar[index] := calendaryPart; + index += 1; + }; + #Ok(calendar) + } else { + #Err + } + + }; + + /////////////////////////// Manage admins functions ///////////////////////////////// public shared ({ caller }) func addAdmin(p: Principal): async {#Ok; #Err} { if(not isAdmin(caller)){ @@ -160,10 +200,8 @@ shared ({ caller }) actor class Triourism () = this { } }; - /////////////////////////// Admin functions - - - /////////////////////////////// Verification process ////////////////////////////// + /////////////////////////// Admin functions ////////////////////////////////////////////// + /////////////////////////////// Verification process ///////////////////////////////////// // TODO actualmente todos los usuarios se inicializan como verificados func userIsVerificated(u: Principal): Bool { @@ -174,17 +212,17 @@ shared ({ caller }) actor class Triourism () = this { }; }; - //////////////////////////////// CRUD Housing ///////////////////////////////////// + //////////////////////////////// CRUD Housing //////////////////////////////////////////// public shared ({ caller }) func publishHousing(data: HousingDataInit): async PublishResult { let user = Map.get(users, phash, caller); switch user { case null { - return #Err("Usuario no registrado"); + return #Err(msg.NotUser); }; case (?user){ if(not userIsVerificated(caller)){ - return #Err("Usuario no verificado"); + return #Err(msg.NotVerifiedUser); }; lastHousingId += 1; let newHousing: Housing = { @@ -248,7 +286,7 @@ shared ({ caller }) actor class Triourism () = this { }; case (?housing) { if(housing.owner != caller){ - return #Err("The caller is not the owner of the housing") + return #Err(msg.CallerNotHousingOwner) }; let photos = Prim.Array_tabulate( housing.photos.size() +1, @@ -264,11 +302,11 @@ shared ({ caller }) actor class Triourism () = this { let housing = Map.get(housings, nhash, id); switch housing { case null { - #Err("Invalid Housing Id") + #Err(msg.NotHosting) }; case (?housing) { if(housing.owner != caller){ - return #Err("The caller is not the owner of the housing") + return #Err(msg.UnauthorizedCaller) }; ignore Map.put(housings, nhash, id, {housing with thumbnail}); #Ok @@ -280,10 +318,10 @@ shared ({ caller }) actor class Triourism () = this { let housing = Map.get(housings, nhash, id); switch housing { case null { - return #Err("Error Housing ID"); + return #Err(msg.NotHosting); }; case (?housing) { - if(housing.owner != caller){ return #Err("Unauthorized caller") }; + if(housing.owner != caller){ return #Err(msg.UnauthorizedCaller) }; ignore Map.put(housings, nhash, id, {housing with prices}); return #Ok }; @@ -294,17 +332,17 @@ shared ({ caller }) actor class Triourism () = this { let housing = Map.get(housings, nhash, id); switch housing { case null { - return #Err("No hay un housing asociado al ID porporcionado"); + return #Err(msg.NotHosting); }; case (?housing) { if(housing.owner != caller){ - return #Err("El caller no es dueño del housing"); + return #Err(msg.CallerNotHousingOwner); }; ignore Map.put( housings, nhash, id, - {housing with minReservationLeadTimeNanoSeg = hours * 60 * 60 * 1_000_000_000}); + {housing with minReservationLeadTimeNanoSeg = hours * NANO_SEG_PER_HOUR}); #Ok; } @@ -312,11 +350,11 @@ shared ({ caller }) actor class Triourism () = this { }; - ////////////////////////////////// Getters /////////////////////////////////////////// + ////////////////////////////////// Getters /////////////////////////////////////////////// public query func getHousingPaginate(page: Nat): async ResultHousingPaginate { if(Map.size(housings) < page * 10){ - return #Err("Pagination index out of range") + return #Err(msg.PaginationOutOfRange) }; let values = Map.toArray(housings); let bufferHousingPreview = Buffer.fromArray([]); @@ -334,7 +372,7 @@ shared ({ caller }) actor class Triourism () = this { public query func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { let housing = Map.get(housings, nhash, housingId); return switch housing { - case null { #Err("Error Housing ID")}; + case null { #Err(msg.NotHosting)}; case (?housing) { if(photoIndex == 0){ let housingResponse: HousingResponse = #Start({ @@ -356,38 +394,45 @@ shared ({ caller }) actor class Triourism () = this { } }; - ///////////////////////////////// Reservations ///////////////////////////////////////// + ///////////////////////////////// Reservations /////////////////////////////////////////// - public shared ({ caller }) func requestReservation({id: HousingId; data: Reservation}):async ReservationResult { - let housing = Map.get(housings, nhash, id); + public shared ({ caller }) func requestReservation({hostId: HousingId; data: Reservation}):async ReservationResult { + let housing = Map.get(housings, nhash, hostId); switch housing { case null { - #Err("No hay un housing asociado al id proporcionado"); + #Err(msg.NotHosting); }; case (?housing) { + /////// housing calendar update / housing Map update ////// + let calendar = updateCalendar(housing.calendar); + ignore Map.put(housings, nhash, hostId, {housing with calendar}); ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// - print("Momento actual en NanoSeg: " # Int.toText(now()/(60*60*1000000000))); - print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSeg /(60*60*1000000000))); - print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSeg)/(60*60*1000000000))); - print("Fecha de ingreso silicitada " # Int.toText(data.checkIn/(60*60*1000000000))); + print("Momento actual en NanoSeg: " # Int.toText(now())); + print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSeg )); + print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSeg))); + print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if(now() + housing.minReservationLeadTimeNanoSeg > data.checkIn){ - return #Err("Las reservas se solicitan con un minimo de anticipacion de " # - Int.toText(housing.minReservationLeadTimeNanoSeg /(60 * 60 * 1_000_000_000)) # - " horas"); + return #Err("Reservations are requested at least " # + Int.toText(housing.minReservationLeadTimeNanoSeg /(NANO_SEG_PER_HOUR)) # + " hours in advance."); }; - let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); - let responseReservation = { - houstingId = id; - reservationId; - data; - paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) - }; - - ignore Map.put(housing.reservationRequests, nhash, reservationId, data ); - #Ok( responseReservation ) + if(availableAllDaysResquest(data.checkIn, data.checkOut)){ + let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); + let responseReservation = { + houstingId = hostId; + reservationId; + data; + msg = msg.PayRequest; + paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) + }; + ignore Map.put(housing.reservationRequests, nhash, reservationId, data ); + #Ok( responseReservation ) + } else { + #Err( msg.NotAvalableAllDays); + } }; } }; @@ -397,29 +442,43 @@ shared ({ caller }) actor class Triourism () = this { true }; - // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId; txHash: Nat}): async {#Ok; #Err: Text}{ - // let housing = Map.get(housings, nhash, hostId); - // switch housing { - // case null { #Err("Incorrect housing ID") }; - // case ( ?housing ) { - // let reserv = Map.remove(housing.reservationRequests, nhash, reservId); - // switch reserv { - // case null { #Err("Incorrect reservation ID") }; - // case ( ?reserv ) { - // if(caller != reserv.applicant) { - // #Err("The caller does not match the reservation requester") - // }; - // // TODO Verificacion datos de pago a traves del txHhahs - // if (paymentVerification(txHash)){ - - // } - - // } - // } - // } - // } + public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId; txHash: Nat}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, hostId); + switch housing { + case null { #Err(msg.NotHosting) }; + case ( ?housing ) { + let updatedCalendar = updateCalendar(housing.calendar); + ignore Map.put(housings, nhash, hostId, {housing with updatedCalendar}); + let reserv = Map.remove(housing.reservationRequests, nhash, reservId); + switch reserv { + case null { #Err(msg.NotReservation) }; + case ( ?reserv ) { + if(caller != reserv.applicant) { + return #Err(msg.CallerIsNotrequester) + }; + // TODO Verificacion datos de pago a traves del txHhahs + if (await paymentVerification(txHash)){ + let calendar = insertReservationToCalendar(updatedCalendar, reserv); + switch calendar { + case (#Ok(calendar)) { + + ignore Map.put(housings, nhash, hostId, {housing with calendar}); + return #Ok + }; + case (_) { + ignore Map.put(housing.reservationRequests, nhash, reservId, reserv); + return #Err("Error") + } + } + }; + #Err("Incorrect payment verification") + + } + } + } + } - // }; + }; }; From 080d62373e3f65bb19ef77827259e2c6bf2dddda Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sat, 26 Oct 2024 18:43:03 -0300 Subject: [PATCH 13/57] Confirm requestReservation... --- backend/main.mo | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/main.mo b/backend/main.mo index e8b9083..d6825d3 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -472,7 +472,6 @@ shared ({ caller }) actor class Triourism () = this { } }; #Err("Incorrect payment verification") - } } } From 0ce8f3a2f6cfa6db937a8f756ed0a3abec942808 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sat, 2 Nov 2024 18:36:45 -0300 Subject: [PATCH 14/57] Relacionado al cambio signUp -> signUp/SignUpAsHost --- backend/constants.mo | 1 + backend/main.mo | 236 ++++++++++++++++++++++++++++++------------- backend/types.mo | 30 +++--- comandosCLI.md | 2 +- 4 files changed, 179 insertions(+), 90 deletions(-) diff --git a/backend/constants.mo b/backend/constants.mo index 6b191d6..6657780 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -10,5 +10,6 @@ module { public let NotVerifiedUser = "The user is not verified"; public let NotUser = "Unregistered user"; public let CallerIsNotrequester = "The caller does not match the reservation requester"; + public let NotHost = "Te caller in not Host User profile"; }; diff --git a/backend/main.mo b/backend/main.mo index d6825d3..8bafa9b 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -28,7 +28,7 @@ shared ({ caller }) actor class Triourism () = this { type HousingPreview = Types.HousingPreview; type UpdateResult = Types.UpdateResult; - type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; next: Bool}; #Err: Text}; + type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; hasNext: Bool}; #Err: Text}; type PublishResult = {#Ok: HousingId; #Err: Text}; type ReservationResult = { @@ -62,28 +62,39 @@ shared ({ caller }) actor class Triourism () = this { ///////////////////////////////////// Update functions //////////////////////////////////////// - public shared ({ caller }) func signUp(data: Types.SignUpData) : async SignUpResult { - if(Principal.isAnonymous(caller)){ + private func _safeSignUp(p: Principal, data: Types.SignUpData, kind: UserKind): SignUpResult { + if(Principal.isAnonymous(p)){ return #Err(msg.NotUser) }; - let user = Map.get(users, phash, caller); + let user = Map.get(users, phash, p); switch user { case (?User) { #Err("User already exists") }; case null { let newUser: User = { - userKind: [UserKind] = []; + kinds: [UserKind] = [kind]; name = data.name; + lastName = data.lastName; + phone = data.phone; email = data.email; verified = true; score = 0; - avatar = data.avatar; + // avatar = data.avatar; }; - ignore Map.put(users, phash, caller, newUser); + ignore Map.put(users, phash, p, newUser); #Ok(newUser); }; }; }; + public shared ({ caller }) func signUp(data: Types.SignUpData) : async SignUpResult { + _safeSignUp(caller, data, #Initial) + }; + + public shared ({ caller }) func signUpAsHost(data: Types.SignUpData) : async SignUpResult { + _safeSignUp(caller, data, #Host([])); + + }; + public shared query ({ caller }) func logIn(): async {#Ok: User; #Err} { let user = Map.get(users, phash, caller); switch user { @@ -94,16 +105,28 @@ shared ({ caller }) actor class Triourism () = this { //////////////////////////////// CRUD Data User /////////////////////////////////// - public shared ({ caller }) func loadAvatar(avatar: Blob): async {#Ok; #Err: Text} { + // public shared ({ caller }) func loadAvatar(avatar: Blob): async {#Ok; #Err: Text} { + // let user = Map.get(users, phash, caller); + // switch user { + // case null {#Err(msg.NotUser)}; + // case(?user) { + // ignore Map.put(users, phash, caller, {user with avatar = ?avatar}); + // #Ok + // } + // } + // }; + + public shared ({ caller }) func editProfile(data: Types.SignUpData): async {#Ok; #Err}{ let user = Map.get(users, phash, caller); switch user { - case null {#Err(msg.NotUser)}; - case(?user) { - ignore Map.put(users, phash, caller, {user with avatar = ?avatar}); + case null { #Err }; + case (?user){ + ignore Map.put(users, phash, caller, { user with data}); #Ok - } - } + }; + }; }; + // TODO /////////////////////////////////////////////////////////////////////////////////// @@ -214,68 +237,106 @@ shared ({ caller }) actor class Triourism () = this { //////////////////////////////// CRUD Housing //////////////////////////////////////////// - public shared ({ caller }) func publishHousing(data: HousingDataInit): async PublishResult { + // public shared ({ caller }) func publishHousingOld(data: HousingDataInit): async PublishResult { + // let user = Map.get(users, phash, caller); + // switch user { + // case null { + // return #Err(msg.NotUser); + // }; + // case (?user){ + // if(not userIsVerificated(caller)){ + // return #Err(msg.NotVerifiedUser); + // }; + // lastHousingId += 1; + // let newHousing: Housing = { data with + // reservationRequests = Map.new(); + // owner = caller; + // id = lastHousingId; + // calendar: [var CalendaryPart] = initCalendary(); + // photos: [Blob] = []; + // thumbnail: Blob = ""; + // }; + // var updateHousingArray: [HousingId] = []; + // var notPrevious = true; + // var position = 0; + // var i = 0; + // while(i < user.userKind.size()){ + // switch (user.userKind[i]){ + // case(#Host(housingIdArray)){ + // notPrevious := false; + // position := i; + // updateHousingArray := Prim.Array_tabulate( + // housingIdArray.size() + 1, + // func x { + // if(x != housingIdArray.size()){ + // housingIdArray[x]; + // } + // else {newHousing.id} + // } + // ) + // }; + // case(_){}; + // }; + // i += 1; + // }; + // if(notPrevious){ updateHousingArray := [newHousing.id] }; + // let updateKinds = Prim.Array_tabulate( + // user.userKind.size() + (if(notPrevious){ 1 } else { 0 }), + // func i { if(i == position) { + // #Host(updateHousingArray) + // } + // else { + // user.userKind[i] + // } + // } + // ); + // ignore Map.put(housings, nhash, lastHousingId, newHousing); + // ignore Map.put(users, phash, caller, {user with userKind = updateKinds}); + // return #Ok(newHousing.id) + // } + // }; + // }; + + public shared ({ caller }) func publishHousing(data: HousingDataInit): async PublishResult { let user = Map.get(users, phash, caller); switch user { case null { return #Err(msg.NotUser); }; - case (?user){ - if(not userIsVerificated(caller)){ - return #Err(msg.NotVerifiedUser); - }; - lastHousingId += 1; - let newHousing: Housing = { - reservationRequests = Map.new(); - owner = caller; - minReservationLeadTimeNanoSeg = data.minReservationLeadTimeHours * NANO_SEG_PER_HOUR; - id = lastHousingId; - calendar: [var CalendaryPart] = initCalendary(); - photos: [Blob] = []; - thumbnail: Blob = ""; - address = data.address; - prices = data.prices; - kind = data.kind; - }; - var updateHousingArray: [HousingId] = []; - var notPrevious = true; - var position = 0; - var i = 0; - while(i < user.userKind.size()){ - switch (user.userKind[i]){ - case(#Host(housingIdArray)){ - notPrevious := false; - position := i; - updateHousingArray := Prim.Array_tabulate( - housingIdArray.size() + 1, - func x { - if(x != housingIdArray.size()){ - housingIdArray[x]; - } - else {newHousing.id} - } - ) + case ( ?user ){ + var index = 0; + for (kind in user.kinds.vals()){ + switch kind { + case (#Host(hosingIdList)){ + lastHousingId += 1; + let newHousing: Housing = { data with + reservationRequests = Map.new(); + owner = caller; + id = lastHousingId; + calendar: [var CalendaryPart] = initCalendary(); + photos: [Blob] = []; + thumbnail: Blob = ""; + }; + ignore Map.put(housings, nhash, lastHousingId, newHousing); + let hosingIdListUpdate = Prim.Array_tabulate( + hosingIdList.size() + 1, + func x = if(x == 0) { lastHousingId } else {hosingIdList[x + 1]} + ); + let updateKinds = Prim.Array_tabulate( + user.kinds.size(), + func x = if(x == index) {#Host(hosingIdListUpdate)} else {user.kinds[index]} + ); + ignore Map.put(users, phash, caller, {user with userKind = updateKinds}); + return #Ok(newHousing.id) }; - case(_){}; + case _ {}; }; - i += 1; + index += 1; }; - if(notPrevious){ updateHousingArray := [newHousing.id] }; - let updateKinds = Prim.Array_tabulate( - user.userKind.size() + (if(notPrevious){ 1 } else { 0 }), - func i { if(i == position) { - #Host(updateHousingArray) - } - else { - user.userKind[i] - } - } - ); - ignore Map.put(housings, nhash, lastHousingId, newHousing); - ignore Map.put(users, phash, caller, {user with userKind = updateKinds}); - return #Ok(newHousing.id) + return #Err(msg.NotUser) } - }; + } + }; public shared ({ caller }) func addPhotoToHousing({id: HousingId; photo: Blob}): async {#Ok; #Err: Text} { @@ -349,7 +410,6 @@ shared ({ caller }) actor class Triourism () = this { } }; - ////////////////////////////////// Getters /////////////////////////////////////////////// public query func getHousingPaginate(page: Nat): async ResultHousingPaginate { @@ -365,7 +425,7 @@ shared ({ caller }) actor class Triourism () = this { }; #Ok{ array = Buffer.toArray(bufferHousingPreview); - next = ((page + 1) * 10 < values.size()) + hasNext = ((page + 1) * 10 < values.size()) } }; @@ -394,6 +454,38 @@ shared ({ caller }) actor class Triourism () = this { } }; + public shared ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate { + let user = Map.get(users, phash, caller); + switch user { + case null { #Err("There is no user associated with the caller")}; + case ( ?user ) { + for( k in user.kinds.vals()){ + switch k { + case (#Host(hostIds)) { + let bufferHousingreview = Buffer.fromArray([]); + var index = page * 10; + while(index < hostIds.size() and index < 10 * (page + 1)){ + let housing = Map.get(housings, nhash, hostIds[index]); + switch housing { + case null{ }; + case ( ?housing ) { + let prev: HousingPreview = housing; + bufferHousingreview.add(prev); + } + }; + index += 1; + }; + return #Ok({array = Buffer.toArray(bufferHousingreview); hasNext = hostIds.size() > 10 * (page +1)}) + }; + case _ {}; + } + }; + #Err("The user is not a hosting type user") + + } + } + }; + ///////////////////////////////// Reservations /////////////////////////////////////////// public shared ({ caller }) func requestReservation({hostId: HousingId; data: Reservation}):async ReservationResult { @@ -409,14 +501,14 @@ shared ({ caller }) actor class Triourism () = this { ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// print("Momento actual en NanoSeg: " # Int.toText(now())); - print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSeg )); - print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSeg))); + print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSec )); + print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSec))); print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - if(now() + housing.minReservationLeadTimeNanoSeg > data.checkIn){ + if(now() + housing.minReservationLeadTimeNanoSec > data.checkIn){ return #Err("Reservations are requested at least " # - Int.toText(housing.minReservationLeadTimeNanoSeg /(NANO_SEG_PER_HOUR)) # + Int.toText(housing.minReservationLeadTimeNanoSec /(NANO_SEG_PER_HOUR)) # " hours in advance."); }; if(availableAllDaysResquest(data.checkIn, data.checkOut)){ diff --git a/backend/types.mo b/backend/types.mo index 5f03c96..739b638 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -4,19 +4,18 @@ module { public type SignUpData = { name: Text; - email: ?Text; - avatar: ?Blob; + lastName: Text; + phone: ?Nat; + email: Text; + //avatar: ?Blob; }; - public type User = { + public type User = SignUpData and { // id: Nat; - userKind: [UserKind]; //Un user Host puede ser ademas un User - name: Text; - email: ?Text; + kinds: [UserKind]; + // userKind: UserKind; verified: Bool; - score: Nat; - avatar: ?Blob; - + score: Nat; }; public type UserKind = { @@ -26,6 +25,7 @@ module { }; public type SignUpResult = { #Ok : User; #Err : Text }; + // type GetProfileError = { // #userNotAuthenticated; // #profileNotFound; @@ -39,25 +39,21 @@ module { ///////////////////////////////// Housing ///////////////////////////////// public type HousingDataInit = { - // owner: Principal; - minReservationLeadTimeHours: Int; //valor en horas de anticipación para efectuar una reserva + minReservationLeadTimeNanoSec: Int; //valor en nanoSeg de anticipación para efectuar una reserva address: Text; prices: [Price]; kind: HousingKind; + amenities: [Text]; }; - public type Housing = { + public type Housing = HousingDataInit and { id: Nat; // Example L234324 owner: Principal; calendar: [var CalendaryPart]; - reservationRequests: Map.Map; - minReservationLeadTimeNanoSeg: Int; // valor en nanosegundos de anticipacion para efectuar una reserva - address: Text; + reservationRequests: Map.Map; photos: [Blob]; thumbnail: Blob; // Se recomienda la foto principal en tamaño reducido - prices: [Price]; - kind: HousingKind; }; public type HousingResponse = { diff --git a/comandosCLI.md b/comandosCLI.md index 6104d65..c4c7782 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -26,8 +26,8 @@ publicacion de hosting ``` dfx canister call backend publishHousing '(record { + minReservationLeadTimeHours = 24; address = "San Martin 555"; - minReservationLeadTime = 24; prices = vec { variant {PerNight = 50}; variant {PerWeek = 550} From 4ba139879e2c664efe9b3bf4180c5e7c86d4c84a Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sat, 2 Nov 2024 19:10:09 -0300 Subject: [PATCH 15/57] Fix update housingIdsList --- backend/main.mo | 12 ++++++++---- comandosCLI.md | 25 +++++++++++++++---------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 8bafa9b..bb5edf4 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -320,20 +320,20 @@ shared ({ caller }) actor class Triourism () = this { ignore Map.put(housings, nhash, lastHousingId, newHousing); let hosingIdListUpdate = Prim.Array_tabulate( hosingIdList.size() + 1, - func x = if(x == 0) { lastHousingId } else {hosingIdList[x + 1]} + func x = if(x == 0) { lastHousingId } else {hosingIdList[x - 1]} ); let updateKinds = Prim.Array_tabulate( user.kinds.size(), func x = if(x == index) {#Host(hosingIdListUpdate)} else {user.kinds[index]} ); - ignore Map.put(users, phash, caller, {user with userKind = updateKinds}); + ignore Map.put(users, phash, caller, {user with kinds = updateKinds}); return #Ok(newHousing.id) }; case _ {}; }; index += 1; }; - return #Err(msg.NotUser) + return #Err(msg.NotHost) } } @@ -438,7 +438,11 @@ shared ({ caller }) actor class Triourism () = this { let housingResponse: HousingResponse = #Start({ housing with calendar = freezeCalendar(housing.calendar); - photo = housing.photos[photoIndex]; + photo = if(housing.photos.size() > 0){ + housing.photos[0] + } else { + Blob.fromArray([0]) + }; hasNextPhoto = photoIndex < housing.photos.size() }); #Ok(housingResponse); diff --git a/comandosCLI.md b/comandosCLI.md index c4c7782..510f650 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -2,23 +2,27 @@ dfx deploy backend ``` -registro de usuario sin foto: +registro de usuario huesped general: ``` dfx canister call backend signUp '(record { - name="Usuario de Prueba"; - email=opt "usuario_prueba@gmail.com"; - avatar= null} + name="Juan"; + lastName="Perez"; + phone= null; + email="juanperez@gmail.com"; + } )' ``` -registro de usuario con foto: +registro de usuario tipo Host ``` -dfx canister call backend signUp '(record { - name="Usuario de Prueba"; - email=opt "usuario_prueba2@gmail.com"; - avatar= opt blob "11/44/67/87/45/34/09/87/56"} +dfx canister call backend signUpAsHost '(record { + name="Gerardo"; + lastName="Anchorena"; + phone= opt 54221548797; + email="gerardonchorena@gmail.com"; + } )' ``` @@ -26,8 +30,9 @@ publicacion de hosting ``` dfx canister call backend publishHousing '(record { - minReservationLeadTimeHours = 24; + minReservationLeadTimeNanoSec = 86400000000000; address = "San Martin 555"; + amenities = vec{"Jacuzzi"; "Piscina"; "Gimnasio"}; prices = vec { variant {PerNight = 50}; variant {PerWeek = 550} From 0695a66fc608b6b6b1b6ca807a8d42eca31c7bc6 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sun, 3 Nov 2024 13:25:50 -0300 Subject: [PATCH 16/57] Fix getHousingById index Array index out of bounds --- backend/main.mo | 7 ++++--- backend/types.mo | 7 ++++++- comandosCLI.md | 11 +++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index bb5edf4..440cb09 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -354,6 +354,7 @@ shared ({ caller }) actor class Triourism () = this { func i = if(i < housing.photos.size()) {housing.photos[i]} else {photo} ); ignore Map.put(housings, nhash, id, {housing with photos}); + print(debug_show({housing with photos})); #Ok } } @@ -443,15 +444,15 @@ shared ({ caller }) actor class Triourism () = this { } else { Blob.fromArray([0]) }; - hasNextPhoto = photoIndex < housing.photos.size() + hasNextPhoto = (housing.photos.size() > photoIndex + 1) }); #Ok(housingResponse); } else { let housingResponse: HousingResponse = #OnlyPhoto({ - housing with photo = housing.photos[photoIndex]; - hasNextPhoto = photoIndex < housing.photos.size() + hasNextPhoto = (housing.photos.size() > photoIndex + 1) }); + print(debug_show(housing.photos)); #Ok(housingResponse) } }; diff --git a/backend/types.mo b/backend/types.mo index 739b638..fb13476 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -43,6 +43,8 @@ module { address: Text; prices: [Price]; kind: HousingKind; + maxCapacity: Nat; + description: Text; amenities: [Text]; }; @@ -64,6 +66,9 @@ module { thumbnail: Blob; prices: [Price]; kind: HousingKind; + maxCapacity: Nat; + description: Text; + amenities: [Text]; hasNextPhoto: Bool; }; #OnlyPhoto :{ @@ -88,7 +93,7 @@ module { public type HousingKind = { #House; - #Hotel_room; + #Hotel_room: Text; //Ejemplo #Hotel_room("Single Room") #RoomWithSharedSpaces: [Rules]; //Hostels/Pensiones }; diff --git a/comandosCLI.md b/comandosCLI.md index 510f650..ed3bbde 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -32,10 +32,17 @@ publicacion de hosting dfx canister call backend publishHousing '(record { minReservationLeadTimeNanoSec = 86400000000000; address = "San Martin 555"; + description= "Vista al mar, 56 Mts2, silencioso"; + maxCapacity= 6; amenities = vec{"Jacuzzi"; "Piscina"; "Gimnasio"}; prices = vec { - variant {PerNight = 50}; - variant {PerWeek = 550} + variant {PerNight = 90}; + variant {PerWeek = 550}; + variant {CustomPeriod = vec { + record {dais = 15; price = 1000}; + record {dais = 30; price = 1900}; + } + } }; kind = variant {House}} )' From 538ff02fdf696f9c3b0697b6b4a6724af2034404 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sun, 3 Nov 2024 16:51:20 -0300 Subject: [PATCH 17/57] Activr/Desactivar housing --- backend/constants.mo | 1 + backend/main.mo | 23 ++++++++++++++++++++++- backend/types.mo | 4 ++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/constants.mo b/backend/constants.mo index 6657780..0081c89 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -11,5 +11,6 @@ module { public let NotUser = "Unregistered user"; public let CallerIsNotrequester = "The caller does not match the reservation requester"; public let NotHost = "Te caller in not Host User profile"; + public let InactiveHousing = "Housing is temporarily disabled" }; diff --git a/backend/main.mo b/backend/main.mo index 440cb09..73ff373 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -316,6 +316,8 @@ shared ({ caller }) actor class Triourism () = this { calendar: [var CalendaryPart] = initCalendary(); photos: [Blob] = []; thumbnail: Blob = ""; + active = true; + reviews: [Text] = []; }; ignore Map.put(housings, nhash, lastHousingId, newHousing); let hosingIdListUpdate = Prim.Array_tabulate( @@ -411,6 +413,20 @@ shared ({ caller }) actor class Triourism () = this { } }; + public shared ({ caller }) func setHousingStatus({id: HousingId; active: Bool}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, id); + switch housing { + case null { #Err(msg.NotHosting)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put(housings, nhash, id, {housing with active}); + #Ok + } + } + }; + ////////////////////////////////// Getters /////////////////////////////////////////////// public query func getHousingPaginate(page: Nat): async ResultHousingPaginate { @@ -421,7 +437,9 @@ shared ({ caller }) actor class Triourism () = this { let bufferHousingPreview = Buffer.fromArray([]); var index = page * 10; while (index < values.size() and index < (page + 1) * 10){ - bufferHousingPreview.add(values[index].1); + if(values[index].1.active){ + bufferHousingPreview.add(values[index].1); + }; index += 1; }; #Ok{ @@ -435,6 +453,9 @@ shared ({ caller }) actor class Triourism () = this { return switch housing { case null { #Err(msg.NotHosting)}; case (?housing) { + if(not housing.active) { + return #Err(msg.InactiveHousing) + }; if(photoIndex == 0){ let housingResponse: HousingResponse = #Start({ housing with diff --git a/backend/types.mo b/backend/types.mo index fb13476..98a900b 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -50,12 +50,15 @@ module { public type Housing = HousingDataInit and { + id: Nat; // Example L234324 owner: Principal; calendar: [var CalendaryPart]; reservationRequests: Map.Map; + reviews: [Text]; photos: [Blob]; thumbnail: Blob; // Se recomienda la foto principal en tamaño reducido + active: Bool; }; public type HousingResponse = { @@ -70,6 +73,7 @@ module { description: Text; amenities: [Text]; hasNextPhoto: Bool; + reviews: [Text]; }; #OnlyPhoto :{ photo: Blob; From 46efce434f9be84ca8e80b0e002557626a09e54d Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sun, 3 Nov 2024 17:49:30 -0300 Subject: [PATCH 18/57] Fix updateCalendar --- backend/main.mo | 38 +++++++++++++++++++++++++++----------- backend/types.mo | 9 ++++++--- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 73ff373..a72f096 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -20,6 +20,7 @@ shared ({ caller }) actor class Triourism () = this { type UserKind = Types.UserKind; type SignUpResult = Types.SignUpResult; type CalendaryPart = Types.CalendaryPart; + type ReservationDataInput = Types.ReservationDataInput; type Reservation = Types.Reservation; type HousingId = Types.HousingId; type HousingDataInit = Types.HousingDataInit; @@ -149,13 +150,13 @@ shared ({ caller }) actor class Triourism () = this { func updateCalendar(c: [var CalendaryPart]): [var CalendaryPart] { var indexDay = 0; var displace = 0; - while(now() + NANO_SEG_PER_HOUR * 24 > c[indexDay].day){ + while(indexDay < c.size() and now() + NANO_SEG_PER_HOUR * 24 > c[indexDay].day){ displace += 1; indexDay += 1; }; let outPutArray = c; var index = 0; - while(index + displace <= c.size()){ + while(index + displace < c.size()){ outPutArray[index] := c[index + displace]; outPutArray[index + displace] := {day = 0; available = true; reservation = null}; index += 1; @@ -307,7 +308,7 @@ shared ({ caller }) actor class Triourism () = this { var index = 0; for (kind in user.kinds.vals()){ switch kind { - case (#Host(hosingIdList)){ + case (#Host(housingIdList)){ lastHousingId += 1; let newHousing: Housing = { data with reservationRequests = Map.new(); @@ -320,13 +321,13 @@ shared ({ caller }) actor class Triourism () = this { reviews: [Text] = []; }; ignore Map.put(housings, nhash, lastHousingId, newHousing); - let hosingIdListUpdate = Prim.Array_tabulate( - hosingIdList.size() + 1, - func x = if(x == 0) { lastHousingId } else {hosingIdList[x - 1]} + let housingIdListUpdate = Prim.Array_tabulate( + housingIdList.size() + 1, + func x = if(x == 0) { lastHousingId } else {housingIdList[x - 1]} ); let updateKinds = Prim.Array_tabulate( user.kinds.size(), - func x = if(x == index) {#Host(hosingIdListUpdate)} else {user.kinds[index]} + func x = if(x == index) {#Host(housingIdListUpdate)} else {user.kinds[index]} ); ignore Map.put(users, phash, caller, {user with kinds = updateKinds}); return #Ok(newHousing.id) @@ -453,7 +454,7 @@ shared ({ caller }) actor class Triourism () = this { return switch housing { case null { #Err(msg.NotHosting)}; case (?housing) { - if(not housing.active) { + if(not housing.active and housing.owner != caller) { return #Err(msg.InactiveHousing) }; if(photoIndex == 0){ @@ -512,15 +513,29 @@ shared ({ caller }) actor class Triourism () = this { } }; + public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null {#Err(msg.NotHosting)}; + case ( ?housing ) { + if(housing.owner != caller ){ + return #Err(msg.CallerNotHousingOwner); + }; + #Ok(Map.toArray(housing.reservationRequests)) + } + } + }; + ///////////////////////////////// Reservations /////////////////////////////////////////// - public shared ({ caller }) func requestReservation({hostId: HousingId; data: Reservation}):async ReservationResult { + public shared ({ caller }) func requestReservation({hostId: HousingId; data: ReservationDataInput}):async ReservationResult { let housing = Map.get(housings, nhash, hostId); switch housing { case null { #Err(msg.NotHosting); }; case (?housing) { + print("housing"); /////// housing calendar update / housing Map update ////// let calendar = updateCalendar(housing.calendar); ignore Map.put(housings, nhash, hostId, {housing with calendar}); @@ -539,14 +554,15 @@ shared ({ caller }) actor class Triourism () = this { }; if(availableAllDaysResquest(data.checkIn, data.checkOut)){ let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); + let reservation = {data with applicant = caller}; let responseReservation = { houstingId = hostId; reservationId; - data; + data = reservation; msg = msg.PayRequest; paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) }; - ignore Map.put(housing.reservationRequests, nhash, reservationId, data ); + ignore Map.put(housing.reservationRequests, nhash, reservationId, reservation ); #Ok( responseReservation ) } else { #Err( msg.NotAvalableAllDays); diff --git a/backend/types.mo b/backend/types.mo index 98a900b..9e9668e 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -115,12 +115,15 @@ module { public type ReviewsId = Text; ///////////////////////////////// Reservations ///////////////////////////// - public type Reservation = { + public type ReservationDataInput = { checkIn: Int; //Timestamp NanoSeg - checkOut: Int; //Temestamp NanoSeg - applicant: Principal; + checkOut: Int; //Temestamp NanoSeg guest: Text; }; + + public type Reservation = ReservationDataInput and { + applicant: Principal; + }; // La primer posicion es siempre el dia actual con lo cual cada vez que se consulta se tiene que actualizar antes // Para facilitar la implementacion inicial se considera una lista de Disponibility mutable de 30 posiciones From 1f177152ccad2826dc703b6f7526b530d1fa3928 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sun, 3 Nov 2024 18:16:01 -0300 Subject: [PATCH 19/57] fix insertReservationToCalendar --- backend/main.mo | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index a72f096..f13b80a 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -173,22 +173,26 @@ shared ({ caller }) actor class Triourism () = this { Prim.nat64ToNat(Prim.intToNat64Wrap(x)) }; /////////// Probar //////////// - func insertReservationToCalendar(_calendar: [var CalendaryPart], reserv: Reservation): {#Ok: [var CalendaryPart]; #Err }{ - let calendar = updateCalendar(_calendar); // Nos aseguramos de que el primer elemnto del array sea el dia actual - let checkInDay = intToNat((reserv.checkIn - now())) / 24 * NANO_SEG_PER_HOUR; - let daysQty = intToNat(reserv.checkOut - reserv.checkIn) / 24 * NANO_SEG_PER_HOUR; + func insertReservationToCalendar(_calendar: [var CalendaryPart], reserv: Reservation): {#Ok: [var CalendaryPart]; #Err } { + let calendar = updateCalendar(_calendar); // Asegura que el primer elemento sea el día actual + let checkInDay = intToNat((reserv.checkIn - now()) / (24 * NANO_SEG_PER_HOUR)); + let daysQty = intToNat((reserv.checkOut - reserv.checkIn) / (24 * NANO_SEG_PER_HOUR)); var index = checkInDay; var okBaby = true; - while (index < checkInDay + daysQty){ + + // Comprobar disponibilidad + while (index < checkInDay + daysQty and index < calendar.size()) { if (not calendar[index].available) { okBaby := false; - index += daysQty; - }; - index += 1; + index += daysQty; // Salir del bucle si no está disponible + } else { + index += 1; + } }; - if( okBaby ) { + + if (okBaby) { index := checkInDay; - while (index < checkInDay + daysQty){ + while (index < checkInDay + daysQty and index < calendar.size()) { let calendaryPart: CalendaryPart = { reservation = ?reserv; day = index * 24 * NANO_SEG_PER_HOUR; @@ -201,9 +205,9 @@ shared ({ caller }) actor class Triourism () = this { } else { #Err } - }; + /////////////////////////// Manage admins functions ///////////////////////////////// public shared ({ caller }) func addAdmin(p: Principal): async {#Ok; #Err} { @@ -592,6 +596,7 @@ shared ({ caller }) actor class Triourism () = this { }; // TODO Verificacion datos de pago a traves del txHhahs if (await paymentVerification(txHash)){ + print("insertando la reserva en el calendario"); let calendar = insertReservationToCalendar(updatedCalendar, reserv); switch calendar { case (#Ok(calendar)) { From 59a9d8c8dd0ba1cb090ec34f8e484a73d5b8c088 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sun, 3 Nov 2024 19:22:29 -0300 Subject: [PATCH 20/57] rules --- backend/main.mo | 26 ++++++++++++++++++++------ backend/types.mo | 4 +++- comandosCLI.md | 11 ++++++----- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index f13b80a..0d24209 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -35,7 +35,7 @@ shared ({ caller }) actor class Triourism () = this { type ReservationResult = { #Ok: { reservationId: Nat; - houstingId: HousingId; + housingId: HousingId; data: Reservation; paymentCode: Nat; msg: Text; @@ -165,7 +165,7 @@ shared ({ caller }) actor class Triourism () = this { }; func availableAllDaysResquest(checkin: Int, checkout: Int): Bool{ - // Consultar caledario del Hosting y reservationRequests + // TODO Consultar caledario del Hosting y reservationRequests true }; @@ -432,6 +432,20 @@ shared ({ caller }) actor class Triourism () = this { } }; + public shared ({ caller }) func updateHosting({id: Nat; data: HousingDataInit}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, id); + switch housing { + case null { #Err(msg.NotHosting)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put(housings, nhash, id, {housing with data}); + #Ok + } + } + }; + ////////////////////////////////// Getters /////////////////////////////////////////////// public query func getHousingPaginate(page: Nat): async ResultHousingPaginate { @@ -532,8 +546,8 @@ shared ({ caller }) actor class Triourism () = this { ///////////////////////////////// Reservations /////////////////////////////////////////// - public shared ({ caller }) func requestReservation({hostId: HousingId; data: ReservationDataInput}):async ReservationResult { - let housing = Map.get(housings, nhash, hostId); + public shared ({ caller }) func requestReservation({housingId: HousingId; data: ReservationDataInput}):async ReservationResult { + let housing = Map.get(housings, nhash, housingId); switch housing { case null { #Err(msg.NotHosting); @@ -542,7 +556,7 @@ shared ({ caller }) actor class Triourism () = this { print("housing"); /////// housing calendar update / housing Map update ////// let calendar = updateCalendar(housing.calendar); - ignore Map.put(housings, nhash, hostId, {housing with calendar}); + ignore Map.put(housings, nhash, housingId, {housing with calendar}); ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// print("Momento actual en NanoSeg: " # Int.toText(now())); @@ -560,7 +574,7 @@ shared ({ caller }) actor class Triourism () = this { let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); let reservation = {data with applicant = caller}; let responseReservation = { - houstingId = hostId; + housingId; reservationId; data = reservation; msg = msg.PayRequest; diff --git a/backend/types.mo b/backend/types.mo index 9e9668e..2b3886e 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -44,7 +44,8 @@ module { prices: [Price]; kind: HousingKind; maxCapacity: Nat; - description: Text; + description: Text; + rules: [Text]; amenities: [Text]; }; @@ -71,6 +72,7 @@ module { kind: HousingKind; maxCapacity: Nat; description: Text; + rules: [Text]; amenities: [Text]; hasNextPhoto: Bool; reviews: [Text]; diff --git a/comandosCLI.md b/comandosCLI.md index ed3bbde..6378556 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -35,6 +35,8 @@ dfx canister call backend publishHousing '(record { description= "Vista al mar, 56 Mts2, silencioso"; maxCapacity= 6; amenities = vec{"Jacuzzi"; "Piscina"; "Gimnasio"}; + rules = vec {"No fumar"}; + prices = vec { variant {PerNight = 90}; variant {PerWeek = 550}; @@ -82,12 +84,11 @@ Solicitud de reserva ``` dfx canister call backend requestReservation '(record { - id = 1 : nat; + hostId = 1 : nat; data = record { - applicant = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; - checkIn = 1_729_636_239_000_000_000 : int; - guest = "Ariel"; - checkOut = 1_729_722_639_000_000_000 : int; + checkIn = 1731198892000000000 : int; + guest = "Leonardo"; + checkOut = 1733790892000000000 : int; }; })' ``` From f8d3a841a8816410f6ac25e3d5f1d36ba56b73a8 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Tue, 5 Nov 2024 14:19:47 -0300 Subject: [PATCH 21/57] getMyHousingDisponibility --- backend/main.mo | 50 ++++++++++++++++++++++++++++++++++++++++++------ backend/types.mo | 4 +++- comandosCLI.md | 2 +- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 0d24209..61282b7 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -13,6 +13,7 @@ import msg "constants"; ////////////// Debug imports //////////////// import { print } "mo:base/Debug"; +import Array "mo:base/Array"; shared ({ caller }) actor class Triourism () = this { @@ -207,7 +208,6 @@ shared ({ caller }) actor class Triourism () = this { } }; - /////////////////////////// Manage admins functions ///////////////////////////////// public shared ({ caller }) func addAdmin(p: Principal): async {#Ok; #Err} { @@ -499,8 +499,8 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate { - let user = Map.get(users, phash, caller); + func getHousingsPaginate({owner: Principal; page: Nat}): ResultHousingPaginate { + let user = Map.get(users, phash, owner); switch user { case null { #Err("There is no user associated with the caller")}; case ( ?user ) { @@ -526,10 +526,46 @@ shared ({ caller }) actor class Triourism () = this { } }; #Err("The user is not a hosting type user") - } } }; + public shared ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate{ + getHousingsPaginate({owner = caller; page}) + }; + + public shared query ({ caller }) func getMyHousingDisponibility({days: [Nat]; page: Nat}): async ResultHousingPaginate{ + let response = getHousingsPaginate({owner = caller; page}); + switch response { + case ( #Ok({array; hasNext} )) { + let bufferResults = Buffer.fromArray([]); + for(hostPreview in array.vals()){ + let housing = Map.get(housings, nhash, hostPreview.id); + switch housing { + case ( ?housing ){ + if(days.size() == 0) { + if(housing.calendar[0].available){ + bufferResults.add(hostPreview); + } + } else { + var allowed = true; + for (d in days.vals()){ + if (not housing.calendar[d].available) { + allowed := false + }; + }; + if(allowed) { + bufferResults.add(hostPreview) + } + } + }; + case _ {} + } + }; + #Ok({array = Buffer.toArray(bufferResults); hasNext}) + }; + case Err { Err } + } + }; public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ let housing = Map.get(housings, nhash, housingId); @@ -589,6 +625,7 @@ shared ({ caller }) actor class Triourism () = this { } }; + func paymentVerification(txHash: Nat):async Bool{ // TODO protocolo de verificacion de pago true @@ -629,8 +666,9 @@ shared ({ caller }) actor class Triourism () = this { } } } - - }; + // TODO confirmacion de reservacion por parte del dueño del Host }; + + diff --git a/backend/types.mo b/backend/types.mo index 2b3886e..db241e3 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -47,6 +47,7 @@ module { description: Text; rules: [Text]; amenities: [Text]; + properties: {bed: Text; bathroom: Text }; }; @@ -73,7 +74,8 @@ module { maxCapacity: Nat; description: Text; rules: [Text]; - amenities: [Text]; + amenities: [Text]; + properties: {bed: Text; bathroom: Text }; hasNextPhoto: Bool; reviews: [Text]; }; diff --git a/comandosCLI.md b/comandosCLI.md index 6378556..d944c17 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -84,7 +84,7 @@ Solicitud de reserva ``` dfx canister call backend requestReservation '(record { - hostId = 1 : nat; + housingId = 1 : nat; data = record { checkIn = 1731198892000000000 : int; guest = "Leonardo"; From f0a2d7b7f864787c5fea162a4f878868a644d6a6 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 11 Nov 2024 21:31:45 -0300 Subject: [PATCH 22/57] BedKinds variant --- backend/main.mo | 16 +++++++--------- backend/types.mo | 22 ++++++++++++---------- comandosCLI.md | 15 +++++++++++---- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 61282b7..109a5f8 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -73,14 +73,10 @@ shared ({ caller }) actor class Triourism () = this { case (?User) { #Err("User already exists") }; case null { let newUser: User = { + data with kinds: [UserKind] = [kind]; - name = data.name; - lastName = data.lastName; - phone = data.phone; - email = data.email; verified = true; score = 0; - // avatar = data.avatar; }; ignore Map.put(users, phash, p, newUser); #Ok(newUser); @@ -499,7 +495,7 @@ shared ({ caller }) actor class Triourism () = this { } }; - func getHousingsPaginate({owner: Principal; page: Nat}): ResultHousingPaginate { + func getHousingsPaginateByOwner({owner: Principal; page: Nat}): ResultHousingPaginate { let user = Map.get(users, phash, owner); switch user { case null { #Err("There is no user associated with the caller")}; @@ -530,11 +526,11 @@ shared ({ caller }) actor class Triourism () = this { } }; public shared ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate{ - getHousingsPaginate({owner = caller; page}) + getHousingsPaginateByOwner({owner = caller; page}) }; public shared query ({ caller }) func getMyHousingDisponibility({days: [Nat]; page: Nat}): async ResultHousingPaginate{ - let response = getHousingsPaginate({owner = caller; page}); + let response = getHousingsPaginateByOwner({owner = caller; page}); switch response { case ( #Ok({array; hasNext} )) { let bufferResults = Buffer.fromArray([]); @@ -669,6 +665,8 @@ shared ({ caller }) actor class Triourism () = this { }; // TODO confirmacion de reservacion por parte del dueño del Host + // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId}): async { + + // }; }; - diff --git a/backend/types.mo b/backend/types.mo index db241e3..fcd57a2 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -1,9 +1,10 @@ import Map "mo:map/Map"; +import Nat "mo:base/Nat"; module { ////////////////////////////// Users ///////////////////////////////////// public type SignUpData = { - name: Text; + firstName: Text; lastName: Text; phone: ?Nat; email: Text; @@ -41,13 +42,14 @@ module { public type HousingDataInit = { minReservationLeadTimeNanoSec: Int; //valor en nanoSeg de anticipación para efectuar una reserva address: Text; + prices: [Price]; kind: HousingKind; maxCapacity: Nat; description: Text; rules: [Text]; amenities: [Text]; - properties: {bed: Text; bathroom: Text }; + properties: {beds: [BedKind]; bathroom: Bool }; }; @@ -63,19 +65,19 @@ module { active: Bool; }; + type BedKind = { + #Single: Nat; + #Matrimonial: Nat; + #SofaBed: Nat; + // Agregar mas variantes + }; + public type HousingResponse = { - #Start : {id: Nat; + #Start : HousingDataInit and {id: Nat; owner: Principal; calendar: [CalendaryPart]; photo: Blob; thumbnail: Blob; - prices: [Price]; - kind: HousingKind; - maxCapacity: Nat; - description: Text; - rules: [Text]; - amenities: [Text]; - properties: {bed: Text; bathroom: Text }; hasNextPhoto: Bool; reviews: [Text]; }; diff --git a/comandosCLI.md b/comandosCLI.md index d944c17..b2a1e0f 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -6,7 +6,7 @@ registro de usuario huesped general: ``` dfx canister call backend signUp '(record { - name="Juan"; + firstName="Juan"; lastName="Perez"; phone= null; email="juanperez@gmail.com"; @@ -18,7 +18,7 @@ registro de usuario tipo Host ``` dfx canister call backend signUpAsHost '(record { - name="Gerardo"; + firstName="Gerardo"; lastName="Anchorena"; phone= opt 54221548797; email="gerardonchorena@gmail.com"; @@ -46,7 +46,15 @@ dfx canister call backend publishHousing '(record { } } }; - kind = variant {House}} + kind = variant {House}; + properties = record{ + beds= vec{ + variant {Single = 2}; + variant {SofaBed = 2} + }; + bathroom = true + }; +} )' ``` @@ -92,4 +100,3 @@ dfx canister call backend requestReservation '(record { }; })' ``` - From 08c556b5f1e6078e2622600a392b490c4ee8beeb Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 20 Nov 2024 21:16:43 -0300 Subject: [PATCH 23/57] . --- backend/main.mo | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 109a5f8..7e973cd 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -621,7 +621,6 @@ shared ({ caller }) actor class Triourism () = this { } }; - func paymentVerification(txHash: Nat):async Bool{ // TODO protocolo de verificacion de pago true @@ -664,9 +663,11 @@ shared ({ caller }) actor class Triourism () = this { } }; - // TODO confirmacion de reservacion por parte del dueño del Host - // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId}): async { + // // TODO confirmacion de reservacion por parte del dueño del Host + // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId}): async (){ // }; + + }; From a9bfac30f1f32afe740bb5593821538aaa326a26 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 2 Dec 2024 03:44:12 -0300 Subject: [PATCH 24/57] Refactoring --- Anotaciones.md | 36 ++ backend/constants.mo | 7 +- backend/main.mo | 809 +++++++++++++++++++++++++++++-------------- backend/types.mo | 112 +++--- 4 files changed, 660 insertions(+), 304 deletions(-) diff --git a/Anotaciones.md b/Anotaciones.md index 080ee1f..78b0ba1 100644 --- a/Anotaciones.md +++ b/Anotaciones.md @@ -17,3 +17,39 @@ En un contexto de MVP todos los usuarios serán inicializados por defecto como v A: Si el usuario realiza la transaccián, la cual devuelve un transaction hash, se llama a otra funcion de backend enviando el id de la reserva mas el transaction hash. B: Mediante una consulta al Ledger correspondiente a la moneda de pago, desde el bakend se envia el transaction hash, confirmando que los datos de retorno sean los correspondientes a la transaccion solicitada (Campo memo). C: Luego de la confirmacion y verificación se marca como ocupado en el calendario el rango de tiempo de alojamineto + + +--- +#### Modificación del flujo de confirmaciones de reservas. (Confirmación del lado del Host) +##### Ventajas y desventajas + +### Ventajas: +1. El Host puede elegir, para un mismo periodo de alojamiento, el huesped que mejor se acomode a sus conveniencias de entre todos los que hayan requerido ese periodo de alojamiento. + +### Desventajas: +#### Desventajas Para la plataforma: +1. Por cada solicitud de reserva, la platafoma tiene que enviar una notificación al dueño de Host y esperar respuesta +2. Durante el tiempo de espera, para ese mismo periodo de hospedaje solicitado se pueden acumular mas solicitudes, las cuales tienen que ser notificadas también. +3. Que el usuario salga de la plataforma sin haber concretado un pago es motivo suficiente para que no vuelva. +3. Cuando el dueño del Host confirma, la plataforma tiene que notificar tanto al potencial huesped como a los rechazados +4. Al tiempo de demora de la confirmación hay que sumarle el tiempo de demora de confirmacion de la confirmacion por parte del huesped. +4. Es decir, el potencial huesped tiene que reponder de alguna manera a la confirmacion. ¿Mediante un pago? +5. Es incierto el momento en el que se establece definitivamente en el calendario un periodo de alojamiento como ocupado +#### Desventajas Para el dueño: +1. La ventaja de poder elegir es equivalente a la desventaja de tener que elegir en cualquier momento del dia y rápido. Filosoficamente: No es una elección tener que elegir ya +2. Si el tiempo de demora de la confirmación supera los 20 o 30 minutos, es muy probable que en ese momento el potencial huesped ya haya conseguido hospedaje en otro lugar. +3. Posiblemente los rechazados no vuelven nunca más e incluso pidan explicaciones, que de no ser satisfechas consistentemente generen problemas legales. +4. La confirmación de una reserva para un periodo largo de alojamiento y que luego no se materializa en un hospedaje (porque el usuario ya encontro otro lugar u otros motivos) puede tener como consecuencia, el rechazo de multiples solicitudes de alojamiento para ese mismo periodo y que no necesariamente hayan tenido solapamientos entre si. +##### Ejemplo: +##### Solicitud confirmada: ++ dias [20... 30] +##### Solicitudes rechazadas: ++ dias [20... 24], ++ dia 25, ++ dias [27... 28] ++ dia 29, ++ dia 30 + +#### Desventajas Para el Usuario: +1. Que el usuario salga de la plataforma con las manos vacias pudiendo salir con un problema solucionado es algo evitable. + diff --git a/backend/constants.mo b/backend/constants.mo index 0081c89..d7e068a 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -2,15 +2,18 @@ module { public let PayRequest = "Please have 60 minutes to complete the payment process and secure your reservation. After this period, if the transaction has not been completed, the reservation request will be cancelled. Please do not proceed with payment after the deadline has expired."; public let NotAvalableAllDays = "At least one of the requested days is not available"; - public let NotHosting = "There is no hosting associated with the ID provided"; + public let NotHousing = "There is no housing associated with the ID provided"; public let NotReservation = "There is no reservation associated with the ID provided"; public let PaginationOutOfRange = "Pagination index out of range"; public let CallerNotHousingOwner = "The caller is not the owner of the hosting"; public let UnauthorizedCaller = "Unauthorized user"; public let NotVerifiedUser = "The user is not verified"; public let NotUser = "Unregistered user"; + public let NotHostUser = "User is not Host User"; public let CallerIsNotrequester = "The caller does not match the reservation requester"; public let NotHost = "Te caller in not Host User profile"; - public let InactiveHousing = "Housing is temporarily disabled" + public let InactiveHousing = "Housing is temporarily disabled"; + public let ZeroIsNotAllowed = "Is not greater than zero"; + public let IsNotpublishable = "Not publishable due to missing data" }; diff --git a/backend/main.mo b/backend/main.mo index 7e973cd..7ecae96 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -1,19 +1,20 @@ import Prim "mo:⛔"; import Map "mo:map/Map"; import Set "mo:map/Set"; -import { phash; nhash } "mo:map/Map"; +import { phash; nhash; thash } "mo:map/Map"; import Principal "mo:base/Principal"; import Buffer "mo:base/Buffer"; import Blob "mo:base/Blob"; import Types "types"; -import Int "mo:base/Int"; +// import Int "mo:base/Int"; import { now } "mo:base/Time"; -import Rand "mo:random/Rand"; +// import Rand "mo:random/Rand"; import msg "constants"; ////////////// Debug imports //////////////// import { print } "mo:base/Debug"; import Array "mo:base/Array"; +import Iter "mo:base/Iter"; shared ({ caller }) actor class Triourism () = this { @@ -21,13 +22,16 @@ shared ({ caller }) actor class Triourism () = this { type UserKind = Types.UserKind; type SignUpResult = Types.SignUpResult; type CalendaryPart = Types.CalendaryPart; + type Calendary = {dayZero: Int; reservedDays: [Int]}; type ReservationDataInput = Types.ReservationDataInput; type Reservation = Types.Reservation; type HousingId = Types.HousingId; - type HousingDataInit = Types.HousingDataInit; + // type HousingDataInit = Types.HousingDataInit; type Housing = Types.Housing; type HousingResponse = Types.HousingResponse; type HousingPreview = Types.HousingPreview; + type HousingCreateData = Types.HousingCreateData; + type HousingTypesMap = Map.Map; type UpdateResult = Types.UpdateResult; type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; hasNext: Bool}; #Err: Text}; @@ -36,9 +40,6 @@ shared ({ caller }) actor class Triourism () = this { type ReservationResult = { #Ok: { reservationId: Nat; - housingId: HousingId; - data: Reservation; - paymentCode: Nat; msg: Text; }; #Err: Text; @@ -48,8 +49,8 @@ shared ({ caller }) actor class Triourism () = this { stable let DEPLOYER = caller; - let NANO_SEG_PER_HOUR = 60 * 60 * 1_000_000_000; - let ramdomGenerator = Rand.Rand(); + // let NANO_SEG_PER_HOUR = 60 * 60 * 1_000_000_000; + // let ramdomGenerator = Rand.Rand(); // stable var minReservationLeadTime = 24 * 60 * 60 * 1_000_000_000; // 24 horas en nanosegundos @@ -57,7 +58,10 @@ shared ({ caller }) actor class Triourism () = this { ignore Set.put(admins, phash, caller); stable let users = Map.new(); stable let housings = Map.new(); - // stable let calendars = Map.new(); + stable let housingTypesByHostOwner = Map.new(); + stable let calendars = Map.new(); + stable let reservationsRequests = Map.new(); + stable let reservationsConfirmed = Map.new(); stable var lastHousingId = 0; @@ -133,76 +137,71 @@ shared ({ caller }) actor class Triourism () = this { func isUser(p: Principal): Bool { Map.has(users, phash, p)}; - func initCalendary(): [var CalendaryPart]{ - Prim.Array_init( - 30, - {day= 0; available = true; reservation = null} - ) - }; - - func freezeCalendar(c: [var CalendaryPart]): [CalendaryPart]{ - Prim.Array_tabulate(c.size(), func i = c[i]) - }; + // func initCalendary(): [var CalendaryPart]{ + // Prim.Array_init( + // 30, + // {day= 0; available = true; reservation = null} + // ) + // }; - func updateCalendar(c: [var CalendaryPart]): [var CalendaryPart] { - var indexDay = 0; - var displace = 0; - while(indexDay < c.size() and now() + NANO_SEG_PER_HOUR * 24 > c[indexDay].day){ - displace += 1; - indexDay += 1; - }; - let outPutArray = c; - var index = 0; - while(index + displace < c.size()){ - outPutArray[index] := c[index + displace]; - outPutArray[index + displace] := {day = 0; available = true; reservation = null}; - index += 1; - }; - outPutArray; - }; + // func freezeCalendar(c: [var CalendaryPart]): [CalendaryPart]{ + // Prim.Array_tabulate(c.size(), func i = c[i]) + // }; - func availableAllDaysResquest(checkin: Int, checkout: Int): Bool{ - // TODO Consultar caledario del Hosting y reservationRequests - true - }; + // func updateCalendar(c: [var CalendaryPart]): [var CalendaryPart] { + // var indexDay = 0; + // var displace = 0; + // while(indexDay < c.size() and now() + NANO_SEG_PER_HOUR * 24 > c[indexDay].day){ + // displace += 1; + // indexDay += 1; + // }; + // let outPutArray = c; + // var index = 0; + // while(index + displace < c.size()){ + // outPutArray[index] := c[index + displace]; + // outPutArray[index + displace] := {day = 0; available = true; reservation = null}; + // index += 1; + // }; + // outPutArray; + // }; - func intToNat(x: Int): Nat{ - Prim.nat64ToNat(Prim.intToNat64Wrap(x)) - }; + // func intToNat(x: Int): Nat{ + // Prim.nat64ToNat(Prim.intToNat64Wrap(x)) + // }; /////////// Probar //////////// - func insertReservationToCalendar(_calendar: [var CalendaryPart], reserv: Reservation): {#Ok: [var CalendaryPart]; #Err } { - let calendar = updateCalendar(_calendar); // Asegura que el primer elemento sea el día actual - let checkInDay = intToNat((reserv.checkIn - now()) / (24 * NANO_SEG_PER_HOUR)); - let daysQty = intToNat((reserv.checkOut - reserv.checkIn) / (24 * NANO_SEG_PER_HOUR)); - var index = checkInDay; - var okBaby = true; + // func insertReservationToCalendar(_calendar: [var CalendaryPart], reserv: Reservation): {#Ok: [var CalendaryPart]; #Err } { + // let calendar = updateCalendar(_calendar); // Asegura que el primer elemento sea el día actual + // let checkInDay = intToNat((reserv.checkIn - now()) / (24 * NANO_SEG_PER_HOUR)); + // let daysQty = intToNat((reserv.checkOut - reserv.checkIn) / (24 * NANO_SEG_PER_HOUR)); + // var index = checkInDay; + // var okBaby = true; - // Comprobar disponibilidad - while (index < checkInDay + daysQty and index < calendar.size()) { - if (not calendar[index].available) { - okBaby := false; - index += daysQty; // Salir del bucle si no está disponible - } else { - index += 1; - } - }; + // // Comprobar disponibilidad + // while (index < checkInDay + daysQty and index < calendar.size()) { + // if (not calendar[index].available) { + // okBaby := false; + // index += daysQty; // Salir del bucle si no está disponible + // } else { + // index += 1; + // } + // }; - if (okBaby) { - index := checkInDay; - while (index < checkInDay + daysQty and index < calendar.size()) { - let calendaryPart: CalendaryPart = { - reservation = ?reserv; - day = index * 24 * NANO_SEG_PER_HOUR; - available = false; - }; - calendar[index] := calendaryPart; - index += 1; - }; - #Ok(calendar) - } else { - #Err - } - }; + // if (okBaby) { + // index := checkInDay; + // while (index < checkInDay + daysQty and index < calendar.size()) { + // let calendaryPart: CalendaryPart = { + // reservation = ?reserv; + // day = index * 24 * NANO_SEG_PER_HOUR; + // available = false; + // }; + // calendar[index] := calendaryPart; + // index += 1; + // }; + // #Ok(calendar) + // } else { + // #Err + // } + // }; /////////////////////////// Manage admins functions ///////////////////////////////// @@ -228,13 +227,13 @@ shared ({ caller }) actor class Triourism () = this { /////////////////////////////// Verification process ///////////////////////////////////// // TODO actualmente todos los usuarios se inicializan como verificados - func userIsVerificated(u: Principal): Bool { - let user = Map.get(users, phash, u); - switch user{ - case null { false }; - case (?user) { user.verified}; - }; - }; + // func userIsVerificated(u: Principal): Bool { + // let user = Map.get(users, phash, u); + // switch user{ + // case null { false }; + // case (?user) { user.verified}; + // }; + // }; //////////////////////////////// CRUD Housing //////////////////////////////////////////// @@ -298,45 +297,94 @@ shared ({ caller }) actor class Triourism () = this { // }; // }; - public shared ({ caller }) func publishHousing(data: HousingDataInit): async PublishResult { - let user = Map.get(users, phash, caller); + func isHostUser(p: Principal): Bool { + let user = Map.get(users, phash, p); switch user { - case null { - return #Err(msg.NotUser); - }; - case ( ?user ){ - var index = 0; - for (kind in user.kinds.vals()){ + case null { false }; + case (?user) { + for (kind in user.kinds.vals()) { switch kind { - case (#Host(housingIdList)){ - lastHousingId += 1; - let newHousing: Housing = { data with - reservationRequests = Map.new(); - owner = caller; - id = lastHousingId; - calendar: [var CalendaryPart] = initCalendary(); - photos: [Blob] = []; - thumbnail: Blob = ""; - active = true; - reviews: [Text] = []; - }; - ignore Map.put(housings, nhash, lastHousingId, newHousing); - let housingIdListUpdate = Prim.Array_tabulate( - housingIdList.size() + 1, - func x = if(x == 0) { lastHousingId } else {housingIdList[x - 1]} - ); - let updateKinds = Prim.Array_tabulate( - user.kinds.size(), - func x = if(x == index) {#Host(housingIdListUpdate)} else {user.kinds[index]} - ); - ignore Map.put(users, phash, caller, {user with kinds = updateKinds}); - return #Ok(newHousing.id) - }; - case _ {}; + case (#Host(_)) { return true }; + case _ {} }; - index += 1; }; - return #Err(msg.NotHost) + return false + } + } + }; + + func addIdToHostKind(arr: [Types.UserKind], id: HousingId): [Types.UserKind]{ + let bufferKinds = Buffer.fromArray([]); + for (k in arr.vals()) { + switch k { + case (#Host(ids)){ + let setIds = Set.fromIter(ids.vals(), nhash); + ignore Set.put(setIds, nhash, id); + bufferKinds.add(#Host(Set.toArray(setIds))) + }; + case (k) {bufferKinds.add(k)} + }; + }; + Buffer.toArray(bufferKinds); + }; + + let defaultHousinValues = { + active: Bool = false; + rules: [Types.Rule] = []; + prices: [Types.Price] = []; + checkIn: Nat = 15; + checkOut: Nat = 12; + address: Text = ""; + properties: [Types.Property] = []; + amenities: [Text] = []; + calendar: [var Types.CalendaryPart] = [var]; + }; + + public shared ({ caller }) func createHousings(dataInit: HousingCreateData): async {#Ok: Nat; #Err: Text} { + let user = Map.get(users, phash, caller); + switch user { + case null {#Err(msg.NotUser)}; + case (?user) { + if (not isHostUser(caller)) { return #Err(msg.NotHostUser)}; + lastHousingId += 1; + + let newHousing: Housing = { + dataInit and + defaultHousinValues with + id = lastHousingId; + owner = caller; + }; + let kinds = addIdToHostKind(user.kinds, lastHousingId); + ignore Map.put(users, phash, caller, {user with kinds }); + ignore Map.put(housings, nhash, lastHousingId,newHousing ); + #Ok(lastHousingId) + } + } + }; + + func isPublishable(housing: Housing): Bool { + (housing.prices.size() > 0) and + (housing.address != "") and + (housing.properties.size() > 0) + }; + + public shared ({ caller }) func publishHousing(housingId: HousingId): async {#Ok; #Err: Text}{ + let user = Map.get(users, phash, caller); + switch user { + case null { #Err(msg.NotUser)}; + case ( ?user ) { + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(isPublishable(housing)) { + ignore Map.put(housings, nhash, housingId, {housing with ctive = true}); + #Ok + } else { + #Err(msg.IsNotpublishable) + } + } + } } } @@ -346,7 +394,7 @@ shared ({ caller }) actor class Triourism () = this { let housing = Map.get(housings, nhash, id); switch housing { case null { - #Err("Invalid Housing Id") + #Err(msg.NotHousing) }; case (?housing) { if(housing.owner != caller){ @@ -367,7 +415,7 @@ shared ({ caller }) actor class Triourism () = this { let housing = Map.get(housings, nhash, id); switch housing { case null { - #Err(msg.NotHosting) + #Err(msg.NotHousing) }; case (?housing) { if(housing.owner != caller){ @@ -383,7 +431,7 @@ shared ({ caller }) actor class Triourism () = this { let housing = Map.get(housings, nhash, id); switch housing { case null { - return #Err(msg.NotHosting); + return #Err(msg.NotHousing); }; case (?housing) { if(housing.owner != caller){ return #Err(msg.UnauthorizedCaller) }; @@ -393,65 +441,191 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller }) func setMinReservationLeadTime({id: HousingId; hours: Nat}):async {#Ok; #Err: Text} { - let housing = Map.get(housings, nhash, id); + public shared ({ caller }) func setRulesForHousing({id: HousingId; rules: [Types.Rule]}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, id); switch housing { case null { - return #Err(msg.NotHosting); + return #Err(msg.NotHousing); }; case (?housing) { - if(housing.owner != caller){ + if(housing.owner != caller){ return #Err(msg.UnauthorizedCaller) }; + ignore Map.put(housings, nhash, id, {housing with rules}); + return #Ok + }; + } + }; + + // public shared ({ caller }) func setMinReservationLeadTime({id: HousingId; hours: Nat}):async {#Ok; #Err: Text} { + // let housing = Map.get(housings, nhash, id); + // switch housing { + // case null { + // return #Err(msg.NotHosting); + // }; + // case (?housing) { + // if(housing.owner != caller){ + // return #Err(msg.CallerNotHousingOwner); + // }; + // ignore Map.put( + // housings, + // nhash, + // id, + // {housing with minReservationLeadTimeNanoSeg = hours * NANO_SEG_PER_HOUR}); + // #Ok; + // } + + // } + // }; + + public shared ({ caller }) func setHousingStatus({id: HousingId; active: Bool}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, id); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; - ignore Map.put( - housings, - nhash, - id, - {housing with minReservationLeadTimeNanoSeg = hours * NANO_SEG_PER_HOUR}); - #Ok; + ignore Map.put(housings, nhash, id, {housing with active}); + #Ok } - } }; - public shared ({ caller }) func setHousingStatus({id: HousingId; active: Bool}): async {#Ok; #Err: Text}{ - let housing = Map.get(housings, nhash, id); + public shared ({ caller }) func setChekInCheckOut({housingId: HousingId; checkIn: Nat; checkOut: Nat}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, housingId); switch housing { - case null { #Err(msg.NotHosting)}; + case null { #Err(msg.NotHousing)}; case ( ?housing ) { if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; - ignore Map.put(housings, nhash, id, {housing with active}); + ignore Map.put(housings, nhash, housingId, {housing with checkIn; checkOut}); #Ok } } }; - public shared ({ caller }) func updateHosting({id: Nat; data: HousingDataInit}): async {#Ok; #Err: Text}{ - let housing = Map.get(housings, nhash, id); + public shared ({ caller }) func setAddress({housingId: HousingId; address: Text}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, housingId); switch housing { - case null { #Err(msg.NotHosting)}; + case null { #Err(msg.NotHousing)}; case ( ?housing ) { if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; - ignore Map.put(housings, nhash, id, {housing with data}); + ignore Map.put(housings, nhash, housingId, {housing with address}); #Ok } } + + }; + + func putHousingType(user: Principal, propertiesOfType: Types.Property, housingId: HousingId ){ + let housingTypesMap: HousingTypesMap = + switch(Map.get(housingTypesByHostOwner, phash, user)){ + case null {Map.new() }; + case ( ?map ) { map } + }; + let housingType: {properties: Types.Property; housingIds: [HousingId]} = + switch (Map.get( + housingTypesMap, + thash, + propertiesOfType.nameType)){ + case null {{properties = propertiesOfType; housingIds = [housingId]}}; + case (?housingType) { + let housingIds= Prim.Array_tabulate( + housingType.housingIds.size() + 1, + func x = if(x == 0) { housingId } else { housingType.housingIds[x - 1] } + ); + {properties = propertiesOfType; housingIds} + } + }; + ignore Map.put( + housingTypesMap, thash, propertiesOfType.nameType, housingType + ); + }; + + public shared ({ caller }) func setTypeHousing({housingId: HousingId; qty: Nat; propertiesOfType: Types.Property}): async {#Ok; #Err: Text} { + + let user = Map.get(users, phash, caller); + switch user { + case null {return #Err(msg.NotUser)}; + case ( ?user ) { + if (qty < 1) { return #Err(msg.ZeroIsNotAllowed)}; + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put(housings, nhash, housingId, {housing with propertiesOfType}); + // agregamos la habitacion actual al nuevo tipo creado + putHousingType(caller, propertiesOfType, housingId); + // Clonacion de habitacion segun qty con valores por defecto para las nuevas + var index = 1; // la habitacion a partir de la que se define el tipo no se cuenta porque ya tiene su propio id + while (index < qty ){ + lastHousingId += 1; + let newHousing: Housing = { + housing with + id = lastHousingId; + active = false; + propertiesOfType + }; + putHousingType(caller, propertiesOfType, lastHousingId); + ignore Map.put(users, phash, caller, user ); + ignore Map.put(housings, nhash, lastHousingId,newHousing ); + + index += 1; + }; + #Ok + } + } + + } + }; + }; + + public shared ({ caller }) func removeHousingType(housingType: Text): async {#Ok; #Err: Text}{ + let myHousingTypesMap = Map.get(housingTypesByHostOwner, phash, caller); + switch myHousingTypesMap { + case null {#Err("Not housing types")}; + case (?housingTypesMap){ + let removedType = Map.remove( + housingTypesMap, thash, housingType + ); + switch removedType { + case null { #Err("Not housing type")}; + case (?removedType) {#Ok} + } + } + } + }; + + public shared ({ caller }) func setAmenities(amenities: [Text], housingId: HousingId): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put(housings, nhash, housingId, {housing with amenities}); + #Ok + } + } + }; ////////////////////////////////// Getters /////////////////////////////////////////////// - public query func getHousingPaginate(page: Nat): async ResultHousingPaginate { - if(Map.size(housings) < page * 10){ + public query func getHousingPaginate({page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { + if(Map.size(housings) < page * qtyPerPage){ return #Err(msg.PaginationOutOfRange) }; let values = Map.toArray(housings); let bufferHousingPreview = Buffer.fromArray([]); - var index = page * 10; - while (index < values.size() and index < (page + 1) * 10){ + var index = page * qtyPerPage; + while (index < values.size() and index < (page + 1) * qtyPerPage){ if(values[index].1.active){ bufferHousingPreview.add(values[index].1); }; @@ -459,14 +633,14 @@ shared ({ caller }) actor class Triourism () = this { }; #Ok{ array = Buffer.toArray(bufferHousingPreview); - hasNext = ((page + 1) * 10 < values.size()) + hasNext = ((page + 1) * qtyPerPage < values.size()) } }; public query func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { let housing = Map.get(housings, nhash, housingId); return switch housing { - case null { #Err(msg.NotHosting)}; + case null { #Err(msg.NotHousing)}; case (?housing) { if(not housing.active and housing.owner != caller) { return #Err(msg.InactiveHousing) @@ -474,12 +648,7 @@ shared ({ caller }) actor class Triourism () = this { if(photoIndex == 0){ let housingResponse: HousingResponse = #Start({ housing with - calendar = freezeCalendar(housing.calendar); - photo = if(housing.photos.size() > 0){ - housing.photos[0] - } else { - Blob.fromArray([0]) - }; + photos = if(housing.photos.size() > 0) { [housing.photos[0]] } else { [] }; hasNextPhoto = (housing.photos.size() > photoIndex + 1) }); #Ok(housingResponse); @@ -495,6 +664,50 @@ shared ({ caller }) actor class Triourism () = this { } }; + public shared ({ caller }) func getMyHousingsByType({_housingType: Text; page: Nat}): async ResultHousingPaginate{ + let housingTypeMap = Map.get(housingTypesByHostOwner, phash, caller); + switch housingTypeMap { + case null {#Err("Not Housing Types")}; + case (?map) { + let housingsType = Map.get( + map, + thash, + _housingType + ); + switch housingsType { + case null { #Err("Not housing type")}; + case (?housingType) { + getPaginateHousings(housingType.housingIds, page) + } + } + }; + } + }; + + func getPaginateHousings(ids: [HousingId], page: Nat): ResultHousingPaginate { + let resultBuffer = Buffer.fromArray([]); + var index = page * 10; + while(index < (page + 1)* 10 and index < ids.size()){ + switch (Map.get(housings, nhash, ids[index])) { + case null {}; + case (?housing) { + resultBuffer.add( + { + id = ids[index]; + address = housing.address; + thumbnail = housing.thumbnail; + prices = housing.prices; + } + ) + } + }; + index += 1; + }; + let hasNext = ids.size() > (page + 1)* 10; + #Ok({array = Buffer.toArray(resultBuffer); hasNext: Bool}); + }; + + func getHousingsPaginateByOwner({owner: Principal; page: Nat}): ResultHousingPaginate { let user = Map.get(users, phash, owner); switch user { @@ -529,139 +742,209 @@ shared ({ caller }) actor class Triourism () = this { getHousingsPaginateByOwner({owner = caller; page}) }; - public shared query ({ caller }) func getMyHousingDisponibility({days: [Nat]; page: Nat}): async ResultHousingPaginate{ + func updateCalendary(housingId: HousingId, calendary: Calendary): Calendary { + let curranDay = now(); + let pastDays = (curranDay - calendary.dayZero) / (24 * 60 * 60 * 1_000_000_000); + if(pastDays > 0){ + var updateArray = Array.filter( + calendary.reservedDays, + func(x: Int): Bool {x > pastDays} + ); + updateArray := Prim.Array_tabulate( + updateArray.size(), + func (x: Nat) = updateArray[x] - pastDays + ); + let updateCalendar = {dayZero = curranDay; reservedDays = updateArray}; + ignore Map.put(calendars, nhash, housingId, updateCalendar); + return updateCalendar; + }; + calendary + }; + + func checkDisponibility(housingId: HousingId, chechIn: Int, checkOut: Int): Bool { + switch (Map.get(calendars, nhash, housingId)) { + case null { false }; + case (?calendar) { + let updatedCalendary = updateCalendary(housingId, calendar); + var checkDay = chechIn; + while (checkDay <= checkOut) { + for(occuped in updatedCalendary.reservedDays.vals()){ + if(checkDay == occuped) { + return false; + }; + }; + }; + return true + } + } + }; + + public shared query ({ caller }) func getMyHousingDisponibility({checkIn: Nat; checkOut: Nat; page: Nat}): async ResultHousingPaginate{ let response = getHousingsPaginateByOwner({owner = caller; page}); switch response { - case ( #Ok({array; hasNext} )) { + case (#Ok({array; hasNext} )){ let bufferResults = Buffer.fromArray([]); for(hostPreview in array.vals()){ - let housing = Map.get(housings, nhash, hostPreview.id); - switch housing { - case ( ?housing ){ - if(days.size() == 0) { - if(housing.calendar[0].available){ - bufferResults.add(hostPreview); - } - } else { - var allowed = true; - for (d in days.vals()){ - if (not housing.calendar[d].available) { - allowed := false - }; - }; - if(allowed) { - bufferResults.add(hostPreview) - } - } - }; - case _ {} - } + if(checkDisponibility(hostPreview.id, checkIn, checkOut)){ + bufferResults.add(hostPreview); + }; }; #Ok({array = Buffer.toArray(bufferResults); hasNext}) }; - case Err { Err } + case (#Err(msg)) { #Err(msg) } } }; - public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ + public query func getAmenities(housingId: HousingId): async [Text] { let housing = Map.get(housings, nhash, housingId); switch housing { - case null {#Err(msg.NotHosting)}; - case ( ?housing ) { - if(housing.owner != caller ){ - return #Err(msg.CallerNotHousingOwner); - }; - #Ok(Map.toArray(housing.reservationRequests)) + case null { [] }; + case (?housing) { + housing.amenities } } }; + + + // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ + // let housing = Map.get(housings, nhash, housingId); + // switch housing { + // case null {#Err(msg.NotHousing)}; + // case ( ?housing ) { + // if(housing.owner != caller ){ + // return #Err(msg.CallerNotHousingOwner); + // }; + // #Ok(Map.toArray(housing.reservationRequests)) + // } + // } + // }; + ///////////////////////////////// Reservations /////////////////////////////////////////// - public shared ({ caller }) func requestReservation({housingId: HousingId; data: ReservationDataInput}):async ReservationResult { - let housing = Map.get(housings, nhash, housingId); - switch housing { - case null { - #Err(msg.NotHosting); - }; - case (?housing) { - print("housing"); - /////// housing calendar update / housing Map update ////// - let calendar = updateCalendar(housing.calendar); - ignore Map.put(housings, nhash, housingId, {housing with calendar}); - - ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// - print("Momento actual en NanoSeg: " # Int.toText(now())); - print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSec )); - print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSec))); - print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - if(now() + housing.minReservationLeadTimeNanoSec > data.checkIn){ - return #Err("Reservations are requested at least " # - Int.toText(housing.minReservationLeadTimeNanoSec /(NANO_SEG_PER_HOUR)) # - " hours in advance."); - }; - if(availableAllDaysResquest(data.checkIn, data.checkOut)){ - let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); - let reservation = {data with applicant = caller}; - let responseReservation = { - housingId; - reservationId; - data = reservation; - msg = msg.PayRequest; - paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) - }; - ignore Map.put(housing.reservationRequests, nhash, reservationId, reservation ); - #Ok( responseReservation ) - } else { - #Err( msg.NotAvalableAllDays); - } - }; - } - }; + // public shared ({ caller }) func requestReservationOld({housingId: HousingId; data: ReservationDataInput}):async ReservationResult { + // let housing = Map.get(housings, nhash, housingId); + // switch housing { + // case null { + // #Err(msg.NotHosting); + // }; + // case (?housing) { + // print("housing"); + // /////// housing calendar update / housing Map update ////// + // let calendar = updateCalendar(housing.calendar); + // ignore Map.put(housings, nhash, housingId, {housing with calendar}); + + // ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// + // print("Momento actual en NanoSeg: " # Int.toText(now())); + // print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSec )); + // print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSec))); + // print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); + // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // if(now() + housing.minReservationLeadTimeNanoSec > data.checkIn){ + // return #Err("Reservations are requested at least " # + // Int.toText(housing.minReservationLeadTimeNanoSec /(NANO_SEG_PER_HOUR)) # + // " hours in advance."); + // }; + // if(availableAllDaysResquest(data.checkIn, data.checkOut)){ + // let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); + // let reservation = {data with applicant = caller}; + // let responseReservation = { + // reservationId; + // msg = "Espere" + // }; + // ignore Map.put(housing.reservationRequests, nhash, reservationId, reservation ); + // #Ok( responseReservation ) + // } else { + // #Err( msg.NotAvalableAllDays); + // } + // }; + // } + // }; - func paymentVerification(txHash: Nat):async Bool{ - // TODO protocolo de verificacion de pago - true - }; + // public shared ({ caller }) func requestReservation({housingId: HousingId; data: ReservationDataInput}): async ReservationResult{ + // let housing = Map.get(housings, nhash, housingId); + // switch housing { + // case null { + // #Err(msg.NotHosting); + // }; + // case (?housing) { + // print("housing"); + // /////// housing calendar update / housing Map update ////// + // let calendar = updateCalendar(housing.calendar); + // ignore Map.put(housings, nhash, housingId, {housing with calendar}); + + // ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// + // print("Momento actual en NanoSeg: " # Int.toText(now())); + // print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSec )); + // print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSec))); + // print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); + // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // if(now() + housing.minReservationLeadTimeNanoSec > data.checkIn){ + // return #Err("Reservations are requested at least " # + // Int.toText(housing.minReservationLeadTimeNanoSec /(NANO_SEG_PER_HOUR)) # + // " hours in advance."); + // }; + // if(availableAllDaysResquest(data.checkIn, data.checkOut)){ + // let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); + // let reservation = {data with applicant = caller}; + // let responseReservation = { + // housingId; + // reservationId; + // data = reservation; + // msg = msg.PayRequest; + // paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) + // }; + // ignore Map.put(housing.reservationRequests, nhash, reservationId, reservation ); + // #Ok( responseReservation ) + // } else { + // #Err( msg.NotAvalableAllDays); + // } + // }; + // } + // }; + // func paymentVerification(txHash: Nat):async Bool{ + // // TODO protocolo de verificacion de pago + // true + // }; - public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId; txHash: Nat}): async {#Ok; #Err: Text}{ - let housing = Map.get(housings, nhash, hostId); - switch housing { - case null { #Err(msg.NotHosting) }; - case ( ?housing ) { - let updatedCalendar = updateCalendar(housing.calendar); - ignore Map.put(housings, nhash, hostId, {housing with updatedCalendar}); - let reserv = Map.remove(housing.reservationRequests, nhash, reservId); - switch reserv { - case null { #Err(msg.NotReservation) }; - case ( ?reserv ) { - if(caller != reserv.applicant) { - return #Err(msg.CallerIsNotrequester) - }; - // TODO Verificacion datos de pago a traves del txHhahs - if (await paymentVerification(txHash)){ - print("insertando la reserva en el calendario"); - let calendar = insertReservationToCalendar(updatedCalendar, reserv); - switch calendar { - case (#Ok(calendar)) { - - ignore Map.put(housings, nhash, hostId, {housing with calendar}); - return #Ok - }; - case (_) { - ignore Map.put(housing.reservationRequests, nhash, reservId, reserv); - return #Err("Error") - } - } - }; - #Err("Incorrect payment verification") - } - } - } - } - }; + // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId; txHash: Nat}): async {#Ok; #Err: Text}{ + // let housing = Map.get(housings, nhash, hostId); + // switch housing { + // case null { #Err(msg.NotHosting) }; + // case ( ?housing ) { + // let updatedCalendar = updateCalendar(housing.calendar); + // ignore Map.put(housings, nhash, hostId, {housing with updatedCalendar}); + // let reserv = Map.remove(housing.reservationRequests, nhash, reservId); + // switch reserv { + // case null { #Err(msg.NotReservation) }; + // case ( ?reserv ) { + // if(caller != reserv.applicant) { + // return #Err(msg.CallerIsNotrequester) + // }; + // // TODO Verificacion datos de pago a traves del txHhahs + // if (await paymentVerification(txHash)){ + // print("insertando la reserva en el calendario"); + // let calendar = insertReservationToCalendar(updatedCalendar, reserv); + // switch calendar { + // case (#Ok(calendar)) { + + // ignore Map.put(housings, nhash, hostId, {housing with calendar}); + // return #Ok + // }; + // case (_) { + // ignore Map.put(housing.reservationRequests, nhash, reservId, reserv); + // return #Err("Error") + // } + // } + // }; + // #Err("Incorrect payment verification") + // } + // } + // } + // } + // }; // // TODO confirmacion de reservacion por parte del dueño del Host // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId}): async (){ diff --git a/backend/types.mo b/backend/types.mo index fcd57a2..c79e7ca 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -38,33 +38,75 @@ module { // }; ///////////////////////////////// Housing ///////////////////////////////// - - public type HousingDataInit = { - minReservationLeadTimeNanoSec: Int; //valor en nanoSeg de anticipación para efectuar una reserva - address: Text; - prices: [Price]; - kind: HousingKind; - maxCapacity: Nat; - description: Text; - rules: [Text]; - amenities: [Text]; - properties: {beds: [BedKind]; bathroom: Bool }; - }; + public type HousingCreateData = { + namePlace: Text; + nameHost: Text; + descriptionPlace: Text; + descriptionHost: Text; + link: Text; + photos: [Blob]; + thumbnail: Blob; - public type Housing = HousingDataInit and { + }; - id: Nat; // Example L234324 - owner: Principal; - calendar: [var CalendaryPart]; - reservationRequests: Map.Map; - reviews: [Text]; - photos: [Blob]; - thumbnail: Blob; // Se recomienda la foto principal en tamaño reducido + public type Rule = { + #PetsAllowed: Bool; + #SmookingAllowed: Bool; + #PartiesAllowed: Bool; + #AdditionalGuests: Bool; + #NoiseAfter10pm: Bool; + #ParkOnTheStreet: Bool; + #CustomRule: {rule: Text; allowed: Bool}; + }; + + public type Housing = HousingCreateData and { active: Bool; + id: Nat; + prices: [Price]; + owner: Principal; + rules: [Rule]; + checkIn: Int; + checkOut: Int; + address: Text; + properties: [Property]; + amenities: [Text] }; + + public type Property = { + nameType: Text; + beds: [BedKind]; + bathroom: Bool; + maxGuest: Nat; + extraGuest: Nat; + }; + + // public type HousingDataInit = { + // minReservationLeadTimeNanoSec: Int; //valor en nanoSeg de anticipación para efectuar una reserva + // address: Text; + // prices: [Price]; + // kind: HousingKind; + // maxCapacity: Nat; + // description: Text; + // rules: [Text]; + // amenities: [Text]; + // properties: [Property]; + // }; + + // public type Housing = HousingDataInit and { + + // id: Nat; // Example L234324 + // owner: Principal; + // calendar: [var CalendaryPart]; + // reservationRequests: Map.Map; + // reviews: [Text]; + // photos: [Blob]; + // thumbnail: Blob; // Se recomienda la foto principal en tamaño reducido + // active: Bool; + // }; + type BedKind = { #Single: Nat; #Matrimonial: Nat; @@ -73,13 +115,8 @@ module { }; public type HousingResponse = { - #Start : HousingDataInit and {id: Nat; - owner: Principal; - calendar: [CalendaryPart]; - photo: Blob; - thumbnail: Blob; + #Start : Housing and { hasNextPhoto: Bool; - reviews: [Text]; }; #OnlyPhoto :{ photo: Blob; @@ -101,11 +138,11 @@ module { #Err: Text; }; - public type HousingKind = { - #House; - #Hotel_room: Text; //Ejemplo #Hotel_room("Single Room") - #RoomWithSharedSpaces: [Rules]; //Hostels/Pensiones - }; + // public type HousingKind = { + // #House; + // #Hotel_room: Text; //Ejemplo #Hotel_room("Single Room") + // #RoomWithSharedSpaces: [Rule]; //Hostels/Pensiones + // }; public type Price = { #PerNight: Nat; @@ -113,10 +150,10 @@ module { #CustomPeriod: [{dais: Nat; price: Nat}]; }; - public type Rules = { // Ejemplo de Rule: {key = "Horarios"; value = "Sin ruidos molestos entre las 22:00 y las 8:00"} - key: Text; - value: Text - }; + // public type Rules = { // Ejemplo de Rule: {key = "Horarios"; value = "Sin ruidos molestos entre las 22:00 y las 8:00"} + // key: Text; + // value: Text + // }; public type ReviewsId = Text; ///////////////////////////////// Reservations ///////////////////////////// @@ -134,13 +171,10 @@ module { // La primer posicion es siempre el dia actual con lo cual cada vez que se consulta se tiene que actualizar antes // Para facilitar la implementacion inicial se considera una lista de Disponibility mutable de 30 posiciones public type Disponibility = {day: Int; available: Bool}; - // public type Calendar = [var Disponibility]; - // El rango de no disponibilidad se establece con el campo day y el campo checkOut de reservation + public type CalendaryPart = Disponibility and {reservation: ?Reservation}; - // public type Calendary = [var CalendaryPart]; - // public type FrozenCalendar = [CalendaryPart] From 0720f9b9afd93a30fb5fa53ce2e2424fd8ffe583 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 5 Dec 2024 19:44:11 -0300 Subject: [PATCH 25/57] Fix and rename assignHousingType, bathroom type... etc --- backend/constants.mo | 3 +- backend/main.mo | 110 +++++++++++++++++++++++++++++++------------ backend/types.mo | 25 ++++++++-- 3 files changed, 103 insertions(+), 35 deletions(-) diff --git a/backend/constants.mo b/backend/constants.mo index d7e068a..81000bb 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -14,6 +14,7 @@ module { public let NotHost = "Te caller in not Host User profile"; public let InactiveHousing = "Housing is temporarily disabled"; public let ZeroIsNotAllowed = "Is not greater than zero"; - public let IsNotpublishable = "Not publishable due to missing data" + public let IsNotpublishable = "Not publishable due to missing data"; + public let HousingTypeExist = "The housing type already exists"; }; diff --git a/backend/main.mo b/backend/main.mo index 7ecae96..5f2cdba 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -328,19 +328,37 @@ shared ({ caller }) actor class Triourism () = this { Buffer.toArray(bufferKinds); }; + func addressEqual(a: Types.Location, b: Types.Location ) : Bool { + a.country == b.country and + a.city == b.city and + a.neighborhood == b.neighborhood and + a.zipCode == b.zipCode and + a.externalNumber == b.externalNumber and + a.internalNumber == b.internalNumber + }; + + let NULL_LOCATION = { + country = ""; + city = ""; + neighborhood = ""; + zipCode = 0; street = ""; + externalNumber = 0; + internalNumber = 0 + }; + let defaultHousinValues = { active: Bool = false; rules: [Types.Rule] = []; prices: [Types.Price] = []; checkIn: Nat = 15; checkOut: Nat = 12; - address: Text = ""; + address: Types.Location = NULL_LOCATION; properties: [Types.Property] = []; amenities: [Text] = []; calendar: [var Types.CalendaryPart] = [var]; }; - public shared ({ caller }) func createHousings(dataInit: HousingCreateData): async {#Ok: Nat; #Err: Text} { + public shared ({ caller }) func createHousing(dataInit: HousingCreateData): async {#Ok: Nat; #Err: Text} { let user = Map.get(users, phash, caller); switch user { case null {#Err(msg.NotUser)}; @@ -364,7 +382,7 @@ shared ({ caller }) actor class Triourism () = this { func isPublishable(housing: Housing): Bool { (housing.prices.size() > 0) and - (housing.address != "") and + (addressEqual(housing.address, NULL_LOCATION)) and (housing.properties.size() > 0) }; @@ -504,7 +522,7 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller }) func setAddress({housingId: HousingId; address: Text}): async {#Ok; #Err: Text}{ + public shared ({ caller }) func setAddress({housingId: HousingId; address: Types.Location}): async {#Ok; #Err: Text}{ let housing = Map.get(housings, nhash, housingId); switch housing { case null { #Err(msg.NotHousing)}; @@ -519,6 +537,32 @@ shared ({ caller }) actor class Triourism () = this { }; + func createHousingType(user: Principal, propertiesOfType: Types.Property): {#Ok; #Err: Text} { + let housingTypesMap: HousingTypesMap = + switch(Map.get(housingTypesByHostOwner, phash, user)){ + case null {Map.new() }; + case ( ?map ) { map } + }; + switch (Map.get( + housingTypesMap, + thash, + propertiesOfType.nameType)) { + case null { + // Creación del nuevo tipo + ignore Map.put( + housingTypesMap, + thash, + propertiesOfType.nameType, + {properties = propertiesOfType; housingIds: [HousingId] = []} + ); + #Ok + }; + case (_) { + #Err(msg.HousingTypeExist) + } + } + }; + func putHousingType(user: Principal, propertiesOfType: Types.Property, housingId: HousingId ){ let housingTypesMap: HousingTypesMap = switch(Map.get(housingTypesByHostOwner, phash, user)){ @@ -544,9 +588,8 @@ shared ({ caller }) actor class Triourism () = this { ); }; - - public shared ({ caller }) func setTypeHousing({housingId: HousingId; qty: Nat; propertiesOfType: Types.Property}): async {#Ok; #Err: Text} { + public shared ({ caller }) func assignHousingType({housingId: HousingId; qty: Nat; propertiesOfType: Types.Property}): async {#Ok; #Err: Text} { let user = Map.get(users, phash, caller); switch user { @@ -560,26 +603,32 @@ shared ({ caller }) actor class Triourism () = this { if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; - ignore Map.put(housings, nhash, housingId, {housing with propertiesOfType}); - // agregamos la habitacion actual al nuevo tipo creado - putHousingType(caller, propertiesOfType, housingId); - // Clonacion de habitacion segun qty con valores por defecto para las nuevas - var index = 1; // la habitacion a partir de la que se define el tipo no se cuenta porque ya tiene su propio id - while (index < qty ){ - lastHousingId += 1; - let newHousing: Housing = { - housing with - id = lastHousingId; - active = false; - propertiesOfType + let createResponse = createHousingType(caller,propertiesOfType); + switch createResponse { + case (#Ok) { + ignore Map.put(housings, nhash, housingId, {housing with propertiesOfType}); + // agregamos la habitacion actual al nuevo tipo creado + putHousingType(caller, propertiesOfType, housingId); + // Clonacion de habitacion segun qty con valores por defecto para las nuevas + var index = 1; // la habitacion a partir de la que se define el tipo no se cuenta porque ya tiene su propio id + while (index < qty ){ + lastHousingId += 1; + let newHousing: Housing = { + housing with + id = lastHousingId; + active = false; + propertiesOfType + }; + putHousingType(caller, propertiesOfType, lastHousingId); + ignore Map.put(users, phash, caller, user ); + ignore Map.put(housings, nhash, lastHousingId,newHousing ); + + index += 1; + }; + #Ok }; - putHousingType(caller, propertiesOfType, lastHousingId); - ignore Map.put(users, phash, caller, user ); - ignore Map.put(housings, nhash, lastHousingId,newHousing ); - - index += 1; - }; - #Ok + case (#Err(msg)) {#Err(msg)} + }; } } @@ -616,7 +665,7 @@ shared ({ caller }) actor class Triourism () = this { } } }; - ////////////////////////////////// Getters /////////////////////////////////////////////// + ///////////////////////////////////////// Getters //////////////////////////////////////// public query func getHousingPaginate({page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { if(Map.size(housings) < page * qtyPerPage){ @@ -663,8 +712,9 @@ shared ({ caller }) actor class Triourism () = this { }; } }; + //TODO servicio que devuelva los tipos - public shared ({ caller }) func getMyHousingsByType({_housingType: Text; page: Nat}): async ResultHousingPaginate{ + public shared ({ caller }) func getMyHousingsByType({housingType: Text; page: Nat}): async ResultHousingPaginate{ let housingTypeMap = Map.get(housingTypesByHostOwner, phash, caller); switch housingTypeMap { case null {#Err("Not Housing Types")}; @@ -672,7 +722,7 @@ shared ({ caller }) actor class Triourism () = this { let housingsType = Map.get( map, thash, - _housingType + housingType ); switch housingsType { case null { #Err("Not housing type")}; @@ -707,7 +757,6 @@ shared ({ caller }) actor class Triourism () = this { #Ok({array = Buffer.toArray(resultBuffer); hasNext: Bool}); }; - func getHousingsPaginateByOwner({owner: Principal; page: Nat}): ResultHousingPaginate { let user = Map.get(users, phash, owner); switch user { @@ -738,6 +787,7 @@ shared ({ caller }) actor class Triourism () = this { } } }; + public shared ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate{ getHousingsPaginateByOwner({owner = caller; page}) }; @@ -795,7 +845,7 @@ shared ({ caller }) actor class Triourism () = this { } }; - public query func getAmenities(housingId: HousingId): async [Text] { + public query func getAmenities({housingId: HousingId}): async [Text] { let housing = Map.get(housings, nhash, housingId); switch housing { case null { [] }; diff --git a/backend/types.mo b/backend/types.mo index c79e7ca..4dd2335 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -58,9 +58,20 @@ module { #AdditionalGuests: Bool; #NoiseAfter10pm: Bool; #ParkOnTheStreet: Bool; + #VisitsAllowed: Bool; #CustomRule: {rule: Text; allowed: Bool}; }; + public type Location = { + country: Text; + city: Text; + neighborhood: Text; + zipCode: Nat; + street: Text; + externalNumber: Nat; + internalNumber: Nat; + }; + public type Housing = HousingCreateData and { active: Bool; id: Nat; @@ -69,16 +80,22 @@ module { rules: [Rule]; checkIn: Int; checkOut: Int; - address: Text; + address: Location; properties: [Property]; amenities: [Text] }; - + public type Bathroom = { + toilette: Bool; + shower: Bool; + bathtub: Bool; + isShared: Bool; + sink: Bool; + }; public type Property = { nameType: Text; beds: [BedKind]; - bathroom: Bool; + bathroom: Bathroom; maxGuest: Nat; extraGuest: Nat; }; @@ -126,7 +143,7 @@ module { public type HousingPreview = { id: Nat; - address: Text; + address: Location; thumbnail: Blob; prices: [Price]; }; From 29f2b381286aecb56752ab9c4da199c290238aeb Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 11 Dec 2024 20:42:43 -0300 Subject: [PATCH 26/57] Amenities type --- backend/main.mo | 16 +++++++++------- backend/types.mo | 29 ++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 5f2cdba..efdb2e5 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -354,7 +354,7 @@ shared ({ caller }) actor class Triourism () = this { checkOut: Nat = 12; address: Types.Location = NULL_LOCATION; properties: [Types.Property] = []; - amenities: [Text] = []; + amenities = null; calendar: [var Types.CalendaryPart] = [var]; }; @@ -631,7 +631,6 @@ shared ({ caller }) actor class Triourism () = this { }; } } - } }; }; @@ -652,7 +651,7 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller }) func setAmenities(amenities: [Text], housingId: HousingId): async {#Ok; #Err: Text}{ + public shared ({ caller }) func setAmenities(amenities: Types.Amenities, housingId: HousingId): async {#Ok; #Err: Text}{ let housing = Map.get(housings, nhash, housingId); switch housing { case null { #Err(msg.NotHousing)}; @@ -660,7 +659,11 @@ shared ({ caller }) actor class Triourism () = this { if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; - ignore Map.put(housings, nhash, housingId, {housing with amenities}); + ignore Map.put( + housings, + nhash, + housingId, + {housing with amenities = ?amenities}); #Ok } } @@ -845,10 +848,10 @@ shared ({ caller }) actor class Triourism () = this { } }; - public query func getAmenities({housingId: HousingId}): async [Text] { + public query func getAmenities({housingId: HousingId}): async ?Types.Amenities { let housing = Map.get(housings, nhash, housingId); switch housing { - case null { [] }; + case null { null }; case (?housing) { housing.amenities } @@ -856,7 +859,6 @@ shared ({ caller }) actor class Triourism () = this { }; - // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ // let housing = Map.get(housings, nhash, housingId); // switch housing { diff --git a/backend/types.mo b/backend/types.mo index 4dd2335..7f5fbfa 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -62,6 +62,33 @@ module { #CustomRule: {rule: Text; allowed: Bool}; }; + public type Amenities = { + freeWifi: Bool; + airCond: Bool; // Aire acondicionado + flatTV: Bool; // TV de pantalla plana + minibar: Bool; + safeBox: Bool; // Caja de seguridad + roomService: Bool; // Servicio a la habitación + premiumLinen: Bool; // Ropa de cama premium + ironBoard: Bool; // Plancha y tabla de planchar + privateBath: Bool; // Baño privado con artículos de tocador + hairDryer: Bool; // Secador de pelo + hotelRest: Bool; // Restaurante en el hotel + barLounge: Bool; // Bar y lounge + buffetBrkfst: Bool; // Desayuno buffet + lobbyCoffee: Bool; // Servicio de café/té en el lobby + catering: Bool; // Servicio de catering para eventos + specialMenu: Bool; // Menú para dietas especiales (bajo solicitud) + outdoorPool: Bool; // Piscina al aire libre + spaWellness: Bool; // Spa y centro de bienestar + gym: Bool; + jacuzzi: Bool; + gameRoom: Bool; // Salón de juegos + tennisCourt: Bool; // Pista de tenis + natureTrails: Bool; // Acceso a senderos naturales + custom: [{amenitieName: Text; value: Bool}]; + }; + public type Location = { country: Text; city: Text; @@ -82,7 +109,7 @@ module { checkOut: Int; address: Location; properties: [Property]; - amenities: [Text] + amenities: ?Amenities; }; public type Bathroom = { From 3d3cd1db1bbecfe2875ba134ac56ca181ba1a488 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 2 Jan 2025 21:23:41 -0300 Subject: [PATCH 27/57] fix getMyDisponibility --- backend/main.mo | 74 +++++++++------ package.json | 1 + scripts-sh/deploy-with-content.sh | 144 ++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 27 deletions(-) create mode 100755 scripts-sh/deploy-with-content.sh diff --git a/backend/main.mo b/backend/main.mo index efdb2e5..ff1fdbc 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -15,6 +15,7 @@ import msg "constants"; import { print } "mo:base/Debug"; import Array "mo:base/Array"; import Iter "mo:base/Iter"; +import Int "mo:base/Int"; shared ({ caller }) actor class Triourism () = this { @@ -375,6 +376,7 @@ shared ({ caller }) actor class Triourism () = this { let kinds = addIdToHostKind(user.kinds, lastHousingId); ignore Map.put(users, phash, caller, {user with kinds }); ignore Map.put(housings, nhash, lastHousingId,newHousing ); + ignore Map.put(calendars, nhash, lastHousingId, {dayZero= now(); reservedDays= []}); #Ok(lastHousingId) } } @@ -382,7 +384,7 @@ shared ({ caller }) actor class Triourism () = this { func isPublishable(housing: Housing): Bool { (housing.prices.size() > 0) and - (addressEqual(housing.address, NULL_LOCATION)) and + (not addressEqual(housing.address, NULL_LOCATION)) and (housing.properties.size() > 0) }; @@ -396,7 +398,7 @@ shared ({ caller }) actor class Triourism () = this { case null { #Err(msg.NotHousing)}; case ( ?housing ) { if(isPublishable(housing)) { - ignore Map.put(housings, nhash, housingId, {housing with ctive = true}); + ignore Map.put(housings, nhash, housingId, {housing with active = true}); #Ok } else { #Err(msg.IsNotpublishable) @@ -502,6 +504,9 @@ shared ({ caller }) actor class Triourism () = this { if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; + if (not isPublishable(housing)) { + return #Err(msg.IsNotpublishable) + }; ignore Map.put(housings, nhash, id, {housing with active}); #Ok } @@ -534,44 +539,43 @@ shared ({ caller }) actor class Triourism () = this { #Ok } } - }; func createHousingType(user: Principal, propertiesOfType: Types.Property): {#Ok; #Err: Text} { - let housingTypesMap: HousingTypesMap = + let housingTypesMap: HousingTypesMap = switch(Map.get(housingTypesByHostOwner, phash, user)){ case null {Map.new() }; case ( ?map ) { map } }; switch (Map.get( - housingTypesMap, + housingTypesMap, thash, propertiesOfType.nameType)) { - case null { - // Creación del nuevo tipo - ignore Map.put( - housingTypesMap, - thash, - propertiesOfType.nameType, - {properties = propertiesOfType; housingIds: [HousingId] = []} - ); - #Ok - }; - case (_) { - #Err(msg.HousingTypeExist) - } + case null { + // Creación del nuevo tipo + ignore Map.put( + housingTypesMap, + thash, + propertiesOfType.nameType, + {properties = propertiesOfType; housingIds: [HousingId] = []} + ); + #Ok + }; + case (_) { + #Err(msg.HousingTypeExist) + } } }; func putHousingType(user: Principal, propertiesOfType: Types.Property, housingId: HousingId ){ - let housingTypesMap: HousingTypesMap = + let housingTypesMap: HousingTypesMap = switch(Map.get(housingTypesByHostOwner, phash, user)){ case null {Map.new() }; case ( ?map ) { map } }; - let housingType: {properties: Types.Property; housingIds: [HousingId]} = + let housingType: {properties: Types.Property; housingIds: [HousingId]} = switch (Map.get( - housingTypesMap, + housingTypesMap, thash, propertiesOfType.nameType)){ case null {{properties = propertiesOfType; housingIds = [housingId]}}; @@ -603,10 +607,10 @@ shared ({ caller }) actor class Triourism () = this { if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; - let createResponse = createHousingType(caller,propertiesOfType); + let createResponse = createHousingType(caller, propertiesOfType); switch createResponse { case (#Ok) { - ignore Map.put(housings, nhash, housingId, {housing with propertiesOfType}); + ignore Map.put(housings, nhash, housingId, {housing with properties = [propertiesOfType]}); // agregamos la habitacion actual al nuevo tipo creado putHousingType(caller, propertiesOfType, housingId); // Clonacion de habitacion segun qty con valores por defecto para las nuevas @@ -619,20 +623,20 @@ shared ({ caller }) actor class Triourism () = this { active = false; propertiesOfType }; - putHousingType(caller, propertiesOfType, lastHousingId); + putHousingType(caller, propertiesOfType, lastHousingId); ignore Map.put(users, phash, caller, user ); - ignore Map.put(housings, nhash, lastHousingId,newHousing ); + ignore Map.put(housings, nhash, lastHousingId, newHousing ); index += 1; }; #Ok }; case (#Err(msg)) {#Err(msg)} - }; + }; } } } - }; + }; }; public shared ({ caller }) func removeHousingType(housingType: Text): async {#Ok; #Err: Text}{ @@ -689,6 +693,13 @@ shared ({ caller }) actor class Triourism () = this { } }; + public shared ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ + switch (Map.get(calendars, nhash, id)) { + case null { #Err(msg.NotHousing)}; + case (?calendar) { #Ok(calendar) } + } + }; + public query func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { let housing = Map.get(housings, nhash, housingId); return switch housing { @@ -796,8 +807,10 @@ shared ({ caller }) actor class Triourism () = this { }; func updateCalendary(housingId: HousingId, calendary: Calendary): Calendary { + print("Actualizando calendario"); let curranDay = now(); let pastDays = (curranDay - calendary.dayZero) / (24 * 60 * 60 * 1_000_000_000); + print("Dias pasados: " # Int.toText(pastDays)); if(pastDays > 0){ var updateArray = Array.filter( calendary.reservedDays, @@ -815,9 +828,14 @@ shared ({ caller }) actor class Triourism () = this { }; func checkDisponibility(housingId: HousingId, chechIn: Int, checkOut: Int): Bool { + switch (Map.get(housings, nhash, housingId)) { + case (?housing) { if(not housing.active) { return false } }; + case _ { return false } + }; switch (Map.get(calendars, nhash, housingId)) { case null { false }; case (?calendar) { + print("Calendario encontrado"); let updatedCalendary = updateCalendary(housingId, calendar); var checkDay = chechIn; while (checkDay <= checkOut) { @@ -826,6 +844,7 @@ shared ({ caller }) actor class Triourism () = this { return false; }; }; + checkDay += 1; }; return true } @@ -834,6 +853,7 @@ shared ({ caller }) actor class Triourism () = this { public shared query ({ caller }) func getMyHousingDisponibility({checkIn: Nat; checkOut: Nat; page: Nat}): async ResultHousingPaginate{ let response = getHousingsPaginateByOwner({owner = caller; page}); + print(debug_show(response)); switch response { case (#Ok({array; hasNext} )){ let bufferResults = Buffer.fromArray([]); diff --git a/package.json b/package.json index 8f8f1bc..fdb1a0b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "typescript" ], "scripts": { + "deploy-with-content": "chmod +x scripts-sh/deploy-with-content.sh; ./scripts-sh/deploy-with-content.sh", "build": "turbo run build", "preclean": "turbo run clean", "clean": "rm -rf .dfx && rm -rf .turbo && rm -rf node_modules & rm -rf src/declarations" diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh new file mode 100755 index 0000000..df93e78 --- /dev/null +++ b/scripts-sh/deploy-with-content.sh @@ -0,0 +1,144 @@ +#!/bin/bash +# ------------ Usuario deployer ------------ +dfx identity new 0000TestUser0 +dfx identity use 0000TestUser0 +dfx deploy backend + +# ------------ Usuario Host 1 -------------- +dfx identity new 0000TestUser1 +dfx identity use 0000TestUser1 +dfx canister call backend signUpAsHost '(record { + firstName="Alberto"; + lastName="Campos"; + email="null"; + phone = opt 542238780892 +})' +# CreateHousing 1 + +dfx canister call backend createHousing '(record { + namePlace="Far West"; + nameHost="Alberto Campos"; + descriptionPlace="Lugar tranquilo y seguro"; + descriptionHost="Al lado de Far West disco bar y apuestas"; + link="https://media.istockphoto.com/id/2045383950/photo/digital-render-of-a-serene-bedroom-oasis-with-natural-light.jpg?s=2048x2048&w=is&k=20&c=2Rw4fqnC08kfiuc58jhzbCAOYwPp9V8w4e3Ma2TY984="; + photos = vec {}; + thumbnail = blob "Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABAAEAAAA" +})' +# Update Prices housing 1 +dfx canister call backend updatePrices '(record { + id = 1 : nat; + prices = vec { + variant { PerNight = 30 : nat }; + variant { PerWeek = 200 : nat }; + }; +})' + +#Assing housing Type + +dfx canister call backend assignHousingType '(record { + housingId = 1 : nat; + qty = 1 : nat; + propertiesOfType = record { + extraGuest = 2 : nat; + bathroom = record { + shower = true; + sink = true; + toilette = true; + isShared = false; + bathtub = true; + }; + beds = vec { variant { Matrimonial = 1 : nat } }; + maxGuest = 4 : nat; + nameType = "Standard"; + }; +})' + +#Set Address Housing 1 + +dfx canister call backend setAddress '(record { + housingId = 1 : nat; + address = record { + street = "9 de Julio"; + externalNumber = 1207; + internalNumber = 43; + city = "Mar del Plata"; + neighborhood = "Centro"; + country = "Argentina"; + zipCode = 7600 ; + }; +})' + +dfx canister call backend setHousingStatus '(record { + id = 1 : nat; + active = true; +})' + +dfx canister call backend publishHousing 1 + +# CreateHousing 2 + +dfx canister call backend createHousing '(record { + namePlace=""; + nameHost="Alberto Campos"; + descriptionPlace="Lugar tranquilo y seguro"; + descriptionHost="Espacioso y luminoso con vista al mar"; + link="https://media.istockphoto.com/id/1837566278/photo/scandinavian-style-apartment-interior.jpg?s=2048x2048&w=is&k=20&c=ZT-ZoefdikBU9DhdEg4fV6bW-SdZi_HLRFg_mupNd9E="; + photos = vec {}; + thumbnail = blob "Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABAAEAALKLKAKHUJHAA" +})' + + + + +# ------------ Usuario Host 2 -------------- +dfx identity new 0000TestUser2 +dfx identity use 0000TestUser2 +dfx canister call backend signUpAsHost '(record { + firstName="Lucila"; + lastName="Peralta"; + email="lucilaperalta@live.com"; + phone = opt 542298712438 +})' + +# ------------ Usuario Host 3 -------------- +dfx identity new 0000TestUser3 +dfx identity use 0000TestUser3 +dfx canister call backend signUpAsHost '(record { + firstName="Claudia"; + lastName="Gimenez"; + email="claugimenez@gmail.com"; + phone = opt 558789878522 +})' + +# ------------ Usuario Host 4 -------------- +dfx identity new 0000TestUser4 +dfx identity use 0000TestUser4 +dfx canister call backend signUpAsHost '(record { + firstName="Mario"; + lastName="Pappa"; + email="mariopapa@gmail.com"; + phone = opt 542235227692 +})' + +# ------------ Usuario Host 5 -------------- +dfx identity new 0000TestUser5 +dfx identity use 0000TestUser5 +dfx canister call backend signUpAsHost '(record { + firstName="Rodolfo"; + lastName="Anchorena"; + email="rodolfoanchorena.com"; + phone = opt 542278795421 +})' + +# ------------ Usuario Host 6 -------------- +dfx identity new 0000TestUser6 +dfx identity use 0000TestUser6 +dfx canister call backend signUpAsHost '(record { + firstName="Carlos"; + lastName="Maldonado"; + email="carlosmaldonado.com"; + phone = opt 5422145789544 +})' + + + From 91e8ddf2a92caa9247eb4e8afbfe287abd0ccbf7 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 2 Jan 2025 22:23:41 -0300 Subject: [PATCH 28/57] fix createHousingType --- backend/main.mo | 29 +++++++++++++++++++++-------- backend/types.mo | 1 + 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index ff1fdbc..4797697 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -559,6 +559,7 @@ shared ({ caller }) actor class Triourism () = this { propertiesOfType.nameType, {properties = propertiesOfType; housingIds: [HousingId] = []} ); + ignore Map.put(housingTypesByHostOwner, phash, user, housingTypesMap); #Ok }; case (_) { @@ -672,6 +673,7 @@ shared ({ caller }) actor class Triourism () = this { } } }; + ///////////////////////////////////////// Getters //////////////////////////////////////// public query func getHousingPaginate({page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { @@ -693,11 +695,21 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ - switch (Map.get(calendars, nhash, id)) { - case null { #Err(msg.NotHousing)}; - case (?calendar) { #Ok(calendar) } - } + public shared query ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ + switch (Map.get(housings, nhash, id)) { + case (?housing) { + if(housing.active) { + switch (Map.get(calendars, nhash, id)) { + case null { return #Err(msg.NotHousing)}; + case (?calendar) { return #Ok(calendar) } + } + } else { + return #Err(msg.InactiveHousing) + } + }; + case null { return #Err(msg.NotHousing)}; + }; + }; public query func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { @@ -726,9 +738,10 @@ shared ({ caller }) actor class Triourism () = this { }; } }; + //TODO servicio que devuelva los tipos - public shared ({ caller }) func getMyHousingsByType({housingType: Text; page: Nat}): async ResultHousingPaginate{ + public shared query ({ caller }) func getMyHousingsByType({housingType: Text; page: Nat}): async ResultHousingPaginate{ let housingTypeMap = Map.get(housingTypesByHostOwner, phash, caller); switch housingTypeMap { case null {#Err("Not Housing Types")}; @@ -757,6 +770,7 @@ shared ({ caller }) actor class Triourism () = this { case (?housing) { resultBuffer.add( { + active = housing.active; id = ids[index]; address = housing.address; thumbnail = housing.thumbnail; @@ -802,7 +816,7 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate{ + public shared query ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate{ getHousingsPaginateByOwner({owner = caller; page}) }; @@ -853,7 +867,6 @@ shared ({ caller }) actor class Triourism () = this { public shared query ({ caller }) func getMyHousingDisponibility({checkIn: Nat; checkOut: Nat; page: Nat}): async ResultHousingPaginate{ let response = getHousingsPaginateByOwner({owner = caller; page}); - print(debug_show(response)); switch response { case (#Ok({array; hasNext} )){ let bufferResults = Buffer.fromArray([]); diff --git a/backend/types.mo b/backend/types.mo index 7f5fbfa..b90bc6b 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -169,6 +169,7 @@ module { }; public type HousingPreview = { + active: Bool; id: Nat; address: Location; thumbnail: Blob; From d27fb3808bf58a1e5a8fbb6b965b1e092824eec4 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Fri, 3 Jan 2025 21:18:21 -0300 Subject: [PATCH 29/57] fix Prices format --- backend/main.mo | 10 +++++----- backend/types.mo | 15 ++++++++++----- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 4797697..f1b9c47 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -350,7 +350,7 @@ shared ({ caller }) actor class Triourism () = this { let defaultHousinValues = { active: Bool = false; rules: [Types.Rule] = []; - prices: [Types.Price] = []; + price: ?Types.Price = null; checkIn: Nat = 15; checkOut: Nat = 12; address: Types.Location = NULL_LOCATION; @@ -383,7 +383,7 @@ shared ({ caller }) actor class Triourism () = this { }; func isPublishable(housing: Housing): Bool { - (housing.prices.size() > 0) and + (housing.price != null) and (not addressEqual(housing.address, NULL_LOCATION)) and (housing.properties.size() > 0) }; @@ -447,7 +447,7 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller }) func updatePrices({id: HousingId; prices: [Types.Price]}): async UpdateResult{ + public shared ({ caller }) func updatePrices({id: HousingId; price_: Types.Price}): async UpdateResult{ let housing = Map.get(housings, nhash, id); switch housing { case null { @@ -455,7 +455,7 @@ shared ({ caller }) actor class Triourism () = this { }; case (?housing) { if(housing.owner != caller){ return #Err(msg.UnauthorizedCaller) }; - ignore Map.put(housings, nhash, id, {housing with prices}); + ignore Map.put(housings, nhash, id, {housing with price = ?price_}); return #Ok }; } @@ -774,7 +774,7 @@ shared ({ caller }) actor class Triourism () = this { id = ids[index]; address = housing.address; thumbnail = housing.thumbnail; - prices = housing.prices; + price = housing.price; } ) } diff --git a/backend/types.mo b/backend/types.mo index b90bc6b..3a11260 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -102,7 +102,7 @@ module { public type Housing = HousingCreateData and { active: Bool; id: Nat; - prices: [Price]; + price: ?Price; owner: Principal; rules: [Rule]; checkIn: Int; @@ -173,7 +173,7 @@ module { id: Nat; address: Location; thumbnail: Blob; - prices: [Price]; + price: ?Price; }; public type HousingId = Nat; @@ -189,10 +189,15 @@ module { // #RoomWithSharedSpaces: [Rule]; //Hostels/Pensiones // }; + // public type Price = { + // #PerNight: Nat; + // #PerWeek: Nat; + // #CustomPeriod: [{dais: Nat; price: Nat}]; + // }; + public type Price = { - #PerNight: Nat; - #PerWeek: Nat; - #CustomPeriod: [{dais: Nat; price: Nat}]; + base: Nat; // price per nigth + discountTable: [{minimumDays: Nat; discount: Nat}]; }; // public type Rules = { // Ejemplo de Rule: {key = "Horarios"; value = "Sin ruidos molestos entre las 22:00 y las 8:00"} From 95e22a79a0de658675d036f8c9050f30bbfce20e Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sun, 12 Jan 2025 03:07:55 -0300 Subject: [PATCH 30/57] =?UTF-8?q?Refactorizaci=C3=B3n.=20Flujo=20de=20rese?= =?UTF-8?q?rvas=20y=20confirmacion=20de=20transaccion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/constants.mo | 4 +- backend/indexer_icp_token.mo | 80 ++ backend/main.mo | 1151 ++++++++++++++--------------- backend/mainOld.mo | 907 +++++++++++++++++++++++ backend/types.mo | 296 ++++---- backend/typesOld.mo | 233 ++++++ mops.toml | 3 +- scripts-sh/deploy-with-content.sh | 24 +- 8 files changed, 1932 insertions(+), 766 deletions(-) create mode 100644 backend/indexer_icp_token.mo create mode 100644 backend/mainOld.mo create mode 100644 backend/typesOld.mo diff --git a/backend/constants.mo b/backend/constants.mo index 81000bb..cc16eeb 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -11,10 +11,10 @@ module { public let NotUser = "Unregistered user"; public let NotHostUser = "User is not Host User"; public let CallerIsNotrequester = "The caller does not match the reservation requester"; - public let NotHost = "Te caller in not Host User profile"; public let InactiveHousing = "Housing is temporarily disabled"; public let ZeroIsNotAllowed = "Is not greater than zero"; public let IsNotpublishable = "Not publishable due to missing data"; public let HousingTypeExist = "The housing type already exists"; - + public let HousingTypeNoExist = "The type of housing does not exist"; + public let TransactionNotVerified = "The transaction was not verified successfully"; }; diff --git a/backend/indexer_icp_token.mo b/backend/indexer_icp_token.mo new file mode 100644 index 0000000..febf8cf --- /dev/null +++ b/backend/indexer_icp_token.mo @@ -0,0 +1,80 @@ +// This is a generated Motoko binding. +// Please use `import service "ic:canister_id"` instead to call canisters on the IC if possible. + +module { + public type Account = { owner : Principal; subaccount : ?Blob }; + public type GetAccountIdentifierTransactionsArgs = { + max_results : Nat64; + start : ?Nat64; + account_identifier : Text; + }; + public type GetAccountIdentifierTransactionsError = { message : Text }; + public type GetAccountIdentifierTransactionsResponse = { + balance : Nat64; + transactions : [TransactionWithId]; + oldest_tx_id : ?Nat64; + }; + public type GetAccountIdentifierTransactionsResult = { + #Ok : GetAccountIdentifierTransactionsResponse; + #Err : GetAccountIdentifierTransactionsError; + }; + public type GetAccountTransactionsArgs = { + max_results : Nat; + start : ?Nat; + account : Account; + }; + public type GetBlocksRequest = { start : Nat; length : Nat }; + public type GetBlocksResponse = { blocks : [Blob]; chain_length : Nat64 }; + public type HttpRequest = { + url : Text; + method : Text; + body : Blob; + headers : [(Text, Text)]; + }; + public type HttpResponse = { + body : Blob; + headers : [(Text, Text)]; + status_code : Nat16; + }; + public type InitArg = { ledger_id : Principal }; + public type Operation = { + #Approve : { + fee : Tokens; + from : Text; + allowance : Tokens; + expected_allowance : ?Tokens; + expires_at : ?TimeStamp; + spender : Text; + }; + #Burn : { from : Text; amount : Tokens; spender : ?Text }; + #Mint : { to : Text; amount : Tokens }; + #Transfer : { + to : Text; + fee : Tokens; + from : Text; + amount : Tokens; + spender : ?Text; + }; + }; + public type Status = { num_blocks_synced : Nat64 }; + public type TimeStamp = { timestamp_nanos : Nat64 }; + public type Tokens = { e8s : Nat64 }; + public type Transaction = { + memo : Nat64; + icrc1_memo : ?Blob; + operation : Operation; + timestamp : ?TimeStamp; + created_at_time : ?TimeStamp; + }; + public type TransactionWithId = { id : Nat64; transaction : Transaction }; + public type Self = InitArg -> async actor { + get_account_identifier_balance : shared query Text -> async Nat64; + get_account_identifier_transactions : shared query GetAccountIdentifierTransactionsArgs -> async GetAccountIdentifierTransactionsResult; + get_account_transactions : shared query GetAccountTransactionsArgs -> async GetAccountIdentifierTransactionsResult; + get_blocks : shared query GetBlocksRequest -> async GetBlocksResponse; + http_request : shared query HttpRequest -> async HttpResponse; + icrc1_balance_of : shared query Account -> async Nat64; + ledger_id : shared query () -> async Principal; + status : shared query () -> async Status; + } +} \ No newline at end of file diff --git a/backend/main.mo b/backend/main.mo index f1b9c47..2bb3248 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -6,118 +6,126 @@ import Principal "mo:base/Principal"; import Buffer "mo:base/Buffer"; import Blob "mo:base/Blob"; import Types "types"; -// import Int "mo:base/Int"; import { now } "mo:base/Time"; -// import Rand "mo:random/Rand"; import msg "constants"; - -////////////// Debug imports //////////////// import { print } "mo:base/Debug"; import Array "mo:base/Array"; -import Iter "mo:base/Iter"; import Int "mo:base/Int"; - +import List "mo:base/List"; +import Nat "mo:base/Nat"; +import Nat8 "mo:base/Nat8"; +import Nat64 "mo:base/Nat64"; +import Indexer_icp "./indexer_icp_token"; +import AccountIdentifier "mo:account-identifier"; + shared ({ caller }) actor class Triourism () = this { type User = Types.User; - type UserKind = Types.UserKind; + type HostUser =Types.HostUser; + type UserData = Types.UserData; type SignUpResult = Types.SignUpResult; - type CalendaryPart = Types.CalendaryPart; - type Calendary = {dayZero: Int; reservedDays: [Int]}; - type ReservationDataInput = Types.ReservationDataInput; + type Calendary = Types.Calendary; type Reservation = Types.Reservation; - type HousingId = Types.HousingId; - // type HousingDataInit = Types.HousingDataInit; + type HousingId = Nat; + type ReviewId = Nat; + type Review = Types.Review; type Housing = Types.Housing; + type HousingTypeInit = Types.HousingTypeInit; + type HousingType = Types.HousingType; type HousingResponse = Types.HousingResponse; type HousingPreview = Types.HousingPreview; type HousingCreateData = Types.HousingCreateData; - type HousingTypesMap = Map.Map; + type TransactionParams = Types.TransactionParams; + type DataTransaction = Types.DataTransaction; + type TransactionResponse = Types.TransactionResponse; type UpdateResult = Types.UpdateResult; type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; hasNext: Bool}; #Err: Text}; - type PublishResult = {#Ok: HousingId; #Err: Text}; - - type ReservationResult = { - #Ok: { - reservationId: Nat; - msg: Text; - }; - #Err: Text; - }; - - // TODO revisar day, actualemnte es el timestamp de una hora especifica que delimita el comienzo del dia stable let DEPLOYER = caller; - // let NANO_SEG_PER_HOUR = 60 * 60 * 1_000_000_000; - // let ramdomGenerator = Rand.Rand(); - // stable var minReservationLeadTime = 24 * 60 * 60 * 1_000_000_000; // 24 horas en nanosegundos - + // /////////////// WARNING modificar estas variables en produccion a los valores reales //// + let nanoSecPerDay = 40 * 1_000_000_000; // Test Transcurso acelerado de los dias + // let nanoSecPerDay = 86400 * 1_000_000_000; // Valor real de nanosegundos en un dia + stable var TimeToPay = 20 * 1_000_000_000; // Tiempo en nanosegundos para confirmar la reserva mediante pago + // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + stable let admins = Set.new(); ignore Set.put(admins, phash, caller); + stable let users = Map.new(); + stable let hostUsers = Map.new(); + stable let housings = Map.new(); - stable let housingTypesByHostOwner = Map.new(); - stable let calendars = Map.new(); - stable let reservationsRequests = Map.new(); - stable let reservationsConfirmed = Map.new(); + stable let review = Map.new(); + stable let reservationsPendingConfirmation = Map.new(); + stable let reservationsHistory = Map.new(); + stable var lastHousingId = 0; + stable var lastReviewId = 0; + stable var lastReservationId = 0; + ///////////////////////////////////// Login Update functions //////////////////////////////////////// - ///////////////////////////////////// Update functions //////////////////////////////////////// + public shared ({ caller }) func signUpAsUser(data: Types.SignUpData) : async SignUpResult { + if(Principal.isAnonymous(caller)) { return #Err(msg.NotUser) }; - private func _safeSignUp(p: Principal, data: Types.SignUpData, kind: UserKind): SignUpResult { - if(Principal.isAnonymous(p)){ - return #Err(msg.NotUser) - }; - let user = Map.get(users, phash, p); + let user = Map.get(users, phash, caller); switch user { case (?User) { #Err("User already exists") }; case null { let newUser: User = { data with - kinds: [UserKind] = [kind]; verified = true; + reviewsIssued = List.nil(); score = 0; }; - ignore Map.put(users, phash, p, newUser); - #Ok(newUser); + ignore Map.put(users, phash, caller, newUser); + #Ok( newUser ); }; }; }; - public shared ({ caller }) func signUp(data: Types.SignUpData) : async SignUpResult { - _safeSignUp(caller, data, #Initial) - }; - public shared ({ caller }) func signUpAsHost(data: Types.SignUpData) : async SignUpResult { - _safeSignUp(caller, data, #Host([])); - + if(Principal.isAnonymous(caller)) { return #Err(msg.NotUser) }; + let hostUser = Map.get(hostUsers, phash, caller); + switch hostUser { + case (?User) { #Err("Host User already exists") }; + case null { + let newHostUser: HostUser = { + data with + verified = true; + score = 0; + housingIds = List.nil(); + housingTypes = Map.new(); + }; + ignore Map.put(hostUsers, phash, caller, newHostUser); + #Ok(newHostUser); + }; + }; }; - public shared query ({ caller }) func logIn(): async {#Ok: User; #Err} { - let user = Map.get(users, phash, caller); + public shared query ({ caller }) func loginAsUser(): async {#Ok: UserData; #Err} { + let user = Map.get(users, phash, caller); switch user { - case null { #Err }; + case null { #Err() }; case ( ?u ) { #Ok(u)} }; }; - //////////////////////////////// CRUD Data User /////////////////////////////////// + public shared query ({ caller }) func loginAsHost(): async {#Ok: UserData; #Err} { + let hostUser = Map.get(hostUsers, phash, caller); + switch hostUser { + case null { #Err() }; + case ( ?u ) { #Ok(u)} + }; + }; - // public shared ({ caller }) func loadAvatar(avatar: Blob): async {#Ok; #Err: Text} { - // let user = Map.get(users, phash, caller); - // switch user { - // case null {#Err(msg.NotUser)}; - // case(?user) { - // ignore Map.put(users, phash, caller, {user with avatar = ?avatar}); - // #Ok - // } - // } - // }; + + //////////////////////////////// CRUD Data User /////////////////////////////////// public shared ({ caller }) func editProfile(data: Types.SignUpData): async {#Ok; #Err}{ let user = Map.get(users, phash, caller); @@ -130,81 +138,41 @@ shared ({ caller }) actor class Triourism () = this { }; }; - // TODO - /////////////////////////////////////////////////////////////////////////////////// - - ///////////////////////////// Private functions /////////////////////////////////// + ///////////////////////////// Private functions /////////////////////////////////// func isAdmin(p: Principal): Bool { Set.has(admins, phash, p) }; - func isUser(p: Principal): Bool { Map.has(users, phash, p)}; - - // func initCalendary(): [var CalendaryPart]{ - // Prim.Array_init( - // 30, - // {day= 0; available = true; reservation = null} - // ) - // }; - - // func freezeCalendar(c: [var CalendaryPart]): [CalendaryPart]{ - // Prim.Array_tabulate(c.size(), func i = c[i]) - // }; - - // func updateCalendar(c: [var CalendaryPart]): [var CalendaryPart] { - // var indexDay = 0; - // var displace = 0; - // while(indexDay < c.size() and now() + NANO_SEG_PER_HOUR * 24 > c[indexDay].day){ - // displace += 1; - // indexDay += 1; - // }; - // let outPutArray = c; - // var index = 0; - // while(index + displace < c.size()){ - // outPutArray[index] := c[index + displace]; - // outPutArray[index + displace] := {day = 0; available = true; reservation = null}; - // index += 1; - // }; - // outPutArray; - // }; + func addressEqual(a: Types.Location, b: Types.Location ) : Bool { + a.country == b.country and + a.city == b.city and + a.neighborhood == b.neighborhood and + a.zipCode == b.zipCode and + a.externalNumber == b.externalNumber and + a.internalNumber == b.internalNumber + }; - // func intToNat(x: Int): Nat{ - // Prim.nat64ToNat(Prim.intToNat64Wrap(x)) - // }; - /////////// Probar //////////// - // func insertReservationToCalendar(_calendar: [var CalendaryPart], reserv: Reservation): {#Ok: [var CalendaryPart]; #Err } { - // let calendar = updateCalendar(_calendar); // Asegura que el primer elemento sea el día actual - // let checkInDay = intToNat((reserv.checkIn - now()) / (24 * NANO_SEG_PER_HOUR)); - // let daysQty = intToNat((reserv.checkOut - reserv.checkIn) / (24 * NANO_SEG_PER_HOUR)); - // var index = checkInDay; - // var okBaby = true; - - // // Comprobar disponibilidad - // while (index < checkInDay + daysQty and index < calendar.size()) { - // if (not calendar[index].available) { - // okBaby := false; - // index += daysQty; // Salir del bucle si no está disponible - // } else { - // index += 1; - // } - // }; + let NULL_LOCATION = { + country = ""; + city = ""; + neighborhood = ""; + zipCode = 0; street = ""; + externalNumber = 0; + internalNumber = 0 + }; - // if (okBaby) { - // index := checkInDay; - // while (index < checkInDay + daysQty and index < calendar.size()) { - // let calendaryPart: CalendaryPart = { - // reservation = ?reserv; - // day = index * 24 * NANO_SEG_PER_HOUR; - // available = false; - // }; - // calendar[index] := calendaryPart; - // index += 1; - // }; - // #Ok(calendar) - // } else { - // #Err - // } - // }; + let defaultHousinValues = { + active: Bool = false; + rules: [Types.Rule] = []; + price: ?Types.Price = null; + checkIn: Nat = 15; + checkOut: Nat = 12; + address: Types.Location = NULL_LOCATION; + properties: ?HousingType = null; + housingType: ?Text = null; + amenities = null; + reviews = List.nil(); + }; - /////////////////////////// Manage admins functions ///////////////////////////////// + /////////////////////////// Manage admins functions ///////////////////////////////// public shared ({ caller }) func addAdmin(p: Principal): async {#Ok; #Err} { if(not isAdmin(caller)){ @@ -223,9 +191,9 @@ shared ({ caller }) actor class Triourism () = this { #Ok } }; - - /////////////////////////// Admin functions ////////////////////////////////////////////// - /////////////////////////////// Verification process ///////////////////////////////////// + + /////////////////////////// Admin functions ////////////////////////////////////////////// + /////////////////////////////// Verification process ///////////////////////////////////// // TODO actualmente todos los usuarios se inicializan como verificados // func userIsVerificated(u: Principal): Bool { @@ -236,147 +204,26 @@ shared ({ caller }) actor class Triourism () = this { // }; // }; - //////////////////////////////// CRUD Housing //////////////////////////////////////////// - - // public shared ({ caller }) func publishHousingOld(data: HousingDataInit): async PublishResult { - // let user = Map.get(users, phash, caller); - // switch user { - // case null { - // return #Err(msg.NotUser); - // }; - // case (?user){ - // if(not userIsVerificated(caller)){ - // return #Err(msg.NotVerifiedUser); - // }; - // lastHousingId += 1; - // let newHousing: Housing = { data with - // reservationRequests = Map.new(); - // owner = caller; - // id = lastHousingId; - // calendar: [var CalendaryPart] = initCalendary(); - // photos: [Blob] = []; - // thumbnail: Blob = ""; - // }; - // var updateHousingArray: [HousingId] = []; - // var notPrevious = true; - // var position = 0; - // var i = 0; - // while(i < user.userKind.size()){ - // switch (user.userKind[i]){ - // case(#Host(housingIdArray)){ - // notPrevious := false; - // position := i; - // updateHousingArray := Prim.Array_tabulate( - // housingIdArray.size() + 1, - // func x { - // if(x != housingIdArray.size()){ - // housingIdArray[x]; - // } - // else {newHousing.id} - // } - // ) - // }; - // case(_){}; - // }; - // i += 1; - // }; - // if(notPrevious){ updateHousingArray := [newHousing.id] }; - // let updateKinds = Prim.Array_tabulate( - // user.userKind.size() + (if(notPrevious){ 1 } else { 0 }), - // func i { if(i == position) { - // #Host(updateHousingArray) - // } - // else { - // user.userKind[i] - // } - // } - // ); - // ignore Map.put(housings, nhash, lastHousingId, newHousing); - // ignore Map.put(users, phash, caller, {user with userKind = updateKinds}); - // return #Ok(newHousing.id) - // } - // }; - // }; - - func isHostUser(p: Principal): Bool { - let user = Map.get(users, phash, p); - switch user { - case null { false }; - case (?user) { - for (kind in user.kinds.vals()) { - switch kind { - case (#Host(_)) { return true }; - case _ {} - }; - }; - return false - } - } - }; - - func addIdToHostKind(arr: [Types.UserKind], id: HousingId): [Types.UserKind]{ - let bufferKinds = Buffer.fromArray([]); - for (k in arr.vals()) { - switch k { - case (#Host(ids)){ - let setIds = Set.fromIter(ids.vals(), nhash); - ignore Set.put(setIds, nhash, id); - bufferKinds.add(#Host(Set.toArray(setIds))) - }; - case (k) {bufferKinds.add(k)} - }; - }; - Buffer.toArray(bufferKinds); - }; - - func addressEqual(a: Types.Location, b: Types.Location ) : Bool { - a.country == b.country and - a.city == b.city and - a.neighborhood == b.neighborhood and - a.zipCode == b.zipCode and - a.externalNumber == b.externalNumber and - a.internalNumber == b.internalNumber - }; - - let NULL_LOCATION = { - country = ""; - city = ""; - neighborhood = ""; - zipCode = 0; street = ""; - externalNumber = 0; - internalNumber = 0 - }; - - let defaultHousinValues = { - active: Bool = false; - rules: [Types.Rule] = []; - price: ?Types.Price = null; - checkIn: Nat = 15; - checkOut: Nat = 12; - address: Types.Location = NULL_LOCATION; - properties: [Types.Property] = []; - amenities = null; - calendar: [var Types.CalendaryPart] = [var]; - }; + //////////////////////////////// CRUD Housing //////////////////////////////////////////// public shared ({ caller }) func createHousing(dataInit: HousingCreateData): async {#Ok: Nat; #Err: Text} { - let user = Map.get(users, phash, caller); - switch user { - case null {#Err(msg.NotUser)}; - case (?user) { - if (not isHostUser(caller)) { return #Err(msg.NotHostUser)}; - lastHousingId += 1; + let hostUser = Map.get(hostUsers, phash, caller); + switch hostUser { + case null {#Err(msg.NotHostUser)}; + case (?hostUser) { + lastHousingId += 1; let newHousing: Housing = { dataInit and defaultHousinValues with - id = lastHousingId; + housingId = lastHousingId; owner = caller; + calendary = {dayZero = now(); reservations = []}; + reservationsPending = []; }; - let kinds = addIdToHostKind(user.kinds, lastHousingId); - ignore Map.put(users, phash, caller, {user with kinds }); - ignore Map.put(housings, nhash, lastHousingId,newHousing ); - ignore Map.put(calendars, nhash, lastHousingId, {dayZero= now(); reservedDays= []}); + let housingIdsUser = List.push(lastHousingId, hostUser.housingIds); + ignore Map.put(hostUsers, phash, caller, {hostUser with housingIds = housingIdsUser}); + ignore Map.put(housings, nhash, lastHousingId, newHousing ); #Ok(lastHousingId) } } @@ -385,18 +232,19 @@ shared ({ caller }) actor class Triourism () = this { func isPublishable(housing: Housing): Bool { (housing.price != null) and (not addressEqual(housing.address, NULL_LOCATION)) and - (housing.properties.size() > 0) + (housing.properties != null) }; public shared ({ caller }) func publishHousing(housingId: HousingId): async {#Ok; #Err: Text}{ - let user = Map.get(users, phash, caller); - switch user { + let hostUser = Map.get(hostUsers, phash, caller); + switch hostUser { case null { #Err(msg.NotUser)}; - case ( ?user ) { + case ( ?hostUser ) { let housing = Map.get(housings, nhash, housingId); switch housing { case null { #Err(msg.NotHousing)}; case ( ?housing ) { + if(housing.owner != caller) { return #Err(msg.CallerNotHousingOwner)}; if(isPublishable(housing)) { ignore Map.put(housings, nhash, housingId, {housing with active = true}); #Ok @@ -540,121 +388,83 @@ shared ({ caller }) actor class Triourism () = this { } } }; - - func createHousingType(user: Principal, propertiesOfType: Types.Property): {#Ok; #Err: Text} { - let housingTypesMap: HousingTypesMap = - switch(Map.get(housingTypesByHostOwner, phash, user)){ - case null {Map.new() }; - case ( ?map ) { map } - }; - switch (Map.get( - housingTypesMap, - thash, - propertiesOfType.nameType)) { - case null { - // Creación del nuevo tipo - ignore Map.put( - housingTypesMap, - thash, - propertiesOfType.nameType, - {properties = propertiesOfType; housingIds: [HousingId] = []} - ); - ignore Map.put(housingTypesByHostOwner, phash, user, housingTypesMap); - #Ok - }; - case (_) { - #Err(msg.HousingTypeExist) - } - } - }; - - func putHousingType(user: Principal, propertiesOfType: Types.Property, housingId: HousingId ){ - let housingTypesMap: HousingTypesMap = - switch(Map.get(housingTypesByHostOwner, phash, user)){ - case null {Map.new() }; - case ( ?map ) { map } - }; - let housingType: {properties: Types.Property; housingIds: [HousingId]} = - switch (Map.get( - housingTypesMap, - thash, - propertiesOfType.nameType)){ - case null {{properties = propertiesOfType; housingIds = [housingId]}}; - case (?housingType) { - let housingIds= Prim.Array_tabulate( - housingType.housingIds.size() + 1, - func x = if(x == 0) { housingId } else { housingType.housingIds[x - 1] } - ); - {properties = propertiesOfType; housingIds} - } - }; - ignore Map.put( - housingTypesMap, thash, propertiesOfType.nameType, housingType - ); - - }; - public shared ({ caller }) func assignHousingType({housingId: HousingId; qty: Nat; propertiesOfType: Types.Property}): async {#Ok; #Err: Text} { + public shared ({ caller }) func cloneHousingWithProperties({housingId: HousingId; qty: Nat; housingTypeInit: HousingTypeInit}): async {#Ok; #Err: Text} { - let user = Map.get(users, phash, caller); - switch user { - case null {return #Err(msg.NotUser)}; - case ( ?user ) { - if (qty < 1) { return #Err(msg.ZeroIsNotAllowed)}; + let hostUser = Map.get(hostUsers, phash, caller); + switch hostUser { + case null { + return #Err(msg.NotHostUser) + }; + case ( ?hostUser ) { + if (Map.has(hostUser.housingTypes, thash, housingTypeInit.nameType)) { + return #Err(msg.HousingTypeExist) + }; let housing = Map.get(housings, nhash, housingId); switch housing { - case null { #Err(msg.NotHousing)}; + case ( null ) { return #Err(msg.NotHousing)}; case ( ?housing ) { - if(caller != housing.owner) { - return #Err(msg.CallerNotHousingOwner); + if(housing.owner != caller) { + return #Err(msg.CallerNotHousingOwner) }; - let createResponse = createHousingType(caller, propertiesOfType); - switch createResponse { - case (#Ok) { - ignore Map.put(housings, nhash, housingId, {housing with properties = [propertiesOfType]}); - // agregamos la habitacion actual al nuevo tipo creado - putHousingType(caller, propertiesOfType, housingId); - // Clonacion de habitacion segun qty con valores por defecto para las nuevas - var index = 1; // la habitacion a partir de la que se define el tipo no se cuenta porque ya tiene su propio id - while (index < qty ){ - lastHousingId += 1; - let newHousing: Housing = { - housing with - id = lastHousingId; - active = false; - propertiesOfType - }; - putHousingType(caller, propertiesOfType, lastHousingId); - ignore Map.put(users, phash, caller, user ); - ignore Map.put(housings, nhash, lastHousingId, newHousing ); - - index += 1; - }; - #Ok + //Establecemos el tipo al housing a clonar + let housingType: HousingType = {housingTypeInit with housingIds: [HousingId] = []}; + ignore Map.put( + housings, + nhash, + housingId, + { housing with properties = ?housingType; housingType = ?housingTypeInit.nameType}); + + var i = qty; + var housingIdsOfThisType = Buffer.fromArray([housingId]); + while (i > 0) { + lastHousingId += 1; + housingIdsOfThisType.add(lastHousingId); + + let newHousing: Housing = { + defaultHousinValues with + owner = caller; + housingId = lastHousingId; + namePlace = housing.namePlace; + nameHost = housing.nameHost; + descriptionPlace = housing.descriptionPlace; + descriptionHost = housing.descriptionHost; + link = housing.link; + photos = []; + properties = null; + housingType = ?housingTypeInit.nameType; + thumbnail = housing.thumbnail; + calendary = {dayZero = now(); reservations = []}; + reservationsPending = []; }; - case (#Err(msg)) {#Err(msg)} + ignore Map.put(housings, nhash, lastHousingId, newHousing ); + i -= 1; }; + let housingIds = Buffer.toArray(housingIdsOfThisType); + ignore Map.put(hostUser.housingTypes, thash, housingType.nameType, {housingType with housingIds}); + #Ok } - } + }; } }; }; + - public shared ({ caller }) func removeHousingType(housingType: Text): async {#Ok; #Err: Text}{ - let myHousingTypesMap = Map.get(housingTypesByHostOwner, phash, caller); - switch myHousingTypesMap { - case null {#Err("Not housing types")}; - case (?housingTypesMap){ - let removedType = Map.remove( - housingTypesMap, thash, housingType - ); - switch removedType { - case null { #Err("Not housing type")}; - case (?removedType) {#Ok} - } - } - } - }; + // public shared ({ caller }) func removeHousingType(housingType: Text): async {#Ok; #Err: Text}{ + // let myHousingTypesMap = Map.get(housingTypesByHostOwner, phash, caller); + // switch myHousingTypesMap { + // case null {#Err("Not housing types")}; + // case (?housingTypesMap){ + // let removedType = Map.remove( + // housingTypesMap, thash, housingType + // ); + // switch removedType { + // case null { #Err("Not housing type")}; + // case (?removedType) {#Ok} + // } + // } + // } + // }; public shared ({ caller }) func setAmenities(amenities: Types.Amenities, housingId: HousingId): async {#Ok; #Err: Text}{ let housing = Map.get(housings, nhash, housingId); @@ -674,7 +484,7 @@ shared ({ caller }) actor class Triourism () = this { } }; - ///////////////////////////////////////// Getters //////////////////////////////////////// + ///////////////////////////////////////// Getters //////////////////////////////////////// public query func getHousingPaginate({page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { if(Map.size(housings) < page * qtyPerPage){ @@ -689,20 +499,20 @@ shared ({ caller }) actor class Triourism () = this { }; index += 1; }; + let array = Buffer.toArray(bufferHousingPreview); #Ok{ - array = Buffer.toArray(bufferHousingPreview); - hasNext = ((page + 1) * qtyPerPage < values.size()) + array; + hasNext = ((page + 1) * qtyPerPage < array.size()) } }; public shared query ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ + //agregar lista de marcados como pendientes de verificacion switch (Map.get(housings, nhash, id)) { case (?housing) { if(housing.active) { - switch (Map.get(calendars, nhash, id)) { - case null { return #Err(msg.NotHousing)}; - case (?calendar) { return #Ok(calendar) } - } + let calendary = updateCalendary(id); + #Ok(calendary) } else { return #Err(msg.InactiveHousing) } @@ -712,22 +522,28 @@ shared ({ caller }) actor class Triourism () = this { }; - public query func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { + public func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { let housing = Map.get(housings, nhash, housingId); + return switch housing { case null { #Err(msg.NotHousing)}; case (?housing) { + let reservationsPending = cleanPendingVerifications(housingId, housing.reservationsPending); if(not housing.active and housing.owner != caller) { return #Err(msg.InactiveHousing) }; + let calendary = updateCalendary(housingId); if(photoIndex == 0){ let housingResponse: HousingResponse = #Start({ housing with + reservationsPending; + calendary; photos = if(housing.photos.size() > 0) { [housing.photos[0]] } else { [] }; hasNextPhoto = (housing.photos.size() > photoIndex + 1) }); #Ok(housingResponse); } else { + if (photoIndex >= housing.photos.size()) { return #Err(msg.PaginationOutOfRange)}; let housingResponse: HousingResponse = #OnlyPhoto({ photo = housing.photos[photoIndex]; hasNextPhoto = (housing.photos.size() > photoIndex + 1) @@ -739,24 +555,32 @@ shared ({ caller }) actor class Triourism () = this { } }; - //TODO servicio que devuelva los tipos + public shared ({ caller }) func getMyHousingTypes(): async {#Ok: [{typeName: Text; housingIds: [Nat]}]; #Err: Text}{ + let hostUser = Map.get(hostUsers, phash, caller); + switch hostUser { + case null {#Err(msg.NotHostUser)}; + case (?hostUser) { + #Ok( + Array.map<(Text, HousingType),{ typeName: Text; housingIds: [Nat]} >( + Map.toArray(hostUser.housingTypes), + func x = {typeName = x.0; housingIds = x.1.housingIds} + ) + ) + }; + } + }; public shared query ({ caller }) func getMyHousingsByType({housingType: Text; page: Nat}): async ResultHousingPaginate{ - let housingTypeMap = Map.get(housingTypesByHostOwner, phash, caller); - switch housingTypeMap { - case null {#Err("Not Housing Types")}; - case (?map) { - let housingsType = Map.get( - map, - thash, - housingType - ); - switch housingsType { - case null { #Err("Not housing type")}; - case (?housingType) { + let hostUser = Map.get(hostUsers, phash, caller); + switch hostUser { + case null {#Err(msg.NotHostUser)}; + case (?hostUser) { + switch (Map.get(hostUser.housingTypes, thash, housingType)){ + case null { return #Err(msg.HousingTypeNoExist)}; + case ( ?housingType ) { getPaginateHousings(housingType.housingIds, page) } - } + } }; } }; @@ -771,7 +595,7 @@ shared ({ caller }) actor class Triourism () = this { resultBuffer.add( { active = housing.active; - id = ids[index]; + housingId = ids[index]; address = housing.address; thumbnail = housing.thumbnail; price = housing.price; @@ -785,93 +609,181 @@ shared ({ caller }) actor class Triourism () = this { #Ok({array = Buffer.toArray(resultBuffer); hasNext: Bool}); }; - func getHousingsPaginateByOwner({owner: Principal; page: Nat}): ResultHousingPaginate { - let user = Map.get(users, phash, owner); - switch user { - case null { #Err("There is no user associated with the caller")}; - case ( ?user ) { - for( k in user.kinds.vals()){ - switch k { - case (#Host(hostIds)) { - let bufferHousingreview = Buffer.fromArray([]); - var index = page * 10; - while(index < hostIds.size() and index < 10 * (page + 1)){ - let housing = Map.get(housings, nhash, hostIds[index]); - switch housing { - case null{ }; - case ( ?housing ) { - let prev: HousingPreview = housing; - bufferHousingreview.add(prev); - } - }; - index += 1; - }; - return #Ok({array = Buffer.toArray(bufferHousingreview); hasNext = hostIds.size() > 10 * (page +1)}) + func getHousingsPaginateByOwner(owner: Principal, page: Nat, qtyPerPage: Nat, onlyActives: Bool): ResultHousingPaginate { + let hostUser = Map.get(hostUsers, phash, owner); + + switch hostUser { + case null { #Err(msg.NotHostUser)}; + case ( ?hostUser ) { + // let housingArray = List.toArray(hostUser.housingIds); + let housingPreviewBuffer = Buffer.fromArray([]); + for(id in List.toIter(hostUser.housingIds)){ + switch (Map.get(housings, nhash, id)){ + case (?housing) { + if(not onlyActives or housing.active){ + housingPreviewBuffer.add(housing) + } }; - case _ {}; - } + case _ { } + }; + }; + let arrayHousingPreview = Buffer.toArray(housingPreviewBuffer); + if ( arrayHousingPreview.size() < page * qtyPerPage){ + return #Err(msg.PaginationOutOfRange); }; - #Err("The user is not a hosting type user") + let (size: Nat, hasNext: Bool) = if (arrayHousingPreview.size() >= (page + 1) * qtyPerPage){ + (qtyPerPage, arrayHousingPreview.size() > (page + 1)) + } else { + (arrayHousingPreview.size() % qtyPerPage, false) + }; + return #Ok({array = Array.subArray(arrayHousingPreview, page * qtyPerPage, size); hasNext : Bool}) + } } }; - public shared query ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate{ - getHousingsPaginateByOwner({owner = caller; page}) + public shared query ({ caller }) func getMyHousingsPaginate({page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate{ + getHousingsPaginateByOwner(caller, page, qtyPerPage, false) + }; + + public shared query ({ caller }) func getMyActiveHousings({page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate{ + getHousingsPaginateByOwner(caller, page, qtyPerPage, true) + + }; + + public shared query func getHousingByHostUser({host: Principal; page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate{ + getHousingsPaginateByOwner(host, page, qtyPerPage, true) + }; + + func updateCalendary(housingId: HousingId): Calendary { + switch (Map.get(housings, nhash, housingId)){ + case null {assert false; {dayZero = 0; reservations = []}}; + case ( ?housing ) { + let startOfCurrentDayGMT = now() - now() % nanoSecPerDay ; // Timestamp inicio del dia actual en GTM + 0 + let daysSinceLastUpdate = (startOfCurrentDayGMT - housing.calendary.dayZero) / nanoSecPerDay ; + if(daysSinceLastUpdate > 0){ + var updateArray = Array.filter( + housing.calendary.reservations, + func(x: Reservation): Bool {x.checkOut >= daysSinceLastUpdate} + ); + updateArray := Prim.Array_tabulate( + updateArray.size(), + func x {{ + updateArray[x] with + checkIn = updateArray[x].checkIn - daysSinceLastUpdate; + checkOut = updateArray[x].checkOut -daysSinceLastUpdate + }} + ); + let calendary = {dayZero = startOfCurrentDayGMT; reservations = updateArray}; + let housingUpdate = {housing with calendary}; + ignore Map.put(housings, nhash, housingId, housingUpdate); + return calendary + } else { + return housing.calendary + }; + } + } }; - func updateCalendary(housingId: HousingId, calendary: Calendary): Calendary { - print("Actualizando calendario"); - let curranDay = now(); - let pastDays = (curranDay - calendary.dayZero) / (24 * 60 * 60 * 1_000_000_000); - print("Dias pasados: " # Int.toText(pastDays)); - if(pastDays > 0){ - var updateArray = Array.filter( - calendary.reservedDays, - func(x: Int): Bool {x > pastDays} - ); - updateArray := Prim.Array_tabulate( - updateArray.size(), - func (x: Nat) = updateArray[x] - pastDays - ); - let updateCalendar = {dayZero = curranDay; reservedDays = updateArray}; - ignore Map.put(calendars, nhash, housingId, updateCalendar); - return updateCalendar; + func cleanPendingVerifications(housingId: Nat, ids: [Nat]): [Nat] { //Remueve las solicitudes no confirmadas y con el tiempo de confirmacion transcurrido; + // TODO Esta funcion viola los principios solid ya que se encarga de limpiar las solicitudes pendientes tanto del Map general + // como tambien los id de solicitudes de dentro de las estructuras de los housing y ademas devuelve un array con los ids vigentes + // correspondientes al HousingID pasado por parametro. Ealuar alguna refactorizacion + var reservationsPendingForId = ids; + for ((id, reservation) in Map.toArray(reservationsPendingConfirmation).vals()) { + if (now() > reservation.date + TimeToPay) { + print("Solicitud de reserva " # Nat.toText(id) # " Eliminada"); + ignore Map.remove(reservationsPendingConfirmation, nhash, id); + let housing = Map.get(housings, nhash, reservation.housingId); + switch housing { + case null { }; + case (?housing) { + let reservationsPending = Array.filter( + housing.reservationsPending, + func x = x != id + ); + print("Id de solicitud " # Nat.toText(id) # " borrado\nreservas pendientes: "); + print(debug_show(reservationsPending)); // revisar el filter + if (id == housingId ) { + reservationsPendingForId := reservationsPending; + print("Id de reserva: " # Nat.toText(housingId)); + print(debug_show(reservationsPendingForId)) + }; + ignore Map.put(housings, nhash, reservation.housingId, {housing with reservationsPending}); + } + } + } }; - calendary + reservationsPendingForId + }; + + func getPendingReservations(ids: [Nat]): {pendings: [Reservation]; pendingReservUpdate: [Nat]}{ + let bufferReservations = Buffer.fromArray([]); + let bufferReservUpdate = Buffer.fromArray([]); + for (id in ids.vals()) { + switch (Map.get(reservationsPendingConfirmation, nhash, id)) { + case null {bufferReservUpdate.add(id)}; + case ( ?reservation ) { + // Si la solicitud es antigua se elimina del map y se devuelte el id para limpiar + if ( now() > reservation.date + TimeToPay) { + ignore Map.remove(reservationsPendingConfirmation, nhash, id); + print("reserva " # Nat.toText(id) # " eliminada"); + } else { + bufferReservations.add(reservation); + bufferReservUpdate.add(id); + } + }; + }; + }; + {pendings = Buffer.toArray(bufferReservations); pendingReservUpdate = Buffer.toArray(bufferReservUpdate)} }; func checkDisponibility(housingId: HousingId, chechIn: Int, checkOut: Int): Bool { switch (Map.get(housings, nhash, housingId)) { - case (?housing) { if(not housing.active) { return false } }; - case _ { return false } - }; - switch (Map.get(calendars, nhash, housingId)) { - case null { false }; - case (?calendar) { - print("Calendario encontrado"); - let updatedCalendary = updateCalendary(housingId, calendar); - var checkDay = chechIn; - while (checkDay <= checkOut) { - for(occuped in updatedCalendary.reservedDays.vals()){ - if(checkDay == occuped) { - return false; + case (?housing) { + if(not housing.active) { return false } + else { + // let updatedCalendary = updateCalendary(housing.calendary); + let calendary = updateCalendary(housingId); + var checkDay = chechIn; + let {pendings; pendingReservUpdate} = getPendingReservations(housing.reservationsPending); + ignore Map.put(housings, nhash, housingId, {housing with reservationsPending = pendingReservUpdate}); + while (checkDay < checkOut) { + for (occuped in calendary.reservations.vals()){ + if(checkDay >= occuped.checkIn and checkDay < occuped.checkOut) { + return false; + }; + }; + if (pendingReservUpdate.size() > 0) { + print("Hay " # Nat.toText(pendingReservUpdate.size()) # " reservations pendientes"); + for (bloqued in pendings.vals()){ + print(debug_show(bloqued)); + if(checkDay >= bloqued.checkIn and checkDay < bloqued.checkOut) { + return false; + }; + } }; + checkDay += 1; }; - checkDay += 1; - }; - return true - } - } + return true + } + }; + case _ { return false } + }; + }; + ////////// view reservations pendding ///////////////// + public query func endingReserv(): async () { + print(debug_show(Map.toArray(reservationsPendingConfirmation))) }; - public shared query ({ caller }) func getMyHousingDisponibility({checkIn: Nat; checkOut: Nat; page: Nat}): async ResultHousingPaginate{ - let response = getHousingsPaginateByOwner({owner = caller; page}); + ////////////////////////////////////////////////////// + public shared query ({ caller }) func getMyHousingDisponibility({checkIn: Nat; checkOut: Nat; page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate{ + let response = getHousingsPaginateByOwner(caller, page, qtyPerPage, true); switch response { case (#Ok({array; hasNext} )){ let bufferResults = Buffer.fromArray([]); for(hostPreview in array.vals()){ - if(checkDisponibility(hostPreview.id, checkIn, checkOut)){ + if(checkDisponibility(hostPreview.housingId, checkIn, checkOut)){ bufferResults.add(hostPreview); }; }; @@ -881,6 +793,35 @@ shared ({ caller }) actor class Triourism () = this { } }; + public func getDisponibilityById(housingId: Nat, period: {#M30; #M60; #M90; #M120} ): async {#Ok: [Int]; #Err: Text} { //Devuelve los dias no disponibles + //TODO marcar los dias pendientes de confirmacion de reserva + let housing = Map.get(housings, nhash, housingId); + let maxPeriod: Int = switch period { + case ( #M30 ) { 30 }; + case ( #M60 ) { 60 }; + case ( #M90 ) { 90 }; + case ( #M120) { 120 }; + }; + switch housing { + case null { #Err(msg.NotHousing)}; + case (?housing) { + if(not housing.active) { return #Err(msg.InactiveHousing)}; + let bufferDaysOccuped = Buffer.fromArray([]); + let calendary = updateCalendary(housingId); + print(debug_show(calendary)); + for(reservation in calendary.reservations.vals()){ + var dayOccuped = reservation.checkIn; + while (dayOccuped <= reservation.checkOut and dayOccuped < maxPeriod) { + bufferDaysOccuped.add(dayOccuped); + dayOccuped += 1; + }; + if (dayOccuped >= maxPeriod) {return #Ok(Buffer.toArray(bufferDaysOccuped))} + }; + #Ok(Buffer.toArray(bufferDaysOccuped)) + } + } + }; + public query func getAmenities({housingId: HousingId}): async ?Types.Amenities { let housing = Map.get(housings, nhash, housingId); switch housing { @@ -891,6 +832,157 @@ shared ({ caller }) actor class Triourism () = this { } }; + ///////////////////////////// Reservations //////////////////////////// + + func blobToText(t: Blob): Text { + var result = ""; + let chars = ["0", "1" , "2" , "3" ,"4" , "5" , "6" , "7" , "8" , "9" , "a" , "b" , "c" , "d" , "e" , "f"]; + for (c in Blob.toArray(t).vals()){ + result #= chars[Nat8.toNat(c) / 16]; + result #= chars[Nat8.toNat(c) % 16] + }; + result + }; + + func calculatePrice(price: ?Types.Price, days: Nat): Nat { + switch price { + case null {assert (false); 0 }; + case (?price) { + var currentDiscount = 0; + let discounts = Array.sort<{minimumDays: Nat; discount: Nat}>( + price.discountTable, + func (a, b) = if (a.minimumDays < b.minimumDays) { #less } else { #greater } + ); + for(discount in discounts.vals()){ + print(debug_show(discount)); + if(days < discount.minimumDays) { return (price.base * days) - (price.base * days * currentDiscount / 100) }; + currentDiscount := discount.discount; + }; + return (price.base * days) - (price.base * days * currentDiscount / 100); + } + } + }; + + func putRequestReservationToHousing(housingId: HousingId, requestId: Nat) { + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null {assert false}; + case ( ?housing ) { + let reservationsPending = Prim.Array_tabulate( + housing.reservationsPending.size() + 1, + func x = if( x == 0 ) {requestId} else {housing.reservationsPending[x - 1]} + ); + ignore Map.put(housings, nhash, housingId, {housing with reservationsPending}); + } + } + }; + + public shared ({ caller = applicant }) func requestReservation({housingId: HousingId; checkIn: Nat; checkOut: Nat; guest: Text}): async TransactionResponse { + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(checkDisponibility(housingId, checkIn, checkOut)){ + lastReservationId += 1; + let reservation: Reservation = { + date = now(); + housingId; + applicant; + checkIn; + checkOut; + guest; + reservationId = lastReservationId; + confirmated = false; + dataTransaction = null; + }; + ignore Map.put(reservationsPendingConfirmation, nhash, lastReservationId, reservation); + putRequestReservationToHousing(housingId, lastReservationId); + let price = calculatePrice(housing.price, checkOut - checkIn); + let dataTransaction: TransactionParams = { + // Se toma el account por defecto correspondiente al principal del dueño del Host + // Se puede establecer otro account proporcionado por el usuario + to = blobToText(AccountIdentifier.accountIdentifier(housing.owner, AccountIdentifier.defaultSubaccount())); + amount = Nat64.fromNat(price); + }; + #Ok({transactionParams = dataTransaction; reservationId = lastReservationId}); + } else { + #Err("") + }; + } + } + }; + + func verifyTransaction({from; to; amount}: DataTransaction): async Bool { + return true; // Test + let indexer_icp = actor("qhbym-qaaaa-aaaaa-aaafq-cai"): actor { + get_account_identifier_transactions : + shared query Indexer_icp.GetAccountIdentifierTransactionsArgs -> async Indexer_icp.GetAccountIdentifierTransactionsResult; + }; + let result = await indexer_icp.get_account_identifier_transactions({max_results = 10; start = null; account_identifier = from}); + switch result { + case (#Ok(response)) { + for (transaction in response.transactions.vals()) { + let operation = transaction.transaction.operation; + switch operation { + case( #Transfer(tx)) { + if (tx.from == from and + tx.to == to and + tx.amount.e8s >= amount){ + return true + } + }; + case ( _ ) { } + }; + }; + false + + }; + case (#Err(_)) { false } + } + + }; + + public shared ({ caller }) func confirmReservation({reservationId: Nat; txData: DataTransaction}): async {#Ok: Reservation; #Err: Text} { + if (await verifyTransaction(txData)){ + let reservation = Map.remove(reservationsPendingConfirmation, nhash, reservationId); + switch reservation { + case null { #Err(msg.NotReservation)}; + case ( ?reservation ){ + let housing = Map.get(housings, nhash, reservation.housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + let calendary = { + housing.calendary with + reservations = Prim.Array_tabulate( + housing.calendary.reservations.size() + 1, + func x = if (x == 0) { + { reservation with + confirmated = true; + dataTransaction = ?txData + } + } + else { + housing.calendary.reservations[x - 1] + } + ) + }; + print(debug_show(calendary)); + let reservationsPending = Array.filter( + housing.reservationsPending, + func x = x !=reservationId + ); + ignore Map.put(housings, nhash, reservation.housingId, { housing with calendary; reservationsPending}); + #Ok({ reservation with confirmated = true }) + } + } + } + } + } else { + #Err(msg.TransactionNotVerified) + } + + }; // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ // let housing = Map.get(housings, nhash, housingId); @@ -903,139 +995,8 @@ shared ({ caller }) actor class Triourism () = this { // #Ok(Map.toArray(housing.reservationRequests)) // } // } - // }; - - ///////////////////////////////// Reservations /////////////////////////////////////////// - - // public shared ({ caller }) func requestReservationOld({housingId: HousingId; data: ReservationDataInput}):async ReservationResult { - // let housing = Map.get(housings, nhash, housingId); - // switch housing { - // case null { - // #Err(msg.NotHosting); - // }; - // case (?housing) { - // print("housing"); - // /////// housing calendar update / housing Map update ////// - // let calendar = updateCalendar(housing.calendar); - // ignore Map.put(housings, nhash, housingId, {housing with calendar}); - - // ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// - // print("Momento actual en NanoSeg: " # Int.toText(now())); - // print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSec )); - // print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSec))); - // print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); - // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // if(now() + housing.minReservationLeadTimeNanoSec > data.checkIn){ - // return #Err("Reservations are requested at least " # - // Int.toText(housing.minReservationLeadTimeNanoSec /(NANO_SEG_PER_HOUR)) # - // " hours in advance."); - // }; - // if(availableAllDaysResquest(data.checkIn, data.checkOut)){ - // let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); - // let reservation = {data with applicant = caller}; - // let responseReservation = { - // reservationId; - // msg = "Espere" - // }; - // ignore Map.put(housing.reservationRequests, nhash, reservationId, reservation ); - // #Ok( responseReservation ) - // } else { - // #Err( msg.NotAvalableAllDays); - // } - // }; - // } - // }; - - // public shared ({ caller }) func requestReservation({housingId: HousingId; data: ReservationDataInput}): async ReservationResult{ - // let housing = Map.get(housings, nhash, housingId); - // switch housing { - // case null { - // #Err(msg.NotHosting); - // }; - // case (?housing) { - // print("housing"); - // /////// housing calendar update / housing Map update ////// - // let calendar = updateCalendar(housing.calendar); - // ignore Map.put(housings, nhash, housingId, {housing with calendar}); - - // ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// - // print("Momento actual en NanoSeg: " # Int.toText(now())); - // print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSec )); - // print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSec))); - // print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); - // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // if(now() + housing.minReservationLeadTimeNanoSec > data.checkIn){ - // return #Err("Reservations are requested at least " # - // Int.toText(housing.minReservationLeadTimeNanoSec /(NANO_SEG_PER_HOUR)) # - // " hours in advance."); - // }; - // if(availableAllDaysResquest(data.checkIn, data.checkOut)){ - // let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); - // let reservation = {data with applicant = caller}; - // let responseReservation = { - // housingId; - // reservationId; - // data = reservation; - // msg = msg.PayRequest; - // paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) - // }; - // ignore Map.put(housing.reservationRequests, nhash, reservationId, reservation ); - // #Ok( responseReservation ) - // } else { - // #Err( msg.NotAvalableAllDays); - // } - // }; - // } - // }; - // func paymentVerification(txHash: Nat):async Bool{ - // // TODO protocolo de verificacion de pago - // true - // }; - - // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId; txHash: Nat}): async {#Ok; #Err: Text}{ - // let housing = Map.get(housings, nhash, hostId); - // switch housing { - // case null { #Err(msg.NotHosting) }; - // case ( ?housing ) { - // let updatedCalendar = updateCalendar(housing.calendar); - // ignore Map.put(housings, nhash, hostId, {housing with updatedCalendar}); - // let reserv = Map.remove(housing.reservationRequests, nhash, reservId); - // switch reserv { - // case null { #Err(msg.NotReservation) }; - // case ( ?reserv ) { - // if(caller != reserv.applicant) { - // return #Err(msg.CallerIsNotrequester) - // }; - // // TODO Verificacion datos de pago a traves del txHhahs - // if (await paymentVerification(txHash)){ - // print("insertando la reserva en el calendario"); - // let calendar = insertReservationToCalendar(updatedCalendar, reserv); - // switch calendar { - // case (#Ok(calendar)) { - - // ignore Map.put(housings, nhash, hostId, {housing with calendar}); - // return #Ok - // }; - // case (_) { - // ignore Map.put(housing.reservationRequests, nhash, reservId, reserv); - // return #Err("Error") - // } - // } - // }; - // #Err("Incorrect payment verification") - // } - // } - // } - // } - // }; - - // // TODO confirmacion de reservacion por parte del dueño del Host - // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId}): async (){ - - // }; - + // } + }; diff --git a/backend/mainOld.mo b/backend/mainOld.mo new file mode 100644 index 0000000..c0bafa2 --- /dev/null +++ b/backend/mainOld.mo @@ -0,0 +1,907 @@ +import Prim "mo:⛔"; +import Map "mo:map/Map"; +import Set "mo:map/Set"; +import { phash; nhash; thash } "mo:map/Map"; +import Principal "mo:base/Principal"; +import Buffer "mo:base/Buffer"; +import Blob "mo:base/Blob"; +import Types "types"; +// import Int "mo:base/Int"; +import { now } "mo:base/Time"; +// import Rand "mo:random/Rand"; +import msg "constants"; + +////////////// Debug imports //////////////// +import { print } "mo:base/Debug"; +import Array "mo:base/Array"; +import Iter "mo:base/Iter"; +import Int "mo:base/Int"; + +shared ({ caller }) actor class Triourism () = this { + + type User = Types.User; + type UserKind = Types.UserKind; + type SignUpResult = Types.SignUpResult; + // type CalendaryPart = Types.CalendaryPart; + type Calendary = {dayZero: Int; reservedDays: [Int]}; + type ReservationDataInput = Types.ReservationDataInput; + type Reservation = Types.Reservation; + type HousingId = Types.HousingId; + // type HousingDataInit = Types.HousingDataInit; + type Housing = Types.Housing; + type HousingResponse = Types.HousingResponse; + type HousingPreview = Types.HousingPreview; + type HousingCreateData = Types.HousingCreateData; + type HousingTypesMap = Map.Map; + + type UpdateResult = Types.UpdateResult; + type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; hasNext: Bool}; #Err: Text}; + type PublishResult = {#Ok: HousingId; #Err: Text}; + + type ReservationResult = { + #Ok: { + reservationId: Nat; + msg: Text; + }; + #Err: Text; + }; + + // TODO revisar day, actualemnte es el timestamp de una hora especifica que delimita el comienzo del dia + + + stable let DEPLOYER = caller; + // let NANO_SEG_PER_HOUR = 60 * 60 * 1_000_000_000; + // let ramdomGenerator = Rand.Rand(); + + // stable var minReservationLeadTime = 24 * 60 * 60 * 1_000_000_000; // 24 horas en nanosegundos + + stable let admins = Set.new(); + ignore Set.put(admins, phash, caller); + stable let users = Map.new(); + stable let housings = Map.new(); + stable let housingTypesByHostOwner = Map.new(); + stable let calendars = Map.new(); + stable let reservationsRequests = Map.new(); + stable let reservationsConfirmed = Map.new(); + + stable var lastHousingId = 0; + + + ///////////////////////////////////// Update functions //////////////////////////////////////// + + private func _safeSignUp(p: Principal, data: Types.SignUpData, kind: UserKind): SignUpResult { + if(Principal.isAnonymous(p)){ + return #Err(msg.NotUser) + }; + let user = Map.get(users, phash, p); + switch user { + case (?User) { #Err("User already exists") }; + case null { + let newUser: User = { + data with + kinds: [UserKind] = [kind]; + verified = true; + score = 0; + }; + ignore Map.put(users, phash, p, newUser); + #Ok(newUser); + }; + }; + }; + + public shared ({ caller }) func signUp(data: Types.SignUpData) : async SignUpResult { + _safeSignUp(caller, data, #Initial) + }; + + public shared ({ caller }) func signUpAsHost(data: Types.SignUpData) : async SignUpResult { + _safeSignUp(caller, data, #Host([])); + + }; + + public shared query ({ caller }) func logIn(): async {#Ok: User; #Err} { + let user = Map.get(users, phash, caller); + switch user { + case null { #Err }; + case ( ?u ) { #Ok(u)} + }; + }; + + //////////////////////////////// CRUD Data User /////////////////////////////////// + + public shared ({ caller }) func editProfile(data: Types.SignUpData): async {#Ok; #Err}{ + let user = Map.get(users, phash, caller); + switch user { + case null { #Err }; + case (?user){ + ignore Map.put(users, phash, caller, { user with data}); + #Ok + }; + }; + }; + + + ///////////////////////////// Private functions /////////////////////////////////// + func isAdmin(p: Principal): Bool { Set.has(admins, phash, p) }; + + func isUser(p: Principal): Bool { Map.has(users, phash, p)}; + + func isHostUser(p: Principal): Bool { + let user = Map.get(users, phash, p); + switch user { + case null { false }; + case (?user) { + for (kind in user.kinds.vals()) { + switch kind { + case (#Host(_)) { return true }; + case _ {} + }; + }; + return false + } + } + }; + + func addIdToHostKind(arr: [Types.UserKind], id: HousingId): [Types.UserKind]{ + let bufferKinds = Buffer.fromArray([]); + for (k in arr.vals()) { + switch k { + case (#Host(ids)){ + let setIds = Set.fromIter(ids.vals(), nhash); + ignore Set.put(setIds, nhash, id); + bufferKinds.add(#Host(Set.toArray(setIds))) + }; + case (k) {bufferKinds.add(k)} + }; + }; + Buffer.toArray(bufferKinds); + }; + + func addressEqual(a: Types.Location, b: Types.Location ) : Bool { + a.country == b.country and + a.city == b.city and + a.neighborhood == b.neighborhood and + a.zipCode == b.zipCode and + a.externalNumber == b.externalNumber and + a.internalNumber == b.internalNumber + }; + + let NULL_LOCATION = { + country = ""; + city = ""; + neighborhood = ""; + zipCode = 0; street = ""; + externalNumber = 0; + internalNumber = 0 + }; + + let defaultHousinValues = { + active: Bool = false; + rules: [Types.Rule] = []; + price: ?Types.Price = null; + checkIn: Nat = 15; + checkOut: Nat = 12; + address: Types.Location = NULL_LOCATION; + properties: [Types.Property] = []; + amenities = null; + // calendar: [var Types.CalendaryPart] = [var]; + }; + + + + /////////////////////////// Manage admins functions ///////////////////////////////// + + public shared ({ caller }) func addAdmin(p: Principal): async {#Ok; #Err} { + if(not isAdmin(caller)){ + #Err + } else{ + ignore Set.put(admins, phash, p); + #Ok + } + }; + + public shared ({ caller }) func removeAdmin(p: Principal): async {#Ok; #Err} { + if(caller != DEPLOYER){ + #Err; + } else { + ignore Set.remove(admins, phash, p); + #Ok + } + }; + + /////////////////////////// Admin functions ////////////////////////////////////////////// + /////////////////////////////// Verification process ///////////////////////////////////// + // TODO actualmente todos los usuarios se inicializan como verificados + + // func userIsVerificated(u: Principal): Bool { + // let user = Map.get(users, phash, u); + // switch user{ + // case null { false }; + // case (?user) { user.verified}; + // }; + // }; + + //////////////////////////////// CRUD Housing //////////////////////////////////////////// + + + + + public shared ({ caller }) func createHousing(dataInit: HousingCreateData): async {#Ok: Nat; #Err: Text} { + let user = Map.get(users, phash, caller); + switch user { + case null {#Err(msg.NotUser)}; + case (?user) { + if (not isHostUser(caller)) { return #Err(msg.NotHostUser)}; + lastHousingId += 1; + + let newHousing: Housing = { + dataInit and + defaultHousinValues with + id = lastHousingId; + owner = caller; + }; + let kinds = addIdToHostKind(user.kinds, lastHousingId); + ignore Map.put(users, phash, caller, {user with kinds }); + ignore Map.put(housings, nhash, lastHousingId,newHousing ); + ignore Map.put(calendars, nhash, lastHousingId, {dayZero= now(); reservedDays= []}); + #Ok(lastHousingId) + } + } + }; + + func isPublishable(housing: Housing): Bool { + (housing.price != null) and + (not addressEqual(housing.address, NULL_LOCATION)) and + (housing.properties.size() > 0) + }; + + public shared ({ caller }) func publishHousing(housingId: HousingId): async {#Ok; #Err: Text}{ + let user = Map.get(users, phash, caller); + switch user { + case null { #Err(msg.NotUser)}; + case ( ?user ) { + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(isPublishable(housing)) { + ignore Map.put(housings, nhash, housingId, {housing with active = true}); + #Ok + } else { + #Err(msg.IsNotpublishable) + } + } + } + } + } + + }; + + public shared ({ caller }) func addPhotoToHousing({id: HousingId; photo: Blob}): async {#Ok; #Err: Text} { + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + #Err(msg.NotHousing) + }; + case (?housing) { + if(housing.owner != caller){ + return #Err(msg.CallerNotHousingOwner) + }; + let photos = Prim.Array_tabulate( + housing.photos.size() +1, + func i = if(i < housing.photos.size()) {housing.photos[i]} else {photo} + ); + ignore Map.put(housings, nhash, id, {housing with photos}); + print(debug_show({housing with photos})); + #Ok + } + } + }; + + public shared ({ caller }) func addThumbnailToHousing({id: HousingId; thumbnail: Blob}): async {#Ok; #Err: Text} { + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + #Err(msg.NotHousing) + }; + case (?housing) { + if(housing.owner != caller){ + return #Err(msg.UnauthorizedCaller) + }; + ignore Map.put(housings, nhash, id, {housing with thumbnail}); + #Ok + } + } + }; + + public shared ({ caller }) func updatePrices({id: HousingId; price_: Types.Price}): async UpdateResult{ + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + return #Err(msg.NotHousing); + }; + case (?housing) { + if(housing.owner != caller){ return #Err(msg.UnauthorizedCaller) }; + ignore Map.put(housings, nhash, id, {housing with price = ?price_}); + return #Ok + }; + } + }; + + public shared ({ caller }) func setRulesForHousing({id: HousingId; rules: [Types.Rule]}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + return #Err(msg.NotHousing); + }; + case (?housing) { + if(housing.owner != caller){ return #Err(msg.UnauthorizedCaller) }; + ignore Map.put(housings, nhash, id, {housing with rules}); + return #Ok + }; + } + }; + + // public shared ({ caller }) func setMinReservationLeadTime({id: HousingId; hours: Nat}):async {#Ok; #Err: Text} { + // let housing = Map.get(housings, nhash, id); + // switch housing { + // case null { + // return #Err(msg.NotHosting); + // }; + // case (?housing) { + // if(housing.owner != caller){ + // return #Err(msg.CallerNotHousingOwner); + // }; + // ignore Map.put( + // housings, + // nhash, + // id, + // {housing with minReservationLeadTimeNanoSeg = hours * NANO_SEG_PER_HOUR}); + // #Ok; + // } + + // } + // }; + + public shared ({ caller }) func setHousingStatus({id: HousingId; active: Bool}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, id); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + if (not isPublishable(housing)) { + return #Err(msg.IsNotpublishable) + }; + ignore Map.put(housings, nhash, id, {housing with active}); + #Ok + } + } + }; + + public shared ({ caller }) func setChekInCheckOut({housingId: HousingId; checkIn: Nat; checkOut: Nat}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put(housings, nhash, housingId, {housing with checkIn; checkOut}); + #Ok + } + } + }; + + public shared ({ caller }) func setAddress({housingId: HousingId; address: Types.Location}): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put(housings, nhash, housingId, {housing with address}); + #Ok + } + } + }; + + func createHousingType(user: Principal, propertiesOfType: Types.Property): {#Ok; #Err: Text} { + let housingTypesMap: HousingTypesMap = + switch(Map.get(housingTypesByHostOwner, phash, user)){ + case null {Map.new() }; + case ( ?map ) { map } + }; + switch (Map.get( + housingTypesMap, + thash, + propertiesOfType.nameType)) { + case null { + // Creación del nuevo tipo + ignore Map.put( + housingTypesMap, + thash, + propertiesOfType.nameType, + {properties = propertiesOfType; housingIds: [HousingId] = []} + ); + ignore Map.put(housingTypesByHostOwner, phash, user, housingTypesMap); + #Ok + }; + case (_) { + #Err(msg.HousingTypeExist) + } + } + }; + + func putHousingType(user: Principal, propertiesOfType: Types.Property, housingId: HousingId ){ + let housingTypesMap: HousingTypesMap = + switch(Map.get(housingTypesByHostOwner, phash, user)){ + case null {Map.new() }; + case ( ?map ) { map } + }; + let housingType: {properties: Types.Property; housingIds: [HousingId]} = + switch (Map.get( + housingTypesMap, + thash, + propertiesOfType.nameType)){ + case null {{properties = propertiesOfType; housingIds = [housingId]}}; + case (?housingType) { + let housingIds= Prim.Array_tabulate( + housingType.housingIds.size() + 1, + func x = if(x == 0) { housingId } else { housingType.housingIds[x - 1] } + ); + {properties = propertiesOfType; housingIds} + } + }; + ignore Map.put( + housingTypesMap, thash, propertiesOfType.nameType, housingType + ); + + }; + + public shared ({ caller }) func assignHousingType({housingId: HousingId; qty: Nat; propertiesOfType: Types.Property}): async {#Ok; #Err: Text} { + + let user = Map.get(users, phash, caller); + switch user { + case null {return #Err(msg.NotUser)}; + case ( ?user ) { + if (qty < 1) { return #Err(msg.ZeroIsNotAllowed)}; + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + let createResponse = createHousingType(caller, propertiesOfType); + switch createResponse { + case (#Ok) { + ignore Map.put(housings, nhash, housingId, {housing with properties = [propertiesOfType]}); + // agregamos la habitacion actual al nuevo tipo creado + putHousingType(caller, propertiesOfType, housingId); + // Clonacion de habitacion segun qty con valores por defecto para las nuevas + var index = 1; // la habitacion a partir de la que se define el tipo no se cuenta porque ya tiene su propio id + while (index < qty ){ + lastHousingId += 1; + let newHousing: Housing = { + housing with + id = lastHousingId; + active = false; + propertiesOfType + }; + putHousingType(caller, propertiesOfType, lastHousingId); + ignore Map.put(users, phash, caller, user ); + ignore Map.put(housings, nhash, lastHousingId, newHousing ); + + index += 1; + }; + #Ok + }; + case (#Err(msg)) {#Err(msg)} + }; + } + } + } + }; + }; + + public shared ({ caller }) func removeHousingType(housingType: Text): async {#Ok; #Err: Text}{ + let myHousingTypesMap = Map.get(housingTypesByHostOwner, phash, caller); + switch myHousingTypesMap { + case null {#Err("Not housing types")}; + case (?housingTypesMap){ + let removedType = Map.remove( + housingTypesMap, thash, housingType + ); + switch removedType { + case null { #Err("Not housing type")}; + case (?removedType) {#Ok} + } + } + } + }; + + public shared ({ caller }) func setAmenities(amenities: Types.Amenities, housingId: HousingId): async {#Ok; #Err: Text}{ + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put( + housings, + nhash, + housingId, + {housing with amenities = ?amenities}); + #Ok + } + } + }; + + ///////////////////////////////////////// Getters //////////////////////////////////////// + + public query func getHousingPaginate({page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { + if(Map.size(housings) < page * qtyPerPage){ + return #Err(msg.PaginationOutOfRange) + }; + let values = Map.toArray(housings); + let bufferHousingPreview = Buffer.fromArray([]); + var index = page * qtyPerPage; + while (index < values.size() and index < (page + 1) * qtyPerPage){ + if(values[index].1.active){ + bufferHousingPreview.add(values[index].1); + }; + index += 1; + }; + #Ok{ + array = Buffer.toArray(bufferHousingPreview); + hasNext = ((page + 1) * qtyPerPage < values.size()) + } + }; + + public shared query ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ + switch (Map.get(housings, nhash, id)) { + case (?housing) { + if(housing.active) { + switch (Map.get(calendars, nhash, id)) { + case null { return #Err(msg.NotHousing)}; + case (?calendar) { return #Ok(calendar) } + } + } else { + return #Err(msg.InactiveHousing) + } + }; + case null { return #Err(msg.NotHousing)}; + }; + + }; + + public query func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { + let housing = Map.get(housings, nhash, housingId); + return switch housing { + case null { #Err(msg.NotHousing)}; + case (?housing) { + if(not housing.active and housing.owner != caller) { + return #Err(msg.InactiveHousing) + }; + if(photoIndex == 0){ + let housingResponse: HousingResponse = #Start({ + housing with + photos = if(housing.photos.size() > 0) { [housing.photos[0]] } else { [] }; + hasNextPhoto = (housing.photos.size() > photoIndex + 1) + }); + #Ok(housingResponse); + } else { + let housingResponse: HousingResponse = #OnlyPhoto({ + photo = housing.photos[photoIndex]; + hasNextPhoto = (housing.photos.size() > photoIndex + 1) + }); + print(debug_show(housing.photos)); + #Ok(housingResponse) + } + }; + } + }; + + //TODO servicio que devuelva los tipos + + public shared query ({ caller }) func getMyHousingsByType({housingType: Text; page: Nat}): async ResultHousingPaginate{ + let housingTypeMap = Map.get(housingTypesByHostOwner, phash, caller); + switch housingTypeMap { + case null {#Err("Not Housing Types")}; + case (?map) { + let housingsType = Map.get( + map, + thash, + housingType + ); + switch housingsType { + case null { #Err("Not housing type")}; + case (?housingType) { + getPaginateHousings(housingType.housingIds, page) + } + } + }; + } + }; + + func getPaginateHousings(ids: [HousingId], page: Nat): ResultHousingPaginate { + let resultBuffer = Buffer.fromArray([]); + var index = page * 10; + while(index < (page + 1)* 10 and index < ids.size()){ + switch (Map.get(housings, nhash, ids[index])) { + case null {}; + case (?housing) { + resultBuffer.add( + { + active = housing.active; + id = ids[index]; + address = housing.address; + thumbnail = housing.thumbnail; + price = housing.price; + } + ) + } + }; + index += 1; + }; + let hasNext = ids.size() > (page + 1)* 10; + #Ok({array = Buffer.toArray(resultBuffer); hasNext: Bool}); + }; + + func getHousingsPaginateByOwner({owner: Principal; page: Nat}): ResultHousingPaginate { + let user = Map.get(users, phash, owner); + switch user { + case null { #Err("There is no user associated with the caller")}; + case ( ?user ) { + for( k in user.kinds.vals()){ + switch k { + case (#Host(hostIds)) { + let bufferHousingreview = Buffer.fromArray([]); + var index = page * 10; + while(index < hostIds.size() and index < 10 * (page + 1)){ + let housing = Map.get(housings, nhash, hostIds[index]); + switch housing { + case null{ }; + case ( ?housing ) { + let prev: HousingPreview = housing; + bufferHousingreview.add(prev); + } + }; + index += 1; + }; + return #Ok({array = Buffer.toArray(bufferHousingreview); hasNext = hostIds.size() > 10 * (page +1)}) + }; + case _ {}; + } + }; + #Err("The user is not a hosting type user") + } + } + }; + + public shared query ({ caller }) func getMyHousingsPaginate({page: Nat}): async ResultHousingPaginate{ + getHousingsPaginateByOwner({owner = caller; page}) + }; + + func updateCalendary(housingId: HousingId, calendary: Calendary): Calendary { + print("Actualizando calendario"); + let curranDay = now(); + let pastDays = (curranDay - calendary.dayZero) / (24 * 60 * 60 * 1_000_000_000); + print("Dias pasados: " # Int.toText(pastDays)); + if(pastDays > 0){ + var updateArray = Array.filter( + calendary.reservedDays, + func(x: Int): Bool {x > pastDays} + ); + updateArray := Prim.Array_tabulate( + updateArray.size(), + func (x: Nat) = updateArray[x] - pastDays + ); + let updateCalendar = {dayZero = curranDay; reservedDays = updateArray}; + ignore Map.put(calendars, nhash, housingId, updateCalendar); + return updateCalendar; + }; + calendary + }; + + func checkDisponibility(housingId: HousingId, chechIn: Int, checkOut: Int): Bool { + switch (Map.get(housings, nhash, housingId)) { + case (?housing) { if(not housing.active) { return false } }; + case _ { return false } + }; + switch (Map.get(calendars, nhash, housingId)) { + case null { false }; + case (?calendar) { + print("Calendario encontrado"); + let updatedCalendary = updateCalendary(housingId, calendar); + var checkDay = chechIn; + while (checkDay <= checkOut) { + for(occuped in updatedCalendary.reservedDays.vals()){ + if(checkDay == occuped) { + return false; + }; + }; + checkDay += 1; + }; + return true + } + } + }; + + public shared query ({ caller }) func getMyHousingDisponibility({checkIn: Nat; checkOut: Nat; page: Nat}): async ResultHousingPaginate{ + let response = getHousingsPaginateByOwner({owner = caller; page}); + switch response { + case (#Ok({array; hasNext} )){ + let bufferResults = Buffer.fromArray([]); + for(hostPreview in array.vals()){ + if(checkDisponibility(hostPreview.id, checkIn, checkOut)){ + bufferResults.add(hostPreview); + }; + }; + #Ok({array = Buffer.toArray(bufferResults); hasNext}) + }; + case (#Err(msg)) { #Err(msg) } + } + }; + + public query func getAmenities({housingId: HousingId}): async ?Types.Amenities { + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { null }; + case (?housing) { + housing.amenities + } + } + }; + + + // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ + // let housing = Map.get(housings, nhash, housingId); + // switch housing { + // case null {#Err(msg.NotHousing)}; + // case ( ?housing ) { + // if(housing.owner != caller ){ + // return #Err(msg.CallerNotHousingOwner); + // }; + // #Ok(Map.toArray(housing.reservationRequests)) + // } + // } + // }; + + ///////////////////////////////// Reservations /////////////////////////////////////////// + + // public shared ({ caller }) func requestReservationOld({housingId: HousingId; data: ReservationDataInput}):async ReservationResult { + // let housing = Map.get(housings, nhash, housingId); + // switch housing { + // case null { + // #Err(msg.NotHosting); + // }; + // case (?housing) { + // print("housing"); + // /////// housing calendar update / housing Map update ////// + // let calendar = updateCalendar(housing.calendar); + // ignore Map.put(housings, nhash, housingId, {housing with calendar}); + + // ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// + // print("Momento actual en NanoSeg: " # Int.toText(now())); + // print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSec )); + // print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSec))); + // print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); + // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // if(now() + housing.minReservationLeadTimeNanoSec > data.checkIn){ + // return #Err("Reservations are requested at least " # + // Int.toText(housing.minReservationLeadTimeNanoSec /(NANO_SEG_PER_HOUR)) # + // " hours in advance."); + // }; + // if(availableAllDaysResquest(data.checkIn, data.checkOut)){ + // let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); + // let reservation = {data with applicant = caller}; + // let responseReservation = { + // reservationId; + // msg = "Espere" + // }; + // ignore Map.put(housing.reservationRequests, nhash, reservationId, reservation ); + // #Ok( responseReservation ) + // } else { + // #Err( msg.NotAvalableAllDays); + // } + // }; + // } + // }; + + // public shared ({ caller }) func requestReservation({housingId: HousingId; data: ReservationDataInput}): async ReservationResult{ + // let housing = Map.get(housings, nhash, housingId); + // switch housing { + // case null { + // #Err(msg.NotHosting); + // }; + // case (?housing) { + // print("housing"); + // /////// housing calendar update / housing Map update ////// + // let calendar = updateCalendar(housing.calendar); + // ignore Map.put(housings, nhash, housingId, {housing with calendar}); + + // ///////////////////////////////////////////////////// DEBUGIN ////////////////////////////////////////////////////////// + // print("Momento actual en NanoSeg: " # Int.toText(now())); + // print("horas de anticipacio: " # Int.toText(housing.minReservationLeadTimeNanoSec )); + // print("Reserva a partir de fecha: " # Int.toText((now() + housing.minReservationLeadTimeNanoSec))); + // print("Fecha de ingreso silicitada " # Int.toText(data.checkIn)); + // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // if(now() + housing.minReservationLeadTimeNanoSec > data.checkIn){ + // return #Err("Reservations are requested at least " # + // Int.toText(housing.minReservationLeadTimeNanoSec /(NANO_SEG_PER_HOUR)) # + // " hours in advance."); + // }; + // if(availableAllDaysResquest(data.checkIn, data.checkOut)){ + // let reservationId = await ramdomGenerator.randRange(1_000_000_000, 9_999_999_999); + // let reservation = {data with applicant = caller}; + // let responseReservation = { + // housingId; + // reservationId; + // data = reservation; + // msg = msg.PayRequest; + // paymentCode = await ramdomGenerator.randRange(1_000_000_000_000_000_000_000, 9_999_999_999_999_999_999_999) + // }; + // ignore Map.put(housing.reservationRequests, nhash, reservationId, reservation ); + // #Ok( responseReservation ) + // } else { + // #Err( msg.NotAvalableAllDays); + // } + // }; + // } + // }; + // func paymentVerification(txHash: Nat):async Bool{ + // // TODO protocolo de verificacion de pago + // true + // }; + + // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId; txHash: Nat}): async {#Ok; #Err: Text}{ + // let housing = Map.get(housings, nhash, hostId); + // switch housing { + // case null { #Err(msg.NotHosting) }; + // case ( ?housing ) { + // let updatedCalendar = updateCalendar(housing.calendar); + // ignore Map.put(housings, nhash, hostId, {housing with updatedCalendar}); + // let reserv = Map.remove(housing.reservationRequests, nhash, reservId); + // switch reserv { + // case null { #Err(msg.NotReservation) }; + // case ( ?reserv ) { + // if(caller != reserv.applicant) { + // return #Err(msg.CallerIsNotrequester) + // }; + // // TODO Verificacion datos de pago a traves del txHhahs + // if (await paymentVerification(txHash)){ + // print("insertando la reserva en el calendario"); + // let calendar = insertReservationToCalendar(updatedCalendar, reserv); + // switch calendar { + // case (#Ok(calendar)) { + + // ignore Map.put(housings, nhash, hostId, {housing with calendar}); + // return #Ok + // }; + // case (_) { + // ignore Map.put(housing.reservationRequests, nhash, reservId, reserv); + // return #Err("Error") + // } + // } + // }; + // #Err("Incorrect payment verification") + // } + // } + // } + // } + // }; + + // // TODO confirmacion de reservacion por parte del dueño del Host + // public shared ({ caller }) func confirmReservation({reservId: Nat; hostId: HousingId}): async (){ + + // }; + + +}; + diff --git a/backend/types.mo b/backend/types.mo index 3a11260..e762dcd 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -1,44 +1,49 @@ import Map "mo:map/Map"; import Nat "mo:base/Nat"; +import List "mo:base/List"; + module { - ////////////////////////////// Users ///////////////////////////////////// + + ///////////////////////////////// Users ///////////////////////////////////// public type SignUpData = { firstName: Text; lastName: Text; phone: ?Nat; email: Text; - //avatar: ?Blob; + }; + + public type UserData = { + firstName: Text; + lastName: Text; }; public type User = SignUpData and { - // id: Nat; - kinds: [UserKind]; - // userKind: UserKind; verified: Bool; - score: Nat; + score: Nat; + reviewsIssued: List.List; }; - public type UserKind = { - #Initial; - #Guest: [ReviewsId]; - #Host: [HousingId]; + public type HostUser = SignUpData and { + verified: Bool; + score: Nat; + housingIds: List.List; + housingTypes: Map.Map; }; - public type SignUpResult = { #Ok : User; #Err : Text }; - - // type GetProfileError = { - // #userNotAuthenticated; - // #profileNotFound; - // }; - - // type CreateProfileError = { - // #profileAlreadyExists; - // #userNotAuthenticated; - // }; + public type SignUpResult = { + #Ok: UserData; + #Err: Text; + }; - ///////////////////////////////// Housing ///////////////////////////////// + public type Review = { + autor: Principal; + hostId: Nat; + date: Int; + body: Text; + }; + ///////////////////////////////// Housing /////////////////////////////////// public type HousingCreateData = { namePlace: Text; @@ -48,186 +53,165 @@ module { link: Text; photos: [Blob]; thumbnail: Blob; - - }; - - public type Rule = { - #PetsAllowed: Bool; - #SmookingAllowed: Bool; - #PartiesAllowed: Bool; - #AdditionalGuests: Bool; - #NoiseAfter10pm: Bool; - #ParkOnTheStreet: Bool; - #VisitsAllowed: Bool; - #CustomRule: {rule: Text; allowed: Bool}; - }; - - public type Amenities = { - freeWifi: Bool; - airCond: Bool; // Aire acondicionado - flatTV: Bool; // TV de pantalla plana - minibar: Bool; - safeBox: Bool; // Caja de seguridad - roomService: Bool; // Servicio a la habitación - premiumLinen: Bool; // Ropa de cama premium - ironBoard: Bool; // Plancha y tabla de planchar - privateBath: Bool; // Baño privado con artículos de tocador - hairDryer: Bool; // Secador de pelo - hotelRest: Bool; // Restaurante en el hotel - barLounge: Bool; // Bar y lounge - buffetBrkfst: Bool; // Desayuno buffet - lobbyCoffee: Bool; // Servicio de café/té en el lobby - catering: Bool; // Servicio de catering para eventos - specialMenu: Bool; // Menú para dietas especiales (bajo solicitud) - outdoorPool: Bool; // Piscina al aire libre - spaWellness: Bool; // Spa y centro de bienestar - gym: Bool; - jacuzzi: Bool; - gameRoom: Bool; // Salón de juegos - tennisCourt: Bool; // Pista de tenis - natureTrails: Bool; // Acceso a senderos naturales - custom: [{amenitieName: Text; value: Bool}]; - }; - - public type Location = { - country: Text; - city: Text; - neighborhood: Text; - zipCode: Nat; - street: Text; - externalNumber: Nat; - internalNumber: Nat; }; public type Housing = HousingCreateData and { active: Bool; - id: Nat; + housingId: Nat; price: ?Price; owner: Principal; rules: [Rule]; - checkIn: Int; - checkOut: Int; + checkIn: Nat; + checkOut: Nat; address: Location; - properties: [Property]; + properties: ?HousingType; + housingType: ?Text; amenities: ?Amenities; + reviews: List.List; + calendary: Calendary; + reservationsPending: [Nat]; }; - public type Bathroom = { - toilette: Bool; - shower: Bool; - bathtub: Bool; - isShared: Bool; - sink: Bool; - }; - public type Property = { + public type HousingTypeInit = { nameType: Text; - beds: [BedKind]; + beds: [BedKind]; bathroom: Bathroom; maxGuest: Nat; extraGuest: Nat; }; - // public type HousingDataInit = { - // minReservationLeadTimeNanoSec: Int; //valor en nanoSeg de anticipación para efectuar una reserva - // address: Text; - // prices: [Price]; - // kind: HousingKind; - // maxCapacity: Nat; - // description: Text; - // rules: [Text]; - // amenities: [Text]; - // properties: [Property]; - // }; - - // public type Housing = HousingDataInit and { - - // id: Nat; // Example L234324 - // owner: Principal; - // calendar: [var CalendaryPart]; - // reservationRequests: Map.Map; - // reviews: [Text]; - // photos: [Blob]; - // thumbnail: Blob; // Se recomienda la foto principal en tamaño reducido - // active: Bool; - // }; - - type BedKind = { - #Single: Nat; - #Matrimonial: Nat; - #SofaBed: Nat; - // Agregar mas variantes + public type HousingType = HousingTypeInit and { + housingIds: [HousingId]; + }; + + public type HousingId = Nat; + + public type HousingPreview = { + active: Bool; + housingId: Nat; + address: Location; + thumbnail: Blob; + price: ?Price; }; public type HousingResponse = { - #Start : Housing and { + #Start: Housing and { hasNextPhoto: Bool; }; - #OnlyPhoto :{ + #OnlyPhoto: { photo: Blob; hasNextPhoto: Bool; - } + }; }; - public type HousingPreview = { - active: Bool; - id: Nat; - address: Location; - thumbnail: Blob; - price: ?Price; + public type Bathroom = { + toilette: Bool; + shower: Bool; + bathtub: Bool; + isShared: Bool; + sink: Bool; }; - public type HousingId = Nat; + public type Rule = { + #PetsAllowed: Bool; + #SmookingAllowed: Bool; + #PartiesAllowed: Bool; + #AdditionalGuests: Bool; + #NoiseAfter10pm: Bool; + #ParkOnTheStreet: Bool; + #VisitsAllowed: Bool; + #CustomRule: {rule: Text; allowed: Bool}; + }; - public type UpdateResult = { - #Ok; - #Err: Text; + public type BedKind = { + #Single: Nat; + #Matrimonial: Nat; + #SofaBed: Nat; }; - // public type HousingKind = { - // #House; - // #Hotel_room: Text; //Ejemplo #Hotel_room("Single Room") - // #RoomWithSharedSpaces: [Rule]; //Hostels/Pensiones - // }; + public type Amenities = { + freeWifi: Bool; + airCond: Bool; // Aire acondicionado + flatTV: Bool; // TV de pantalla plana + minibar: Bool; + safeBox: Bool; // Caja de seguridad + roomService: Bool; // Servicio a la habitación + premiumLinen: Bool; // Ropa de cama premium + ironBoard: Bool; // Plancha y tabla de planchar + privateBath: Bool; // Baño privado con artículos de tocador + hairDryer: Bool; // Secador de pelo + hotelRest: Bool; // Restaurante en el hotel + barLounge: Bool; // Bar y lounge + buffetBrkfst: Bool; // Desayuno buffet + lobbyCoffee: Bool; // Servicio de café/té en el lobby + catering: Bool; // Servicio de catering para eventos + specialMenu: Bool; // Menú para dietas especiales (bajo solicitud) + outdoorPool: Bool; // Piscina al aire libre + spaWellness: Bool; // Spa y centro de bienestar + gym: Bool; + jacuzzi: Bool; + gameRoom: Bool; // Salón de juegos + tennisCourt: Bool; // Pista de tenis + natureTrails: Bool; // Acceso a senderos naturales + custom: [{amenitieName: Text; value: Bool}]; + }; - // public type Price = { - // #PerNight: Nat; - // #PerWeek: Nat; - // #CustomPeriod: [{dais: Nat; price: Nat}]; - // }; + public type Location = { + country: Text; + city: Text; + neighborhood: Text; + zipCode: Nat; + street: Text; + externalNumber: Nat; + internalNumber: Nat; + }; public type Price = { - base: Nat; // price per nigth - discountTable: [{minimumDays: Nat; discount: Nat}]; + base: Nat; + discountTable: [{minimumDays: Nat; discount: Nat}]; // Ej. [{minimumDays = 5; discount = 10}, {minimumDays = 15; discount = 15}] }; - // public type Rules = { // Ejemplo de Rule: {key = "Horarios"; value = "Sin ruidos molestos entre las 22:00 y las 8:00"} - // key: Text; - // value: Text - // }; + ////////////////////////////// Reservations //////////////////////////////// - public type ReviewsId = Text; - ///////////////////////////////// Reservations ///////////////////////////// + public type Calendary = { + dayZero: Int; + reservations: [Reservation]; + }; public type ReservationDataInput = { - checkIn: Int; //Timestamp NanoSeg - checkOut: Int; //Temestamp NanoSeg - guest: Text; + housingId: HousingId; + checkIn: Int; // Número de día de ingreso. Siendo 0 el día actual + checkOut: Int; // El egreso tiene que ser mayor que 1 + el ingreso + guest: Text; // Nombre del huésped }; - + public type Reservation = ReservationDataInput and { + date: Int; + reservationId: Nat; applicant: Principal; + confirmated: Bool; + dataTransaction: ?DataTransaction; }; - // La primer posicion es siempre el dia actual con lo cual cada vez que se consulta se tiene que actualizar antes - // Para facilitar la implementacion inicial se considera una lista de Disponibility mutable de 30 posiciones - public type Disponibility = {day: Int; available: Bool}; - - - public type CalendaryPart = Disponibility and {reservation: ?Reservation}; + public type TransactionParams = { + to: Text; + amount: Nat64; + }; + public type DataTransaction = TransactionParams and { + from: Text; + }; + public type TransactionResponse = { + #Ok: {transactionParams: TransactionParams; reservationId: Nat}; + #Err: Text; + }; + ///////////////////////////////// General /////////////////////////////////// -} + public type UpdateResult = { + #Ok; + #Err: Text; + }; - \ No newline at end of file +} diff --git a/backend/typesOld.mo b/backend/typesOld.mo new file mode 100644 index 0000000..25763a7 --- /dev/null +++ b/backend/typesOld.mo @@ -0,0 +1,233 @@ +import Map "mo:map/Map"; +import Nat "mo:base/Nat"; +module { + ////////////////////////////// Users ///////////////////////////////////// + + public type SignUpData = { + firstName: Text; + lastName: Text; + phone: ?Nat; + email: Text; + //avatar: ?Blob; + }; + + public type User = SignUpData and { + // id: Nat; + kinds: [UserKind]; + // userKind: UserKind; + verified: Bool; + score: Nat; + }; + + public type UserKind = { + #Initial; + #Guest: [ReviewsId]; + #Host: [HousingId]; + }; + + public type SignUpResult = { #Ok : User; #Err : Text }; + + // type GetProfileError = { + // #userNotAuthenticated; + // #profileNotFound; + // }; + + // type CreateProfileError = { + // #profileAlreadyExists; + // #userNotAuthenticated; + // }; + + ///////////////////////////////// Housing ///////////////////////////////// + + + public type HousingCreateData = { + namePlace: Text; + nameHost: Text; + descriptionPlace: Text; + descriptionHost: Text; + link: Text; + photos: [Blob]; + thumbnail: Blob; + + }; + + public type Rule = { + #PetsAllowed: Bool; + #SmookingAllowed: Bool; + #PartiesAllowed: Bool; + #AdditionalGuests: Bool; + #NoiseAfter10pm: Bool; + #ParkOnTheStreet: Bool; + #VisitsAllowed: Bool; + #CustomRule: {rule: Text; allowed: Bool}; + }; + + public type Amenities = { + freeWifi: Bool; + airCond: Bool; // Aire acondicionado + flatTV: Bool; // TV de pantalla plana + minibar: Bool; + safeBox: Bool; // Caja de seguridad + roomService: Bool; // Servicio a la habitación + premiumLinen: Bool; // Ropa de cama premium + ironBoard: Bool; // Plancha y tabla de planchar + privateBath: Bool; // Baño privado con artículos de tocador + hairDryer: Bool; // Secador de pelo + hotelRest: Bool; // Restaurante en el hotel + barLounge: Bool; // Bar y lounge + buffetBrkfst: Bool; // Desayuno buffet + lobbyCoffee: Bool; // Servicio de café/té en el lobby + catering: Bool; // Servicio de catering para eventos + specialMenu: Bool; // Menú para dietas especiales (bajo solicitud) + outdoorPool: Bool; // Piscina al aire libre + spaWellness: Bool; // Spa y centro de bienestar + gym: Bool; + jacuzzi: Bool; + gameRoom: Bool; // Salón de juegos + tennisCourt: Bool; // Pista de tenis + natureTrails: Bool; // Acceso a senderos naturales + custom: [{amenitieName: Text; value: Bool}]; + }; + + public type Location = { + country: Text; + city: Text; + neighborhood: Text; + zipCode: Nat; + street: Text; + externalNumber: Nat; + internalNumber: Nat; + }; + + public type Housing = HousingCreateData and { + active: Bool; + id: Nat; + price: ?Price; + owner: Principal; + rules: [Rule]; + checkIn: Int; + checkOut: Int; + address: Location; + properties: [Property]; + amenities: ?Amenities; + }; + + public type Bathroom = { + toilette: Bool; + shower: Bool; + bathtub: Bool; + isShared: Bool; + sink: Bool; + }; + public type Property = { + nameType: Text; + beds: [BedKind]; + bathroom: Bathroom; + maxGuest: Nat; + extraGuest: Nat; + }; + + // public type HousingDataInit = { + // minReservationLeadTimeNanoSec: Int; //valor en nanoSeg de anticipación para efectuar una reserva + // address: Text; + // prices: [Price]; + // kind: HousingKind; + // maxCapacity: Nat; + // description: Text; + // rules: [Text]; + // amenities: [Text]; + // properties: [Property]; + // }; + + // public type Housing = HousingDataInit and { + + // id: Nat; // Example L234324 + // owner: Principal; + // calendar: [var CalendaryPart]; + // reservationRequests: Map.Map; + // reviews: [Text]; + // photos: [Blob]; + // thumbnail: Blob; // Se recomienda la foto principal en tamaño reducido + // active: Bool; + // }; + + type BedKind = { + #Single: Nat; + #Matrimonial: Nat; + #SofaBed: Nat; + // Agregar mas variantes + }; + + public type HousingResponse = { + #Start : Housing and { + hasNextPhoto: Bool; + }; + #OnlyPhoto :{ + photo: Blob; + hasNextPhoto: Bool; + } + }; + + public type HousingPreview = { + active: Bool; + id: Nat; + address: Location; + thumbnail: Blob; + price: ?Price; + }; + + public type HousingId = Nat; + + public type UpdateResult = { + #Ok; + #Err: Text; + }; + + // public type HousingKind = { + // #House; + // #Hotel_room: Text; //Ejemplo #Hotel_room("Single Room") + // #RoomWithSharedSpaces: [Rule]; //Hostels/Pensiones + // }; + + // public type Price = { + // #PerNight: Nat; + // #PerWeek: Nat; + // #CustomPeriod: [{dais: Nat; price: Nat}]; + // }; + + public type Price = { + base: Nat; // price per nigth + discountTable: [{minimumDays: Nat; discount: Nat}]; // Ej. [{minimumDays = 5; discount = 10}, {minimumDays = 15; discount = 15}] + }; + + // public type Rules = { // Ejemplo de Rule: {key = "Horarios"; value = "Sin ruidos molestos entre las 22:00 y las 8:00"} + // key: Text; + // value: Text + // }; + + public type ReviewsId = Text; + ///////////////////////////////// Reservations ///////////////////////////// + + public type ReservationDataInput = { + checkIn: Int; //Timestamp NanoSeg + checkOut: Int; //Temestamp NanoSeg + guest: Text; + }; + + public type Reservation = ReservationDataInput and { + applicant: Principal; + }; + + // La primer posicion es siempre el dia actual con lo cual cada vez que se consulta se tiene que actualizar antes + // Para facilitar la implementacion inicial se considera una lista de Disponibility mutable de 30 posiciones + // public type Disponibility = {day: Int; available: Bool}; + + + // public type CalendaryPart = Disponibility and {reservation: ?Reservation}; + + + + +} + + \ No newline at end of file diff --git a/mops.toml b/mops.toml index 1e121a4..e83dd8d 100644 --- a/mops.toml +++ b/mops.toml @@ -1,4 +1,5 @@ [dependencies] base = "0.11.1" map = "9.0.1" -random = "1.0.1" \ No newline at end of file +random = "1.0.1" +account-identifier = "1.0.2" \ No newline at end of file diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index df93e78..b5ea19e 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -26,19 +26,24 @@ dfx canister call backend createHousing '(record { })' # Update Prices housing 1 dfx canister call backend updatePrices '(record { - id = 1 : nat; - prices = vec { - variant { PerNight = 30 : nat }; - variant { PerWeek = 200 : nat }; - }; + id = 1 : nat; + price = record { + base = 100_000_000 : nat; + discountTable = vec { + record { minimumDays = 5 : nat; discount = 5 : nat }; + record { minimumDays = 10 : nat; discount = 15 : nat }; + }; + } })' + + #Assing housing Type -dfx canister call backend assignHousingType '(record { +dfx canister call backend cloneHousingWithProperties '(record { housingId = 1 : nat; qty = 1 : nat; - propertiesOfType = record { + housingTypeInit = record { extraGuest = 2 : nat; bathroom = record { shower = true; @@ -68,11 +73,6 @@ dfx canister call backend setAddress '(record { }; })' -dfx canister call backend setHousingStatus '(record { - id = 1 : nat; - active = true; -})' - dfx canister call backend publishHousing 1 # CreateHousing 2 From 69d47577ab7016a20d35725c7d1755fdb5e4d241 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 13 Jan 2025 01:24:13 -0300 Subject: [PATCH 31/57] verifyTransaction check ammount registered --- backend/main.mo | 80 +++++++++++++++++-------------- backend/types.mo | 4 +- scripts-sh/deploy-with-content.sh | 66 +++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 40 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 2bb3248..c3cb31e 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -220,6 +220,7 @@ shared ({ caller }) actor class Triourism () = this { owner = caller; calendary = {dayZero = now(); reservations = []}; reservationsPending = []; + unavailability = { busy = []; notConfirmed = [] }; }; let housingIdsUser = List.push(lastHousingId, hostUser.housingIds); ignore Map.put(hostUsers, phash, caller, {hostUser with housingIds = housingIdsUser}); @@ -436,6 +437,7 @@ shared ({ caller }) actor class Triourism () = this { thumbnail = housing.thumbnail; calendary = {dayZero = now(); reservations = []}; reservationsPending = []; + unavailability = { busy = []; notConfirmed = [] }; }; ignore Map.put(housings, nhash, lastHousingId, newHousing ); i -= 1; @@ -877,32 +879,35 @@ shared ({ caller }) actor class Triourism () = this { } }; - public shared ({ caller = applicant }) func requestReservation({housingId: HousingId; checkIn: Nat; checkOut: Nat; guest: Text}): async TransactionResponse { + public shared ({ caller = requester }) func requestReservation({housingId: HousingId; checkIn: Nat; checkOut: Nat; guest: Text}): async TransactionResponse { let housing = Map.get(housings, nhash, housingId); + assert(Map.has(users, phash, requester)); switch housing { case null { #Err(msg.NotHousing)}; case ( ?housing ) { if(checkDisponibility(housingId, checkIn, checkOut)){ lastReservationId += 1; + let amount = calculatePrice(housing.price, checkOut - checkIn); let reservation: Reservation = { date = now(); housingId; - applicant; + reservationId = lastReservationId; + requester; checkIn; checkOut; guest; - reservationId = lastReservationId; confirmated = false; + amount; dataTransaction = null; }; ignore Map.put(reservationsPendingConfirmation, nhash, lastReservationId, reservation); putRequestReservationToHousing(housingId, lastReservationId); - let price = calculatePrice(housing.price, checkOut - checkIn); + let dataTransaction: TransactionParams = { // Se toma el account por defecto correspondiente al principal del dueño del Host // Se puede establecer otro account proporcionado por el usuario to = blobToText(AccountIdentifier.accountIdentifier(housing.owner, AccountIdentifier.defaultSubaccount())); - amount = Nat64.fromNat(price); + amount = Nat64.fromNat(amount); }; #Ok({transactionParams = dataTransaction; reservationId = lastReservationId}); } else { @@ -912,8 +917,9 @@ shared ({ caller }) actor class Triourism () = this { } }; - func verifyTransaction({from; to; amount}: DataTransaction): async Bool { + func verifyTransaction({from; to; amount}: DataTransaction, registeredAmount: Nat64): async Bool { return true; // Test + if(amount != registeredAmount) { return false }; let indexer_icp = actor("qhbym-qaaaa-aaaaa-aaafq-cai"): actor { get_account_identifier_transactions : shared query Indexer_icp.GetAccountIdentifierTransactionsArgs -> async Indexer_icp.GetAccountIdentifierTransactionsResult; @@ -943,44 +949,46 @@ shared ({ caller }) actor class Triourism () = this { }; public shared ({ caller }) func confirmReservation({reservationId: Nat; txData: DataTransaction}): async {#Ok: Reservation; #Err: Text} { - if (await verifyTransaction(txData)){ let reservation = Map.remove(reservationsPendingConfirmation, nhash, reservationId); switch reservation { case null { #Err(msg.NotReservation)}; case ( ?reservation ){ - let housing = Map.get(housings, nhash, reservation.housingId); - switch housing { - case null { #Err(msg.NotHousing)}; - case ( ?housing ) { - let calendary = { - housing.calendary with - reservations = Prim.Array_tabulate( - housing.calendary.reservations.size() + 1, - func x = if (x == 0) { - { reservation with - confirmated = true; - dataTransaction = ?txData + if (await verifyTransaction(txData, Nat64.fromNat(reservation.amount))){ + let housing = Map.get(housings, nhash, reservation.housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + let calendary = { + housing.calendary with + reservations = Prim.Array_tabulate( + housing.calendary.reservations.size() + 1, + func x = if (x == 0) { + { reservation with + confirmated = true; + dataTransaction = ?txData + } + } + else { + housing.calendary.reservations[x - 1] } - } - else { - housing.calendary.reservations[x - 1] - } - ) - }; - print(debug_show(calendary)); - let reservationsPending = Array.filter( - housing.reservationsPending, - func x = x !=reservationId - ); - ignore Map.put(housings, nhash, reservation.housingId, { housing with calendary; reservationsPending}); - #Ok({ reservation with confirmated = true }) + ) + }; + print(debug_show(calendary)); + let reservationsPending = Array.filter( + housing.reservationsPending, + func x = x !=reservationId + ); + ignore Map.put(housings, nhash, reservation.housingId, { housing with calendary; reservationsPending}); + #Ok({ reservation with confirmated = true }) + } } - } + } else { + #Err(msg.TransactionNotVerified) + }; + } } - } else { - #Err(msg.TransactionNotVerified) - } + }; diff --git a/backend/types.mo b/backend/types.mo index e762dcd..76f85de 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -70,6 +70,7 @@ module { reviews: List.List; calendary: Calendary; reservationsPending: [Nat]; + unavailability : {busy: [Nat]; notConfirmed: [Nat]} // busy es la lista de dias ocupados, siendo 0 el dia actual y notConfirmed es la lista de dias correspondientes a todas las solicitudes pendiente }; public type HousingTypeInit = { @@ -188,8 +189,9 @@ module { public type Reservation = ReservationDataInput and { date: Int; reservationId: Nat; - applicant: Principal; + requester: Principal; confirmated: Bool; + amount: Nat; dataTransaction: ?DataTransaction; }; diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index b5ea19e..260dca5 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -93,7 +93,7 @@ dfx canister call backend createHousing '(record { # ------------ Usuario Host 2 -------------- dfx identity new 0000TestUser2 dfx identity use 0000TestUser2 -dfx canister call backend signUpAsHost '(record { +dfx canister call backend signUpAsUser '(record { firstName="Lucila"; lastName="Peralta"; email="lucilaperalta@live.com"; @@ -113,7 +113,7 @@ dfx canister call backend signUpAsHost '(record { # ------------ Usuario Host 4 -------------- dfx identity new 0000TestUser4 dfx identity use 0000TestUser4 -dfx canister call backend signUpAsHost '(record { +dfx canister call backend signUpAsUser '(record { firstName="Mario"; lastName="Pappa"; email="mariopapa@gmail.com"; @@ -133,12 +133,72 @@ dfx canister call backend signUpAsHost '(record { # ------------ Usuario Host 6 -------------- dfx identity new 0000TestUser6 dfx identity use 0000TestUser6 -dfx canister call backend signUpAsHost '(record { +dfx canister call backend signUpAsUser '(record { firstName="Carlos"; lastName="Maldonado"; email="carlosmaldonado.com"; phone = opt 5422145789544 })' +# ------------ Usuario 4 solicita una reserva de 6 dias para dentro de 3 dias +dfx identity use 0000TestUser4 +dfx canister call backend requestReservation '(record { + housingId = 1; + checkIn = 3; + checkOut = 9; + guest = "Mario" +})' + + +# ------------ Usuario 2 pide reserva para dentro de 12 dias y se queda 3 + +dfx identity use 0000TestUser2 +dfx canister call backend requestReservation '(record { + housingId = 1; + checkIn = 12; + checkOut = 15; + guest = "Lucila" +})' + +# ------------ Usuario 6 pide reserva para dentro de 9 dias y se queda 2 + +dfx identity use 0000TestUser6 +dfx canister call backend requestReservation '(record { + housingId = 1; + checkIn = 9; + checkOut = 11; + guest = "Carlos" +})' + +# ------------ Usuario 4 confirma la reserva +dfx identity use 0000TestUser4 +dfx canister call backend confirmReservation '(record { + reservationId = 1; + txData = record { + to = ""; + amount = 4_000_000_000; + from = "" + } +})' +# ------------ Usuario 2 confirma la reserva +dfx identity use 0000TestUser2 +dfx canister call backend confirmReservation '(record { + reservationId = 2; + txData = record { + to = ""; + amount = 4_000_000_000; + from = "" + } +})' +# ------------ Usuario 6 confirma la reserva +dfx identity use 0000TestUser6 +dfx canister call backend confirmReservation '(record { + reservationId = 3; + txData = record { + to = ""; + amount = 4_000_000_000; + from = "" + } +})' \ No newline at end of file From ab8bd3d0461ef38529bce0a231909c8793881974 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 13 Jan 2025 18:19:25 -0300 Subject: [PATCH 32/57] unavailability of housings --- backend/main.mo | 144 +++++++++++++++++++----------- backend/types.mo | 2 +- comandosCLI.md | 21 +++-- scripts-sh/deploy-with-content.sh | 40 +++++---- 4 files changed, 131 insertions(+), 76 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index c3cb31e..d89164a 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -46,9 +46,9 @@ shared ({ caller }) actor class Triourism () = this { stable let DEPLOYER = caller; // /////////////// WARNING modificar estas variables en produccion a los valores reales //// - let nanoSecPerDay = 40 * 1_000_000_000; // Test Transcurso acelerado de los dias + let nanoSecPerDay = 30 * 1_000_000_000; // Test Transcurso acelerado de los dias // let nanoSecPerDay = 86400 * 1_000_000_000; // Valor real de nanosegundos en un dia - stable var TimeToPay = 20 * 1_000_000_000; // Tiempo en nanosegundos para confirmar la reserva mediante pago + stable var TimeToPay = 15 * 1_000_000_000; // Tiempo en nanosegundos para confirmar la reserva mediante pago // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -220,7 +220,7 @@ shared ({ caller }) actor class Triourism () = this { owner = caller; calendary = {dayZero = now(); reservations = []}; reservationsPending = []; - unavailability = { busy = []; notConfirmed = [] }; + unavailability = { busy = []; pending = [] }; }; let housingIdsUser = List.push(lastHousingId, hostUser.housingIds); ignore Map.put(hostUsers, phash, caller, {hostUser with housingIds = housingIdsUser}); @@ -437,7 +437,7 @@ shared ({ caller }) actor class Triourism () = this { thumbnail = housing.thumbnail; calendary = {dayZero = now(); reservations = []}; reservationsPending = []; - unavailability = { busy = []; notConfirmed = [] }; + unavailability = { busy = []; pending = [] }; }; ignore Map.put(housings, nhash, lastHousingId, newHousing ); i -= 1; @@ -509,19 +509,14 @@ shared ({ caller }) actor class Triourism () = this { }; public shared query ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ - //agregar lista de marcados como pendientes de verificacion switch (Map.get(housings, nhash, id)) { - case (?housing) { - if(housing.active) { - let calendary = updateCalendary(id); - #Ok(calendary) - } else { - return #Err(msg.InactiveHousing) - } + case (?housing) { + if (housing.owner != caller ) {return #Err(msg.CallerNotHousingOwner)}; + let { calendary } = updateCalendary(id); + #Ok(calendary) }; case null { return #Err(msg.NotHousing)}; }; - }; public func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { @@ -534,12 +529,13 @@ shared ({ caller }) actor class Triourism () = this { if(not housing.active and housing.owner != caller) { return #Err(msg.InactiveHousing) }; - let calendary = updateCalendary(housingId); + let { unavailability } = updateCalendary(housingId); if(photoIndex == 0){ let housingResponse: HousingResponse = #Start({ housing with reservationsPending; - calendary; + calendary = {dayZero = 0 ; reservations = []}; //Informacion omitida para el publico + unavailability; photos = if(housing.photos.size() > 0) { [housing.photos[0]] } else { [] }; hasNextPhoto = (housing.photos.size() > photoIndex + 1) }); @@ -657,12 +653,21 @@ shared ({ caller }) actor class Triourism () = this { getHousingsPaginateByOwner(host, page, qtyPerPage, true) }; - func updateCalendary(housingId: HousingId): Calendary { + type UpdateCalendaryResponse = { + calendary: Calendary; + unavailability: {busy: [Int]; pending: [Int]}; + }; + + func updateCalendary(housingId: HousingId): UpdateCalendaryResponse{ switch (Map.get(housings, nhash, housingId)){ - case null {assert false; {dayZero = 0; reservations = []}}; + case null { + assert false; // La siguiente linea no se ejecuta nunca pero se requiere que todas las ramificaciones tengan una ultima linea con una expresion del tipo de retorno + { calendary = {dayZero = 0; reservations = []} ; unavailability = {busy = []; pending = []}}; + }; case ( ?housing ) { let startOfCurrentDayGMT = now() - now() % nanoSecPerDay ; // Timestamp inicio del dia actual en GTM + 0 - let daysSinceLastUpdate = (startOfCurrentDayGMT - housing.calendary.dayZero) / nanoSecPerDay ; + let daysSinceLastUpdate = (startOfCurrentDayGMT - housing.calendary.dayZero) / nanoSecPerDay; + // El siguiente bloque funciona bien pero se puede acomodar mejor if(daysSinceLastUpdate > 0){ var updateArray = Array.filter( housing.calendary.reservations, @@ -676,17 +681,50 @@ shared ({ caller }) actor class Triourism () = this { checkOut = updateArray[x].checkOut -daysSinceLastUpdate }} ); + let busy = getUnaviabilityFromReservations(#ReservationType(updateArray)); + let pending = getUnaviabilityFromReservations(#IdsReservation(housing.reservationsPending)); + let unavailability = {busy; pending}; let calendary = {dayZero = startOfCurrentDayGMT; reservations = updateArray}; - let housingUpdate = {housing with calendary}; + let housingUpdate = {housing with calendary; unavailability}; ignore Map.put(housings, nhash, housingId, housingUpdate); - return calendary + return {calendary; unavailability}; } else { - return housing.calendary + let busy = getUnaviabilityFromReservations(#ReservationType(housing.calendary.reservations)); + let pending = getUnaviabilityFromReservations(#IdsReservation(housing.reservationsPending)); + let unavailability = {busy; pending}; + return {calendary = housing.calendary; unavailability } }; } } }; + func getUnaviabilityFromReservations(input: {#ReservationType: [Reservation]; #IdsReservation : [Nat]}): [Int] { + let bufferDaysUnavailable = Buffer.fromArray([]); + let reservationsArray = switch input { + case ( #ReservationType(reservationsArray)){ reservationsArray }; + case ( #IdsReservation(idsArray) ) { + let bufferReservations = Buffer.fromArray([]); + for (id in idsArray.vals()) { + switch (Map.get(reservationsPendingConfirmation, nhash, id)) { + case null { }; + case (?reservation) { bufferReservations.add(reservation)} + }; + }; + Buffer.toArray(bufferReservations); + } + }; + for (r in reservationsArray.vals()) { + var dayOccuped = r.checkIn; + while(dayOccuped < r.checkOut ) { + bufferDaysUnavailable.add(dayOccuped); + dayOccuped += 1; + } + }; + Array.sort(Buffer.toArray(bufferDaysUnavailable), Int.compare); + + + }; + func cleanPendingVerifications(housingId: Nat, ids: [Nat]): [Nat] { //Remueve las solicitudes no confirmadas y con el tiempo de confirmacion transcurrido; // TODO Esta funcion viola los principios solid ya que se encarga de limpiar las solicitudes pendientes tanto del Map general // como tambien los id de solicitudes de dentro de las estructuras de los housing y ademas devuelve un array con los ids vigentes @@ -694,7 +732,7 @@ shared ({ caller }) actor class Triourism () = this { var reservationsPendingForId = ids; for ((id, reservation) in Map.toArray(reservationsPendingConfirmation).vals()) { if (now() > reservation.date + TimeToPay) { - print("Solicitud de reserva " # Nat.toText(id) # " Eliminada"); + print("Solicitud de reserva " # Nat.toText(id) # " Eliminada por timeout"); ignore Map.remove(reservationsPendingConfirmation, nhash, id); let housing = Map.get(housings, nhash, reservation.housingId); switch housing { @@ -746,7 +784,7 @@ shared ({ caller }) actor class Triourism () = this { if(not housing.active) { return false } else { // let updatedCalendary = updateCalendary(housing.calendary); - let calendary = updateCalendary(housingId); + let { calendary } = updateCalendary(housingId); var checkDay = chechIn; let {pendings; pendingReservUpdate} = getPendingReservations(housing.reservationsPending); ignore Map.put(housings, nhash, housingId, {housing with reservationsPending = pendingReservUpdate}); @@ -795,34 +833,34 @@ shared ({ caller }) actor class Triourism () = this { } }; - public func getDisponibilityById(housingId: Nat, period: {#M30; #M60; #M90; #M120} ): async {#Ok: [Int]; #Err: Text} { //Devuelve los dias no disponibles - //TODO marcar los dias pendientes de confirmacion de reserva - let housing = Map.get(housings, nhash, housingId); - let maxPeriod: Int = switch period { - case ( #M30 ) { 30 }; - case ( #M60 ) { 60 }; - case ( #M90 ) { 90 }; - case ( #M120) { 120 }; - }; - switch housing { - case null { #Err(msg.NotHousing)}; - case (?housing) { - if(not housing.active) { return #Err(msg.InactiveHousing)}; - let bufferDaysOccuped = Buffer.fromArray([]); - let calendary = updateCalendary(housingId); - print(debug_show(calendary)); - for(reservation in calendary.reservations.vals()){ - var dayOccuped = reservation.checkIn; - while (dayOccuped <= reservation.checkOut and dayOccuped < maxPeriod) { - bufferDaysOccuped.add(dayOccuped); - dayOccuped += 1; - }; - if (dayOccuped >= maxPeriod) {return #Ok(Buffer.toArray(bufferDaysOccuped))} - }; - #Ok(Buffer.toArray(bufferDaysOccuped)) - } - } - }; + // public func getDisponibilityById(housingId: Nat, period: {#M30; #M60; #M90; #M120} ): async {#Ok: [Int]; #Err: Text} { //Devuelve los dias no disponibles + // //TODO marcar los dias pendientes de confirmacion de reserva + // let housing = Map.get(housings, nhash, housingId); + // let maxPeriod: Int = switch period { + // case ( #M30 ) { 30 }; + // case ( #M60 ) { 60 }; + // case ( #M90 ) { 90 }; + // case ( #M120) { 120 }; + // }; + // switch housing { + // case null { #Err(msg.NotHousing)}; + // case (?housing) { + // if(not housing.active) { return #Err(msg.InactiveHousing)}; + // let bufferDaysOccuped = Buffer.fromArray([]); + // let { calendary; unavailability } = updateCalendary(housingId); + // print(debug_show(calendary)); + // for(reservation in calendary.reservations.vals()){ + // var dayOccuped = reservation.checkIn; + // while (dayOccuped <= reservation.checkOut and dayOccuped < maxPeriod) { + // bufferDaysOccuped.add(dayOccuped); + // dayOccuped += 1; + // }; + // if (dayOccuped >= maxPeriod) {return #Ok(Buffer.toArray(bufferDaysOccuped))} + // }; + // #Ok(Buffer.toArray(bufferDaysOccuped)) + // } + // } + // }; public query func getAmenities({housingId: HousingId}): async ?Types.Amenities { let housing = Map.get(housings, nhash, housingId); @@ -881,7 +919,7 @@ shared ({ caller }) actor class Triourism () = this { public shared ({ caller = requester }) func requestReservation({housingId: HousingId; checkIn: Nat; checkOut: Nat; guest: Text}): async TransactionResponse { let housing = Map.get(housings, nhash, housingId); - assert(Map.has(users, phash, requester)); + if ( not Map.has(users, phash, requester)) { return #Err(msg.NotUser)}; switch housing { case null { #Err(msg.NotHousing)}; case ( ?housing ) { @@ -911,7 +949,7 @@ shared ({ caller }) actor class Triourism () = this { }; #Ok({transactionParams = dataTransaction; reservationId = lastReservationId}); } else { - #Err("") + #Err(msg.NotAvalableAllDays) }; } } diff --git a/backend/types.mo b/backend/types.mo index 76f85de..1f048dc 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -70,7 +70,7 @@ module { reviews: List.List; calendary: Calendary; reservationsPending: [Nat]; - unavailability : {busy: [Nat]; notConfirmed: [Nat]} // busy es la lista de dias ocupados, siendo 0 el dia actual y notConfirmed es la lista de dias correspondientes a todas las solicitudes pendiente + unavailability : {busy: [Int]; pending: [Int]} // busy es la lista de dias ocupados, siendo 0 el dia actual y notConfirmed es la lista de dias correspondientes a todas las solicitudes pendiente }; public type HousingTypeInit = { diff --git a/comandosCLI.md b/comandosCLI.md index b2a1e0f..a271cd5 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -92,11 +92,20 @@ Solicitud de reserva ``` dfx canister call backend requestReservation '(record { - housingId = 1 : nat; - data = record { - checkIn = 1731198892000000000 : int; - guest = "Leonardo"; - checkOut = 1733790892000000000 : int; - }; + housingId = 1; + checkIn = 9; + checkOut = 11; + guest = "Carlos" })' + + +dfx canister call backend confirmReservation '(record { + reservationId = ; + txData = record { + to = ""; + amount = 4_000_000_000; + from = "" + } +})' + ``` diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index 260dca5..030eba8 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -7,6 +7,7 @@ dfx deploy backend # ------------ Usuario Host 1 -------------- dfx identity new 0000TestUser1 dfx identity use 0000TestUser1 +echo "registro de User Host Alberto Campos ... " dfx canister call backend signUpAsHost '(record { firstName="Alberto"; lastName="Campos"; @@ -14,7 +15,7 @@ dfx canister call backend signUpAsHost '(record { phone = opt 542238780892 })' # CreateHousing 1 - +echo "Alberto registra un housing ... " dfx canister call backend createHousing '(record { namePlace="Far West"; nameHost="Alberto Campos"; @@ -25,6 +26,7 @@ dfx canister call backend createHousing '(record { thumbnail = blob "Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABAAEAAAA" })' # Update Prices housing 1 +echo "Alberto setea el precio del housing de id 1, o sea el que acaba de crear ... " dfx canister call backend updatePrices '(record { id = 1 : nat; price = record { @@ -39,10 +41,10 @@ dfx canister call backend updatePrices '(record { #Assing housing Type - +echo "Alberto crea un tipo de habitacion a partir de su housing de id 1 indicando que existen 4 ... " dfx canister call backend cloneHousingWithProperties '(record { housingId = 1 : nat; - qty = 1 : nat; + qty = 4 : nat; housingTypeInit = record { extraGuest = 2 : nat; bathroom = record { @@ -59,6 +61,7 @@ dfx canister call backend cloneHousingWithProperties '(record { })' #Set Address Housing 1 +echo "Alberto establece la dirección de su housing de id 1" dfx canister call backend setAddress '(record { housingId = 1 : nat; @@ -73,10 +76,11 @@ dfx canister call backend setAddress '(record { }; })' +echo "Alberto publica su housing de id 1 ..." dfx canister call backend publishHousing 1 # CreateHousing 2 - +echo "Alberto crear otro housing ..." dfx canister call backend createHousing '(record { namePlace=""; nameHost="Alberto Campos"; @@ -93,6 +97,7 @@ dfx canister call backend createHousing '(record { # ------------ Usuario Host 2 -------------- dfx identity new 0000TestUser2 dfx identity use 0000TestUser2 +echo "se registra una usuario llamada Lucila..." dfx canister call backend signUpAsUser '(record { firstName="Lucila"; lastName="Peralta"; @@ -100,7 +105,8 @@ dfx canister call backend signUpAsUser '(record { phone = opt 542298712438 })' -# ------------ Usuario Host 3 -------------- +# ------------ Usuario Host 3 -------------- +echo "se registra una UserHost llamada Claudia..." dfx identity new 0000TestUser3 dfx identity use 0000TestUser3 dfx canister call backend signUpAsHost '(record { @@ -113,6 +119,7 @@ dfx canister call backend signUpAsHost '(record { # ------------ Usuario Host 4 -------------- dfx identity new 0000TestUser4 dfx identity use 0000TestUser4 +echo "se registra un usuario llamado Mario..." dfx canister call backend signUpAsUser '(record { firstName="Mario"; lastName="Pappa"; @@ -123,6 +130,7 @@ dfx canister call backend signUpAsUser '(record { # ------------ Usuario Host 5 -------------- dfx identity new 0000TestUser5 dfx identity use 0000TestUser5 +echo "se registra un UserHost llamado Rodolfo..." dfx canister call backend signUpAsHost '(record { firstName="Rodolfo"; lastName="Anchorena"; @@ -144,8 +152,8 @@ dfx canister call backend signUpAsUser '(record { dfx identity use 0000TestUser4 dfx canister call backend requestReservation '(record { housingId = 1; - checkIn = 3; - checkOut = 9; + checkIn = 1; + checkOut = 12; guest = "Mario" })' @@ -193,12 +201,12 @@ dfx canister call backend confirmReservation '(record { })' # ------------ Usuario 6 confirma la reserva -dfx identity use 0000TestUser6 -dfx canister call backend confirmReservation '(record { - reservationId = 3; - txData = record { - to = ""; - amount = 4_000_000_000; - from = "" - } -})' \ No newline at end of file +# dfx identity use 0000TestUser6 +# dfx canister call backend confirmReservation '(record { +# reservationId = 3; +# txData = record { +# to = ""; +# amount = 4_000_000_000; +# from = "" +# } +# })' \ No newline at end of file From f424dc63f7182a26cae424f943760182413d512d Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 13 Jan 2025 23:18:21 -0300 Subject: [PATCH 33/57] Validate checkOut >= checkIn setChekInCheckOut function --- backend/constants.mo | 2 + backend/main.mo | 94 ++++++++++++++++--------------- scripts-sh/deploy-with-content.sh | 2 +- 3 files changed, 53 insertions(+), 45 deletions(-) diff --git a/backend/constants.mo b/backend/constants.mo index cc16eeb..7b473b1 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -16,5 +16,7 @@ module { public let IsNotpublishable = "Not publishable due to missing data"; public let HousingTypeExist = "The housing type already exists"; public let HousingTypeNoExist = "The type of housing does not exist"; + public let CallerIsNotRequester = "The caller is not the requester of the reservation id number "; public let TransactionNotVerified = "The transaction was not verified successfully"; + public let ErrorCheckinCheckout = "Check-in for one accommodation must be at least one hour later than check-out for the previous accommodation"; }; diff --git a/backend/main.mo b/backend/main.mo index d89164a..b645757 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -18,7 +18,7 @@ import Nat64 "mo:base/Nat64"; import Indexer_icp "./indexer_icp_token"; import AccountIdentifier "mo:account-identifier"; -shared ({ caller }) actor class Triourism () = this { +shared ({ caller = DEPLOYER }) actor class Triourism () = this { type User = Types.User; type HostUser =Types.HostUser; @@ -43,7 +43,7 @@ shared ({ caller }) actor class Triourism () = this { type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; hasNext: Bool}; #Err: Text}; - stable let DEPLOYER = caller; + // stable let DEPLOYER = caller; // /////////////// WARNING modificar estas variables en produccion a los valores reales //// let nanoSecPerDay = 30 * 1_000_000_000; // Test Transcurso acelerado de los dias @@ -53,7 +53,7 @@ shared ({ caller }) actor class Triourism () = this { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// stable let admins = Set.new(); - ignore Set.put(admins, phash, caller); + ignore Set.put(admins, phash, DEPLOYER); stable let users = Map.new(); stable let hostUsers = Map.new(); @@ -363,6 +363,7 @@ shared ({ caller }) actor class Triourism () = this { }; public shared ({ caller }) func setChekInCheckOut({housingId: HousingId; checkIn: Nat; checkOut: Nat}): async {#Ok; #Err: Text}{ + if(checkOut >= checkIn) { return #Err(msg.ErrorCheckinCheckout) }; let housing = Map.get(housings, nhash, housingId); switch housing { case null { #Err(msg.NotHousing)}; @@ -519,7 +520,7 @@ shared ({ caller }) actor class Triourism () = this { }; }; - public func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { + public shared ({ caller }) func getHousingById({housingId: HousingId; photoIndex: Nat}): async {#Ok: HousingResponse; #Err: Text} { let housing = Map.get(housings, nhash, housingId); return switch housing { @@ -956,6 +957,7 @@ shared ({ caller }) actor class Triourism () = this { }; func verifyTransaction({from; to; amount}: DataTransaction, registeredAmount: Nat64): async Bool { + // TODO Verificar tambien aca return true; // Test if(amount != registeredAmount) { return false }; let indexer_icp = actor("qhbym-qaaaa-aaaaa-aaafq-cai"): actor { @@ -979,55 +981,59 @@ shared ({ caller }) actor class Triourism () = this { }; }; false - }; case (#Err(_)) { false } } - }; public shared ({ caller }) func confirmReservation({reservationId: Nat; txData: DataTransaction}): async {#Ok: Reservation; #Err: Text} { - let reservation = Map.remove(reservationsPendingConfirmation, nhash, reservationId); - switch reservation { - case null { #Err(msg.NotReservation)}; - case ( ?reservation ){ - if (await verifyTransaction(txData, Nat64.fromNat(reservation.amount))){ - let housing = Map.get(housings, nhash, reservation.housingId); - switch housing { - case null { #Err(msg.NotHousing)}; - case ( ?housing ) { - let calendary = { - housing.calendary with - reservations = Prim.Array_tabulate( - housing.calendary.reservations.size() + 1, - func x = if (x == 0) { - { reservation with - confirmated = true; - dataTransaction = ?txData - } - } - else { - housing.calendary.reservations[x - 1] - } - ) - }; - print(debug_show(calendary)); - let reservationsPending = Array.filter( - housing.reservationsPending, - func x = x !=reservationId - ); - ignore Map.put(housings, nhash, reservation.housingId, { housing with calendary; reservationsPending}); - #Ok({ reservation with confirmated = true }) - } + let reservation = Map.remove(reservationsPendingConfirmation, nhash, reservationId); + switch reservation { + case null { #Err(msg.NotReservation)}; + case ( ?reservation ){ + if (await verifyTransaction(txData, Nat64.fromNat(reservation.amount))){ + if(reservation.requester != caller) { + return #Err(msg.CallerIsNotRequester # Nat.toText(reservationId)) + }; + let housing = Map.get(housings, nhash, reservation.housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + let calendary = { + housing.calendary with + reservations = Prim.Array_tabulate( + housing.calendary.reservations.size() + 1, + func x = if (x == 0) { + { + reservation with + confirmated = true; + dataTransaction = ?txData + } + } else { + housing.calendary.reservations[x - 1] + } + ) + }; + print(debug_show(calendary)); + let reservationsPending = Array.filter( + housing.reservationsPending, + func x = x !=reservationId + ); + ignore Map.put( + housings, + nhash, + reservation.housingId, + { housing with calendary; reservationsPending } + ); + #Ok({ reservation with confirmated = true }) } - } else { - #Err(msg.TransactionNotVerified) - }; - - } + } + } else { + #Err(msg.TransactionNotVerified) + }; } + } - }; // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index 030eba8..3bc51c3 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -153,7 +153,7 @@ dfx identity use 0000TestUser4 dfx canister call backend requestReservation '(record { housingId = 1; checkIn = 1; - checkOut = 12; + checkOut = 11; guest = "Mario" })' From 2b54007b0be0ca4dcbe79e629678311dd5c11f31 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 13 Jan 2025 23:29:11 -0300 Subject: [PATCH 34/57] add locateonTheMap function --- backend/main.mo | 18 +++++++++++++++++- backend/types.mo | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/main.mo b/backend/main.mo index b645757..d68e7ee 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -156,7 +156,8 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { neighborhood = ""; zipCode = 0; street = ""; externalNumber = 0; - internalNumber = 0 + internalNumber = 0; + coordinates = null; }; let defaultHousinValues = { @@ -390,6 +391,21 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } } }; + + public shared ({ caller }) func locateOnTheMap({housingId: HousingId; lat: Int; lng: Int}): async {#Ok; #Err: Text} { + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + let address = {housing.address with coordinates = ?{lat; lng}}; + ignore Map.put(housings, nhash, housingId, {housing with address}); + #Ok + } + } + }; public shared ({ caller }) func cloneHousingWithProperties({housingId: HousingId; qty: Nat; housingTypeInit: HousingTypeInit}): async {#Ok; #Err: Text} { diff --git a/backend/types.mo b/backend/types.mo index 1f048dc..2624395 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -165,6 +165,7 @@ module { street: Text; externalNumber: Nat; internalNumber: Nat; + coordinates: ?{lat: Int; lng: Int} }; public type Price = { From ab991c3311da8320c24fe7406f8b6eb4ebcd1951 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 15 Jan 2025 17:47:06 -0300 Subject: [PATCH 35/57] Add email and phone to data requestReservation --- backend/constants.mo | 3 +- backend/main.mo | 99 +++++++++++++------------------ backend/types.mo | 6 +- dfx.json | 7 +++ scripts-sh/deploy-with-content.sh | 16 +++-- 5 files changed, 65 insertions(+), 66 deletions(-) diff --git a/backend/constants.mo b/backend/constants.mo index 7b473b1..fe9ac0a 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -18,5 +18,6 @@ module { public let HousingTypeNoExist = "The type of housing does not exist"; public let CallerIsNotRequester = "The caller is not the requester of the reservation id number "; public let TransactionNotVerified = "The transaction was not verified successfully"; - public let ErrorCheckinCheckout = "Check-in for one accommodation must be at least one hour later than check-out for the previous accommodation"; + public let ErrorSetHoursCheckInCheckOut = "Check-in for one accommodation must be at least one hour later than check-out for the previous accommodation"; + public let ErrorCheckInCheckOutDays = "The CheckOut day must be at least one day after the CheckIn day." }; diff --git a/backend/main.mo b/backend/main.mo index d68e7ee..fb96e8f 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -15,6 +15,7 @@ import List "mo:base/List"; import Nat "mo:base/Nat"; import Nat8 "mo:base/Nat8"; import Nat64 "mo:base/Nat64"; +import Text "mo:base/Text"; import Indexer_icp "./indexer_icp_token"; import AccountIdentifier "mo:account-identifier"; @@ -150,14 +151,14 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { a.internalNumber == b.internalNumber }; - let NULL_LOCATION = { + let NULL_LOCATION: Types.Location = { country = ""; city = ""; neighborhood = ""; zipCode = 0; street = ""; externalNumber = 0; internalNumber = 0; - coordinates = null; + geolocation = null; }; let defaultHousinValues = { @@ -325,26 +326,26 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; - // public shared ({ caller }) func setMinReservationLeadTime({id: HousingId; hours: Nat}):async {#Ok; #Err: Text} { - // let housing = Map.get(housings, nhash, id); - // switch housing { - // case null { - // return #Err(msg.NotHosting); - // }; - // case (?housing) { - // if(housing.owner != caller){ - // return #Err(msg.CallerNotHousingOwner); - // }; - // ignore Map.put( - // housings, - // nhash, - // id, - // {housing with minReservationLeadTimeNanoSeg = hours * NANO_SEG_PER_HOUR}); - // #Ok; - // } + public shared ({ caller }) func setMinReservationLeadTime({id: HousingId; hours: Nat}):async {#Ok; #Err: Text} { + let housing = Map.get(housings, nhash, id); + switch housing { + case null { + return #Err(msg.NotHousing); + }; + case (?housing) { + if(housing.owner != caller){ + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put( + housings, + nhash, + id, + {housing with minReservationLeadTimeNanoSeg = hours * 60 * 60 * 1_000_000_000}); + #Ok; + } - // } - // }; + } + }; public shared ({ caller }) func setHousingStatus({id: HousingId; active: Bool}): async {#Ok; #Err: Text}{ let housing = Map.get(housings, nhash, id); @@ -364,7 +365,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; public shared ({ caller }) func setChekInCheckOut({housingId: HousingId; checkIn: Nat; checkOut: Nat}): async {#Ok; #Err: Text}{ - if(checkOut >= checkIn) { return #Err(msg.ErrorCheckinCheckout) }; + if(checkOut >= checkIn) { return #Err(msg.ErrorSetHoursCheckInCheckOut) }; let housing = Map.get(housings, nhash, housingId); switch housing { case null { #Err(msg.NotHousing)}; @@ -392,7 +393,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; - public shared ({ caller }) func locateOnTheMap({housingId: HousingId; lat: Int; lng: Int}): async {#Ok; #Err: Text} { + public shared ({ caller }) func locateOnTheMap({housingId: HousingId; lat: Int; lng: Int}): async {#Ok; #Err: Text} { // lat y lng van multiplicados por 10e7 let housing = Map.get(housings, nhash, housingId); switch housing { case null { #Err(msg.NotHousing)}; @@ -400,15 +401,14 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; - let address = {housing.address with coordinates = ?{lat; lng}}; + let address = {housing.address with geolocation = ?{lat; lng}}; ignore Map.put(housings, nhash, housingId, {housing with address}); #Ok } } }; - public shared ({ caller }) func cloneHousingWithProperties({housingId: HousingId; qty: Nat; housingTypeInit: HousingTypeInit}): async {#Ok; #Err: Text} { - + public shared ({ caller }) func cloneHousingWithProperties({housingId: HousingId; qty: Nat; housingTypeInit: HousingTypeInit}): async {#Ok; #Err: Text} { let hostUser = Map.get(hostUsers, phash, caller); switch hostUser { case null { @@ -850,35 +850,6 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; - // public func getDisponibilityById(housingId: Nat, period: {#M30; #M60; #M90; #M120} ): async {#Ok: [Int]; #Err: Text} { //Devuelve los dias no disponibles - // //TODO marcar los dias pendientes de confirmacion de reserva - // let housing = Map.get(housings, nhash, housingId); - // let maxPeriod: Int = switch period { - // case ( #M30 ) { 30 }; - // case ( #M60 ) { 60 }; - // case ( #M90 ) { 90 }; - // case ( #M120) { 120 }; - // }; - // switch housing { - // case null { #Err(msg.NotHousing)}; - // case (?housing) { - // if(not housing.active) { return #Err(msg.InactiveHousing)}; - // let bufferDaysOccuped = Buffer.fromArray([]); - // let { calendary; unavailability } = updateCalendary(housingId); - // print(debug_show(calendary)); - // for(reservation in calendary.reservations.vals()){ - // var dayOccuped = reservation.checkIn; - // while (dayOccuped <= reservation.checkOut and dayOccuped < maxPeriod) { - // bufferDaysOccuped.add(dayOccuped); - // dayOccuped += 1; - // }; - // if (dayOccuped >= maxPeriod) {return #Ok(Buffer.toArray(bufferDaysOccuped))} - // }; - // #Ok(Buffer.toArray(bufferDaysOccuped)) - // } - // } - // }; - public query func getAmenities({housingId: HousingId}): async ?Types.Amenities { let housing = Map.get(housings, nhash, housingId); switch housing { @@ -901,11 +872,12 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { result }; - func calculatePrice(price: ?Types.Price, days: Nat): Nat { + func calculatePrice(price: ?Types.Price, daysInt: Int): Nat { switch price { case null {assert (false); 0 }; case (?price) { var currentDiscount = 0; + let days = Int.abs(daysInt); let discounts = Array.sort<{minimumDays: Nat; discount: Nat}>( price.discountTable, func (a, b) = if (a.minimumDays < b.minimumDays) { #less } else { #greater } @@ -934,15 +906,22 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; - public shared ({ caller = requester }) func requestReservation({housingId: HousingId; checkIn: Nat; checkOut: Nat; guest: Text}): async TransactionResponse { + public shared ({ caller = requester }) func requestReservation(data: Types.ReservationDataInput): async TransactionResponse { + let {housingId; guest; email; phone; checkIn; checkOut}: Types.ReservationDataInput = data; let housing = Map.get(housings, nhash, housingId); - if ( not Map.has(users, phash, requester)) { return #Err(msg.NotUser)}; + if ( not Map.has(users, phash, requester) ) { + return #Err(msg.NotUser) + }; + if ( checkIn >= checkOut or checkIn < 0) { + return #Err(msg.ErrorSetHoursCheckInCheckOut) + }; + switch housing { case null { #Err(msg.NotHousing)}; case ( ?housing ) { if(checkDisponibility(housingId, checkIn, checkOut)){ lastReservationId += 1; - let amount = calculatePrice(housing.price, checkOut - checkIn); + let amount = calculatePrice(housing.price, Prim.abs(checkOut - checkIn)); let reservation: Reservation = { date = now(); housingId; @@ -951,6 +930,8 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { checkIn; checkOut; guest; + email; + phone; confirmated = false; amount; dataTransaction = null; diff --git a/backend/types.mo b/backend/types.mo index 2624395..8dc1729 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -165,7 +165,7 @@ module { street: Text; externalNumber: Nat; internalNumber: Nat; - coordinates: ?{lat: Int; lng: Int} + geolocation: ?{lat: Int; lng: Int} }; public type Price = { @@ -185,10 +185,14 @@ module { checkIn: Int; // Número de día de ingreso. Siendo 0 el día actual checkOut: Int; // El egreso tiene que ser mayor que 1 + el ingreso guest: Text; // Nombre del huésped + email: Text; + phone: Nat; }; public type Reservation = ReservationDataInput and { date: Int; + // checkIn: Int; // Int es un supertipo de Nat por lo tanto no hay conflicto con el checkIn que se "hereda" de ReservationDataInput y queda como Int + // checkOut: Int; // Idem reservationId: Nat; requester: Principal; confirmated: Bool; diff --git a/dfx.json b/dfx.json index 95b44aa..95b4399 100644 --- a/dfx.json +++ b/dfx.json @@ -10,6 +10,13 @@ } }, + + "icrc1_ledger_canister": { + "type": "custom", + "candid": "https://raw.githubusercontent.com/dfinity/ic/aa705aaa621c2e0d4f146f3a1de801edcb0fa0d5/rs/ledger_suite/icrc1/ledger/ledger.did", + "wasm": "https://download.dfinity.systems/ic/aa705aaa621c2e0d4f146f3a1de801edcb0fa0d5/canisters/ic-icrc1-ledger.wasm.gz" + }, + "test": { "type": "motoko", "main": "backend/test/main.mo", diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index 3bc51c3..7a0f044 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -44,7 +44,7 @@ dfx canister call backend updatePrices '(record { echo "Alberto crea un tipo de habitacion a partir de su housing de id 1 indicando que existen 4 ... " dfx canister call backend cloneHousingWithProperties '(record { housingId = 1 : nat; - qty = 4 : nat; + qty = 1 : nat; housingTypeInit = record { extraGuest = 2 : nat; bathroom = record { @@ -56,7 +56,7 @@ dfx canister call backend cloneHousingWithProperties '(record { }; beds = vec { variant { Matrimonial = 1 : nat } }; maxGuest = 4 : nat; - nameType = "Standard"; + nameType = "Medium"; }; })' @@ -154,7 +154,9 @@ dfx canister call backend requestReservation '(record { housingId = 1; checkIn = 1; checkOut = 11; - guest = "Mario" + guest = "Mario"; + email = "mario@gmil.com"; + phone = 542236676567 })' @@ -165,7 +167,9 @@ dfx canister call backend requestReservation '(record { housingId = 1; checkIn = 12; checkOut = 15; - guest = "Lucila" + guest = "Lucila"; + email = "cucila@gmil.com"; + phone = 556578787998 })' # ------------ Usuario 6 pide reserva para dentro de 9 dias y se queda 2 @@ -175,7 +179,9 @@ dfx canister call backend requestReservation '(record { housingId = 1; checkIn = 9; checkOut = 11; - guest = "Carlos" + guest = "Carlos"; + email = "carlos@gmil.com"; + phone = 536657090 })' # ------------ Usuario 4 confirma la reserva From 9905d92ec036a8da06a2f1d1258b588faee835a7 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 15 Jan 2025 22:33:00 -0300 Subject: [PATCH 36/57] EncodeAmenities function --- backend/main.mo | 56 ++++++++++++++++++++++++++++--- backend/types.mo | 30 ++++++++++++++++- scripts-sh/deploy-with-content.sh | 36 +++++++++++++++++--- 3 files changed, 112 insertions(+), 10 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index fb96e8f..a1b7cb5 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -171,6 +171,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { properties: ?HousingType = null; housingType: ?Text = null; amenities = null; + encodedAmenities = 0; reviews = List.nil(); }; @@ -493,11 +494,12 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { if(caller != housing.owner) { return #Err(msg.CallerNotHousingOwner); }; + let encodedAmenities = encodeAmenities(amenities); ignore Map.put( housings, nhash, housingId, - {housing with amenities = ?amenities}); + {housing with amenities = ?amenities; encodedAmenities}); #Ok } } @@ -614,6 +616,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { address = housing.address; thumbnail = housing.thumbnail; price = housing.price; + encodedAmenities = housing.encodedAmenities } ) } @@ -662,10 +665,53 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; public shared query ({ caller }) func getMyActiveHousings({page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate{ - getHousingsPaginateByOwner(caller, page, qtyPerPage, true) - + getHousingsPaginateByOwner(caller, page, qtyPerPage, true) + }; + + func encodeAmenities(a: Types.Amenities): Nat { + var result = 1: Nat; //el bit mas significativo se ignora en la decodificación + // El orden de los elementos de este array tiene que coincidir con el array para la decodificacion + let arrayBools = [ + a.freeWifi, + a.airCond, + a.flatTV, + a.minibar, + a.safeBox, + a.roomService, + a.premiumLinen, + a.ironBoard, + a.privateBath, + a.hairDryer, + a.hotelRest, + a.barLounge, + a.buffetBrkfst, + a.lobbyCoffee, + a.catering, + a.specialMenu, + a.outdoorPool, + a.spaWellness, + a.gym, + a.jacuzzi, + a.gameRoom, + a.tennisCourt, + a.natureTrails, + ]; + for(i in arrayBools.vals()) { result := result * 2 + (if i { 1 } else { 0 }) }; + result + //Ejemplo aproximado para llenar array de Booleanos a partir de result y el array de amenidades equivalente: + // let amenities = ["freeWifi", "airCond" ... etcetera] + // let amenitiesTrue = []; + // for (let i = 0; i < amenities.length; i++) { + // if(result % 2) { amenitiesTrue.push = amenities[i] }; + // result Math.floor(result / 2); + // } + }; + // public func filterHousing(filters: Filter) { + + // }; + public shared query func getHousingByHostUser({host: Principal; page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate{ getHousingsPaginateByOwner(host, page, qtyPerPage, true) }; @@ -795,7 +841,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { {pendings = Buffer.toArray(bufferReservations); pendingReservUpdate = Buffer.toArray(bufferReservUpdate)} }; - func checkDisponibility(housingId: HousingId, chechIn: Int, checkOut: Int): Bool { + func checkDisponibility(housingId: HousingId, chechIn: Nat, checkOut: Nat): Bool { switch (Map.get(housings, nhash, housingId)) { case (?housing) { if(not housing.active) { return false } @@ -919,7 +965,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { switch housing { case null { #Err(msg.NotHousing)}; case ( ?housing ) { - if(checkDisponibility(housingId, checkIn, checkOut)){ + if(checkDisponibility(housingId, Prim.abs(checkIn), Prim.abs(checkOut))){ lastReservationId += 1; let amount = calculatePrice(housing.price, Prim.abs(checkOut - checkIn)); let reservation: Reservation = { diff --git a/backend/types.mo b/backend/types.mo index 8dc1729..cd94862 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -67,6 +67,7 @@ module { properties: ?HousingType; housingType: ?Text; amenities: ?Amenities; + encodedAmenities: Nat; reviews: List.List; calendary: Calendary; reservationsPending: [Nat]; @@ -76,7 +77,7 @@ module { public type HousingTypeInit = { nameType: Text; beds: [BedKind]; - bathroom: Bathroom; + bathroom: [Bathroom]; maxGuest: Nat; extraGuest: Nat; }; @@ -93,6 +94,7 @@ module { address: Location; thumbnail: Blob; price: ?Price; + encodedAmenities: Nat; }; public type HousingResponse = { @@ -157,6 +159,32 @@ module { custom: [{amenitieName: Text; value: Bool}]; }; + public type Amenities2 = { + #freeWifi; + #airCond; // Aire acondicionado + #flatTV; // TV de pantalla plana + #minibar; + #safeBox; // Caja de seguridad + #roomService; // Servicio a la habitación + #premiumLinen; // Ropa de cama premium + #ironBoard; // Plancha y tabla de planchar + #privateBath; // Baño privado con artículos de tocador + #hairDryerl; // Secador de pelo + #hotelRest; // Restaurante en el hotel + #barLounge; // Bar y lounge + #buffetBrkfst; // Desayuno buffet + #lobbyCoffee; // Servicio de café/té en el lobby + #catering; // Servicio de catering para eventos + #specialMenu; // Menú para dietas especiales (bajo solicitud) + #outdoorPool; // Piscina al aire libre + #spaWellness; // Spa y centro de bienestar + #gym; + #jacuzzi; + #gameRoom; // Salón de juegos + #tennisCourt; // Pista de tenis + #natureTrails; // Acceso a senderos naturales + }; + public type Location = { country: Text; city: Text; diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index 7a0f044..df92148 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -38,6 +38,34 @@ dfx canister call backend updatePrices '(record { } })' +dfx canister call backend setAmenities '( record { + freeWifi = false; + airCond = false; + flatTV = false; + minibar = false; + safeBox = false; + roomService = false; + premiumLinen = false; + ironBoard = false; + privateBath = false; + hairDryer = false; + hotelRest = false; + barLounge = false; + buffetBrkfst = false; + lobbyCoffee = false; + catering = false; + specialMenu = false; + outdoorPool = false; + spaWellness = false; + gym = false; + jacuzzi = false; + gameRoom = false; + tennisCourt = false; + natureTrails = true; + custom = vec {}; +}, 1)' + + #Assing housing Type @@ -47,16 +75,16 @@ dfx canister call backend cloneHousingWithProperties '(record { qty = 1 : nat; housingTypeInit = record { extraGuest = 2 : nat; - bathroom = record { + bathroom = vec {record { shower = true; sink = true; toilette = true; isShared = false; bathtub = true; - }; + }}; beds = vec { variant { Matrimonial = 1 : nat } }; maxGuest = 4 : nat; - nameType = "Medium"; + nameType = "Standard"; }; })' @@ -152,7 +180,7 @@ dfx canister call backend signUpAsUser '(record { dfx identity use 0000TestUser4 dfx canister call backend requestReservation '(record { housingId = 1; - checkIn = 1; + checkIn = 7; checkOut = 11; guest = "Mario"; email = "mario@gmil.com"; From 526d3f0816a2b86af7df8613cff2e97ebfb6414a Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 16 Jan 2025 00:15:54 -0300 Subject: [PATCH 37/57] fix encodedAmenities example --- backend/main.mo | 24 +++++++++++++++--------- scripts-sh/deploy-with-content.sh | 8 ++++---- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index a1b7cb5..1108341 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -669,7 +669,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; func encodeAmenities(a: Types.Amenities): Nat { - var result = 1: Nat; //el bit mas significativo se ignora en la decodificación + var result = 0: Nat; //el bit mas significativo se ignora en la decodificación // El orden de los elementos de este array tiene que coincidir con el array para la decodificacion let arrayBools = [ a.freeWifi, @@ -696,15 +696,21 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { a.tennisCourt, a.natureTrails, ]; - for(i in arrayBools.vals()) { result := result * 2 + (if i { 1 } else { 0 }) }; + for(i in arrayBools.vals()) { result := result * 2 + (if i { 1 } else { 0 })}; result - //Ejemplo aproximado para llenar array de Booleanos a partir de result y el array de amenidades equivalente: - // let amenities = ["freeWifi", "airCond" ... etcetera] - // let amenitiesTrue = []; - // for (let i = 0; i < amenities.length; i++) { - // if(result % 2) { amenitiesTrue.push = amenities[i] }; - // result Math.floor(result / 2); - // } + // Ejemplo aproximado para llenar array de Booleanos a partir de result y el array de amenidades equivalente en el front: + // let amenities = ["freeWifi", "airCond" ... etcetera] + // let amenitiesTrue = []; + // for (let i = 0; i < amenities.length; i++) { + // if ((encodedAmenities & (1 << amenities.length - 1 - i) != 0)){ + // amenitiesTrue.push(amenities[i]); + // } + // } + // //Ejemplo para filtar por jacuzzi + // let jacuzzi = (encodedAmenities >> (amenities.length - 19 -1)) % 2 === 1; + // //Ejemplo para filtrar por jasuzzi y minibar: + // let jacuzziYMiniBar = (encodedAmenities & (1 << amenities.length - 1 - 20) != 0) and + // (encodedAmenities & (1 << amenities.length - 1 - 3) != 0); }; diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index df92148..2d0c07b 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -42,13 +42,13 @@ dfx canister call backend setAmenities '( record { freeWifi = false; airCond = false; flatTV = false; - minibar = false; + minibar = true; safeBox = false; roomService = false; premiumLinen = false; ironBoard = false; privateBath = false; - hairDryer = false; + hairDryer = true; hotelRest = false; barLounge = false; buffetBrkfst = false; @@ -58,10 +58,10 @@ dfx canister call backend setAmenities '( record { outdoorPool = false; spaWellness = false; gym = false; - jacuzzi = false; + jacuzzi = true; gameRoom = false; tennisCourt = false; - natureTrails = true; + natureTrails = false; custom = vec {}; }, 1)' From 5935c9659c838cc7cbd3985463ea5731b0e46d71 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 16 Jan 2025 23:15:10 -0300 Subject: [PATCH 38/57] bitwise filterHousings function --- backend/main.mo | 66 ++++++++++++++++++++++++++++--- backend/types.mo | 34 ++++------------ comandosCLI.md | 30 ++++++++++++++ scripts-sh/deploy-with-content.sh | 2 +- 4 files changed, 98 insertions(+), 34 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 1108341..5139f67 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -16,6 +16,7 @@ import Nat "mo:base/Nat"; import Nat8 "mo:base/Nat8"; import Nat64 "mo:base/Nat64"; import Text "mo:base/Text"; +import Iter "mo:base/Iter"; import Indexer_icp "./indexer_icp_token"; import AccountIdentifier "mo:account-identifier"; @@ -68,6 +69,19 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { stable var lastHousingId = 0; stable var lastReviewId = 0; stable var lastReservationId = 0; + + // Prueba Amenidades dinamicas + stable var amenities: [Text] = Types.amenitiesArray; + + public shared ({ caller }) func addAmenities(a: Text): async (){ + assert(isAdmin(caller)); + // TODO Normalizar cadenas de texto y evitar duplicados + let setAmenities = Set.fromIter(amenities.vals(), thash); + ignore Set.put(setAmenities, thash, a); + amenities := Set.toArray(setAmenities); + // TODO Modificar encodedAmenities en cada housing + }; + ///////////////////////////////////// Login Update functions //////////////////////////////////////// @@ -171,7 +185,8 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { properties: ?HousingType = null; housingType: ?Text = null; amenities = null; - encodedAmenities = 0; + encodedAmenities: Nat64 = 0; + encodedAmenities2: Nat64 = 0; reviews = List.nil(); }; @@ -504,6 +519,26 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } } }; + + ///// Prueba + + public shared ({ caller }) func setAmenities2(housingId: Nat, encodedAmenities2: Nat64): async {#Ok; #Err: Text} { + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { #Err(msg.NotHousing)}; + case ( ?housing ) { + if(caller != housing.owner) { + return #Err(msg.CallerNotHousingOwner); + }; + ignore Map.put( + housings, + nhash, + housingId, + {housing with encodedAmenities2}); + #Ok + } + } + }; ///////////////////////////////////////// Getters //////////////////////////////////////// @@ -527,6 +562,25 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; + public query func filterHousings({filterCode: Nat64; page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { + let filteredHosuings = Array.filter( + Iter.toArray(Map.vals(housings)), + func h = h.active and ((h.encodedAmenities & filterCode) == filterCode) + ); + if(filteredHosuings.size() < page * qtyPerPage){ + return #Err(msg.PaginationOutOfRange) + }; + let (size: Nat, hasNext: Bool) = if (filteredHosuings.size() >= (page + 1) * qtyPerPage){ + (qtyPerPage, filteredHosuings.size() > (page + 1)) + } else { + (filteredHosuings.size() % qtyPerPage, false) + }; + let array = Array.subArray(filteredHosuings, page * qtyPerPage, size); + #Ok{ array; hasNext } + }; + + + public shared query ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ switch (Map.get(housings, nhash, id)) { case (?housing) { @@ -668,8 +722,8 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { getHousingsPaginateByOwner(caller, page, qtyPerPage, true) }; - func encodeAmenities(a: Types.Amenities): Nat { - var result = 0: Nat; //el bit mas significativo se ignora en la decodificación + func encodeAmenities(a: Types.Amenities): Nat64 { + var result = 0: Nat64; //el bit mas significativo se ignora en la decodificación // El orden de los elementos de este array tiene que coincidir con el array para la decodificacion let arrayBools = [ a.freeWifi, @@ -747,7 +801,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { func x {{ updateArray[x] with checkIn = updateArray[x].checkIn - daysSinceLastUpdate; - checkOut = updateArray[x].checkOut -daysSinceLastUpdate + checkOut = updateArray[x].checkOut - daysSinceLastUpdate }} ); let busy = getUnaviabilityFromReservations(#ReservationType(updateArray)); @@ -881,7 +935,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; }; ////////// view reservations pendding ///////////////// - public query func endingReserv(): async () { + public query func pendingReserv(): async () { print(debug_show(Map.toArray(reservationsPendingConfirmation))) }; @@ -982,7 +1036,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { checkIn; checkOut; guest; - email; + email; phone; confirmated = false; amount; diff --git a/backend/types.mo b/backend/types.mo index cd94862..d2db67e 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -67,7 +67,8 @@ module { properties: ?HousingType; housingType: ?Text; amenities: ?Amenities; - encodedAmenities: Nat; + encodedAmenities: Nat64; + encodedAmenities2: Nat64; // Prueba reviews: List.List; calendary: Calendary; reservationsPending: [Nat]; @@ -94,7 +95,7 @@ module { address: Location; thumbnail: Blob; price: ?Price; - encodedAmenities: Nat; + encodedAmenities: Nat64; }; public type HousingResponse = { @@ -133,6 +134,7 @@ module { }; public type Amenities = { + // freeWifi: Bool; airCond: Bool; // Aire acondicionado flatTV: Bool; // TV de pantalla plana @@ -159,31 +161,9 @@ module { custom: [{amenitieName: Text; value: Bool}]; }; - public type Amenities2 = { - #freeWifi; - #airCond; // Aire acondicionado - #flatTV; // TV de pantalla plana - #minibar; - #safeBox; // Caja de seguridad - #roomService; // Servicio a la habitación - #premiumLinen; // Ropa de cama premium - #ironBoard; // Plancha y tabla de planchar - #privateBath; // Baño privado con artículos de tocador - #hairDryerl; // Secador de pelo - #hotelRest; // Restaurante en el hotel - #barLounge; // Bar y lounge - #buffetBrkfst; // Desayuno buffet - #lobbyCoffee; // Servicio de café/té en el lobby - #catering; // Servicio de catering para eventos - #specialMenu; // Menú para dietas especiales (bajo solicitud) - #outdoorPool; // Piscina al aire libre - #spaWellness; // Spa y centro de bienestar - #gym; - #jacuzzi; - #gameRoom; // Salón de juegos - #tennisCourt; // Pista de tenis - #natureTrails; // Acceso a senderos naturales - }; + public type Amenities2 = [Text]; // Eventualmente se pueden establecer las amenidades dinamicamente + public let amenitiesArray = ["freeWifi", "airCond", "flatTV", "minibar", "safeBox", "roomService", "premiumLinen", "ironBoard", "privateBath", "hairDryer", "hotelRest", "barLounge", + "buffetBrkfst", "lobbyCoffee", "catering", "specialMenu", "outdoorPool", "spaWellness", "gym", "jacuzzi", "gameRoom", "tennisCourt", "natureTrails"]; public type Location = { country: Text; diff --git a/comandosCLI.md b/comandosCLI.md index a271cd5..06ef1e0 100644 --- a/comandosCLI.md +++ b/comandosCLI.md @@ -87,7 +87,37 @@ dfx canister call backend updatePrices '(record { } })' ``` +Set amenities +``` +dfx canister call backend setAmenities '( record { + freeWifi = false; + airCond = false; + flatTV = false; + minibar = true; + safeBox = false; + roomService = false; + premiumLinen = false; + ironBoard = false; + privateBath = true; + hairDryer = false; + hotelRest = false; + barLounge = false; + buffetBrkfst = false; + lobbyCoffee = false; + catering = true; + specialMenu = false; + outdoorPool = false; + spaWellness = false; + gym = false; + jacuzzi = false; + gameRoom = true; + tennisCourt = false; + natureTrails = false; + custom = vec {}; +}, 1)' + +``` Solicitud de reserva ``` diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index 2d0c07b..29df881 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -181,7 +181,7 @@ dfx identity use 0000TestUser4 dfx canister call backend requestReservation '(record { housingId = 1; checkIn = 7; - checkOut = 11; + checkOut = 9; guest = "Mario"; email = "mario@gmil.com"; phone = 542236676567 From 2c616fd9e31e82be0fa58d701170d34e212c17af Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Fri, 17 Jan 2025 19:52:00 -0300 Subject: [PATCH 39/57] Referral code --- backend/main.mo | 30 +++++++++++++++++++++++++++--- backend/types.mo | 1 + mops.toml | 1 - scripts-sh/deployToken.sh | 0 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 scripts-sh/deployToken.sh diff --git a/backend/main.mo b/backend/main.mo index 5139f67..c205d92 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -70,6 +70,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { stable var lastReviewId = 0; stable var lastReservationId = 0; + // Prueba Amenidades dinamicas stable var amenities: [Text] = Types.amenitiesArray; @@ -82,12 +83,13 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { // TODO Modificar encodedAmenities en cada housing }; - ///////////////////////////////////// Login Update functions //////////////////////////////////////// public shared ({ caller }) func signUpAsUser(data: Types.SignUpData) : async SignUpResult { if(Principal.isAnonymous(caller)) { return #Err(msg.NotUser) }; - + if (Map.has(users,phash, caller )){ + return #Err("The caller is linked to an existing User Host") + }; let user = Map.get(users, phash, caller); switch user { case (?User) { #Err("User already exists") }; @@ -106,6 +108,9 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { public shared ({ caller }) func signUpAsHost(data: Types.SignUpData) : async SignUpResult { if(Principal.isAnonymous(caller)) { return #Err(msg.NotUser) }; + if (Map.has(users,phash, caller )){ + return #Err("The caller is linked to an existing User") + }; let hostUser = Map.get(hostUsers, phash, caller); switch hostUser { case (?User) { #Err("Host User already exists") }; @@ -139,6 +144,9 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; }; + public shared query ({ caller }) func getMyReferralCode(): async Nat32 { + Principal.hash(caller) + }; //////////////////////////////// CRUD Data User /////////////////////////////////// @@ -562,7 +570,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; - public query func filterHousings({filterCode: Nat64; page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { + public query func filterByAmenities({filterCode: Nat64; page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { let filteredHosuings = Array.filter( Iter.toArray(Map.vals(housings)), func h = h.active and ((h.encodedAmenities & filterCode) == filterCode) @@ -579,6 +587,22 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { #Ok{ array; hasNext } }; + // public query func filterByProperties({filterCode: Nat64; page: Nat; qtyPerPage: Nat}): async ResultHousingPaginate { + // let filteredHosuings = Array.filter( + // Iter.toArray(Map.vals(housings)), + // func h = h.active and ((h.encodedAmenities & filterCode) == filterCode) + // ); + // if(filteredHosuings.size() < page * qtyPerPage){ + // return #Err(msg.PaginationOutOfRange) + // }; + // let (size: Nat, hasNext: Bool) = if (filteredHosuings.size() >= (page + 1) * qtyPerPage){ + // (qtyPerPage, filteredHosuings.size() > (page + 1)) + // } else { + // (filteredHosuings.size() % qtyPerPage, false) + // }; + // let array = Array.subArray(filteredHosuings, page * qtyPerPage, size); + // #Ok{ array; hasNext } + // }; public shared query ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ diff --git a/backend/types.mo b/backend/types.mo index d2db67e..58c90f5 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -11,6 +11,7 @@ module { lastName: Text; phone: ?Nat; email: Text; + referralBy: ?Nat; }; public type UserData = { diff --git a/mops.toml b/mops.toml index e83dd8d..b6a92c9 100644 --- a/mops.toml +++ b/mops.toml @@ -1,5 +1,4 @@ [dependencies] base = "0.11.1" map = "9.0.1" -random = "1.0.1" account-identifier = "1.0.2" \ No newline at end of file diff --git a/scripts-sh/deployToken.sh b/scripts-sh/deployToken.sh new file mode 100644 index 0000000..e69de29 From 862e70316dbf7bb8531472edb18ac31702fa5299 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 22 Jan 2025 00:30:24 -0300 Subject: [PATCH 40/57] referral | deploy-token.sh --- backend/main.mo | 95 +++++++++++++++---- backend/types.mo | 28 +++++- canister_ids.json | 5 + dfx.json | 10 +- package.json | 1 + scripts-sh/deploy-token.sh | 147 ++++++++++++++++++++++++++++++ scripts-sh/deploy-with-content.sh | 12 ++- scripts-sh/deployToken.sh | 0 scripts-sh/verLogo.html | 60 ++++++++++++ 9 files changed, 332 insertions(+), 26 deletions(-) create mode 100644 canister_ids.json create mode 100755 scripts-sh/deploy-token.sh delete mode 100644 scripts-sh/deployToken.sh create mode 100644 scripts-sh/verLogo.html diff --git a/backend/main.mo b/backend/main.mo index c205d92..7ed2514 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -1,7 +1,7 @@ import Prim "mo:⛔"; import Map "mo:map/Map"; import Set "mo:map/Set"; -import { phash; nhash; thash } "mo:map/Map"; +import { phash; nhash; n32hash; thash } "mo:map/Map"; import Principal "mo:base/Principal"; import Buffer "mo:base/Buffer"; import Blob "mo:base/Blob"; @@ -70,6 +70,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { stable var lastReviewId = 0; stable var lastReservationId = 0; + stable let referralCodes = Map.new(); // Prueba Amenidades dinamicas stable var amenities: [Text] = Types.amenitiesArray; @@ -94,14 +95,20 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { switch user { case (?User) { #Err("User already exists") }; case null { - let newUser: User = { - data with - verified = true; - reviewsIssued = List.nil(); - score = 0; - }; - ignore Map.put(users, phash, caller, newUser); - #Ok( newUser ); + + let referralProcess = putRefered(data.referralBy, caller, #User(#Level1)); + if (referralProcess){ + let newUser: User = { + data with + verified = true; + reviewsIssued = List.nil(); + score = 0; + }; + ignore Map.put(users, phash, caller, newUser); + #Ok( newUser ); + } else { + #Err("Invalid referral code") + } }; }; }; @@ -115,15 +122,20 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { switch hostUser { case (?User) { #Err("Host User already exists") }; case null { - let newHostUser: HostUser = { - data with - verified = true; - score = 0; - housingIds = List.nil(); - housingTypes = Map.new(); - }; - ignore Map.put(hostUsers, phash, caller, newHostUser); - #Ok(newHostUser); + let referralProcess = putRefered(data.referralBy, caller, #User(#Level1)); + if (referralProcess){ + let newHostUser: HostUser = { + data with + verified = true; + score = 0; + housingIds = List.nil(); + housingTypes = Map.new(); + }; + ignore Map.put(hostUsers, phash, caller, newHostUser); + #Ok(newHostUser); + } else { + #Err("Invalid referral code") + } }; }; }; @@ -144,10 +156,53 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; }; - public shared query ({ caller }) func getMyReferralCode(): async Nat32 { - Principal.hash(caller) + public shared ({ caller }) func getMyReferralCode(): async Nat32 { + let code = (Principal.hash(caller)); + let referralBook = Map.get(referralCodes, n32hash, code); + switch referralBook { + case null { + let book: Types.ReferralBook = { + owner = caller; + refereds: [Types.Refered] = []; + }; + ignore Map.put(referralCodes, n32hash, code, book); + }; + case _ {} + }; + code + }; + + public shared ({ caller }) func getMyReferralBook(): async ?Types.ReferralBook{ + Map.get(referralCodes, n32hash, Principal.hash(caller)); }; + func putRefered(code: ?Nat32, user: Principal, kind: Types.ReferalKind): Bool { + switch (code) { + case null { true }; + case ( ?code ) { + let referralBook = Map.get(referralCodes, n32hash, code); + switch referralBook { + case null { false }; + case (?referralBook) { + let refered = { date = now(); user; kind }; + let refereds = Prim.Array_tabulate( + referralBook.refereds.size() +1, + func x = if( x == 0 ) { refered } else { referralBook.refereds[x -1]} + ); + ignore Map.put( + referralCodes, + n32hash, + code, + {referralBook with refereds } + ); + true + } + }; + } + } + }; + + //////////////////////////////// CRUD Data User /////////////////////////////////// public shared ({ caller }) func editProfile(data: Types.SignUpData): async {#Ok; #Err}{ diff --git a/backend/types.mo b/backend/types.mo index 58c90f5..53a5588 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -11,7 +11,7 @@ module { lastName: Text; phone: ?Nat; email: Text; - referralBy: ?Nat; + referralBy: ?Nat32; }; public type UserData = { @@ -182,6 +182,32 @@ module { discountTable: [{minimumDays: Nat; discount: Nat}]; // Ej. [{minimumDays = 5; discount = 10}, {minimumDays = 15; discount = 15}] }; + ////////////////////////////// Referrals /////////////////////////////////// + + public type Refered = { + date: Int; + user: Principal; + kind: ReferalKind + }; + + public type ReferalKind = { + #User: StatusReferral; + #Host: StatusReferral + }; + + public type ReferralBook = { + owner: Principal; + refereds: [Refered]; + }; + public type StatusReferral = { + #Level1; + #Level2; + #Level3; + #Level4; + #Level5; + }; + + ////////////////////////////// Reservations //////////////////////////////// public type Calendary = { diff --git a/canister_ids.json b/canister_ids.json new file mode 100644 index 0000000..3a1f9ba --- /dev/null +++ b/canister_ids.json @@ -0,0 +1,5 @@ +{ + "icrc1_ledger_canister": { + "ic": "d7ew5-caaaa-aaaam-aebqa-cai" + } +} \ No newline at end of file diff --git a/dfx.json b/dfx.json index 95b4399..8507479 100644 --- a/dfx.json +++ b/dfx.json @@ -13,8 +13,14 @@ "icrc1_ledger_canister": { "type": "custom", - "candid": "https://raw.githubusercontent.com/dfinity/ic/aa705aaa621c2e0d4f146f3a1de801edcb0fa0d5/rs/ledger_suite/icrc1/ledger/ledger.did", - "wasm": "https://download.dfinity.systems/ic/aa705aaa621c2e0d4f146f3a1de801edcb0fa0d5/canisters/ic-icrc1-ledger.wasm.gz" + "candid": "https://raw.githubusercontent.com/dfinity/ic/233c1ee2ef68c1c8800b8151b2b9f38e17b8440a/rs/ledger_suite/icrc1/ledger/ledger.did", + "wasm": "https://download.dfinity.systems/ic/233c1ee2ef68c1c8800b8151b2b9f38e17b8440a/canisters/ic-icrc1-ledger.wasm.gz" + }, + + "icrc1_index_canister": { + "type": "custom", + "candid": "https://raw.githubusercontent.com/dfinity/ic/a62848817cec7ae50618a87a526c85d020283fd9/rs/rosetta-api/icrc1/index-ng/index-ng.did", + "wasm": "https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283fd9/canisters/ic-icrc1-index-ng.wasm.gz" }, "test": { diff --git a/package.json b/package.json index fdb1a0b..7d4b7f4 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ ], "scripts": { "deploy-with-content": "chmod +x scripts-sh/deploy-with-content.sh; ./scripts-sh/deploy-with-content.sh", + "deploy-token": "chmod +x scripts-sh/deploy-token.sh; ./scripts-sh/deploy-token.sh", "build": "turbo run build", "preclean": "turbo run clean", "clean": "rm -rf .dfx && rm -rf .turbo && rm -rf node_modules & rm -rf src/declarations" diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh new file mode 100755 index 0000000..eda8ee1 --- /dev/null +++ b/scripts-sh/deploy-token.sh @@ -0,0 +1,147 @@ + +##No funciona la visualizacion del logo en la wallet +dfx deploy icrc1_ledger_canister --ic --argument '( variant { + Init = record { + decimals = opt (8 : nat8); + token_symbol = "SNC"; + transfer_fee = 10_000 : nat; + metadata = vec { + record { + "icrc1:logo"; + variant {Text = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJ + QyAiLS8vVzNDLy9EVEQgU1ZHIDIwMDEwOTA0Ly9FTiIKICJodHRwOi8vd3d3LnczLm9yZy9UUi8y + MDAxL1JFQy1TVkctMjAwMTA5MDQvRFREL3N2ZzEwLmR0ZCI+CjxzdmcgdmVyc2lvbj0iMS4wIiB4 + bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiB3aWR0aD0iMTI4MC4wMDAwMDBwdCIg + aGVpZ2h0PSI4MzAuMDAwMDAwcHQiIHZpZXdCb3g9IjAgMCAxMjgwLjAwMDAwMCA4MzAuMDAwMDAw + IgogcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgbWVldCI+CjxtZXRhZGF0YT4KQ3JlYXRl + ZCBieSBwb3RyYWNlIDEuMTUsIHdyaXR0ZW4gYnkgUGV0ZXIgU2VsaW5nZXIgMjAwMS0yMDE3Cjwv + bWV0YWRhdGE+CjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLDgzMC4wMDAwMDApIHNj + YWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiMwMDAwMDAiIHN0cm9rZT0ibm9uZSI+Cjxw + YXRoIGQ9Ik0xNzQ2IDgyNDAgYy0xMDkgLTMzIC0yMDEgLTYxIC0yMDMgLTY0IC0yIC0yIDM4IC0y + MTAgOTEgLTQ2MyA1MgotMjUyIDE1NCAtNzQxIDIyNiAtMTA4OCA3MiAtMzQ2IDE1NyAtNzU4IDE5 + MCAtOTE1IDE4NCAtODgyIDI3MCAtMTMwMSAyNzAKLTEzMTAgMCAtOCAtMjQgLTE2IC0xMDQgLTM1 + IGwtMjkgLTYgNiAtMTA3IGM0IC01OSA5IC0xMjIgMTMgLTEzOSA2IC0zMSA1Ci0zMyAtNDIgLTU0 + IC0yNyAtMTIgLTIwNiAtNzMgLTM5OSAtMTM1IC03MDQgLTIzMSAtMTQxOSAtNDczIC0xNTg5IC01 + NDAgLTE4NAotNzIgLTIyNCAtMTYwIC0xMjMgLTI2OCA0MSAtNDMgNDYgLTQ1IDIwMiAtODYgbDE2 + MCAtNDIgMTgwIDU3IGM5OSAzMSAzMzEKMTA0IDUxNSAxNjIgMTg0IDU3IDQ4NiAxNTIgNjcwIDIx + MCA1NjUgMTc3IDYyMiAxOTQgNjM0IDE5MCA2IC0zIDUyIC00MCAxMDEKLTg0IDMzMCAtMjg5IDk3 + MiAtNzcyIDExODUgLTg5MSAzMCAtMTcgOTMgLTQwIDE0MCAtNTIgNDcgLTEyIDE2NCAtNDUgMjYw + Ci03NSAyODcgLTg4IDQ1MSAtMTM0IDUyMCAtMTQ0IDUzIC04IDQ4OSAtMjggNTUzIC0yNSA5IDAg + MTcgLTMgMTcgLTggMCAtNCAtNAotOCAtOSAtOCAtNSAwIC0yODYgLTEyMyAtNjIzIC0yNzQgLTcy + MiAtMzIzIC0xNjg0IC03NTIgLTI1NjcgLTExNDYgLTU4MAotMjU5IC02MzUgLTI4MiAtNjQ3IC0y + NjYgLTcgMTAgLTgxIDEzNyAtMTY0IDI4MyBsLTE1MyAyNjQgLTIxMSAtNyBjLTExNiAtMwotMjEy + IC03IC0yMTIgLTggLTEgMCA0NSAtOTUgMTAyIC0yMTEgMTM4IC0yODAgMjI4IC00ODQgMjc2IC02 + MzAgNDkgLTE0NyA2OAotMTg1IDEwOSAtMjE5IDc3IC02NSAxNzUgLTk0IDM0NCAtMTAzIGwxMjAg + LTYgMTQwMCA1NjEgYzEyNTYgNTAzIDE5NzEgNzkxCjI3NzMgMTExNyAxNDUgNTkgMjY1IDEwNSAy + NjYgMTAzIDEgLTEgOSAtMTkgMTYgLTM4IDIwIC00OSA3MyAtMTQwIDgyIC0xNDAgNAowIDEyIDE4 + IDE4IDM5IDEyIDQ1IDU1IDg3IDEwMCA5NSAxNiAzIDQ0IDkgNjAgMTIgNDEgOCA0MCAtMTMgLTYg + LTczIC03NgotMTAwIC0xMjcgLTIzMiAtMTA2IC0yNzkgMTMgLTI3IDM3IC0zMCA4NSAtMTAgbDMz + IDEzIDE4IC05MCBjMzMgLTE2MyAxMTUKLTI4NCAyMTcgLTMxOSAxMzMgLTQ3IDIzOCAtNjIgNDY5 + IC02NSAxMjQgLTEgMjY1IC03IDMxNCAtMTQgMTM1IC0xNyAyMTggMwoyNzYgNjYgMTQgMTUgNTgg + NTQgOTggODcgNjQgNTMgNzcgNzAgMTE2IDE1MSA1MyAxMTAgOTkgMjQ1IDEyMiAzNTkgMTQgNjgg + MTYKMTE0IDEwIDI1OCAtNCA5NiAtMTMgMjA2IC0yMSAyNDMgLTI2IDEyNiAtODggMjA2IC0yMDUg + MjY3IC0zMCAxNiAtNDggMjkgLTQwCjMwIDggMCAyMDkgMTYgNDQ1IDM1IDQzNCAzNiA2NDUgNDYg + MTMyMCA2NSA5NTEgMjcgMjAyOSAxMDUgMjI5MCAxNjUgMzA1IDcxCjY1OSAzMjAgOTQ4IDY2NyAx + MDIgMTIyIDE1NSAyNjcgMTQ0IDM5MCAtMTEgMTIyIC0xNDUgMzMxIC0zNjcgNTcyIC0zNiAzOQot + ODMgMTAyIC0xMDUgMTQwIC00NyA4MiAtMTYzIDIwNCAtMjU3IDI3MCAtNzUgNTMgLTM1OCAyMTYg + LTQ5MiAyODMgLTExNiA1OAotMjcwIDEwOSAtMzgxIDEyNSAtMTQxIDIwIC0xMzU1IDE3IC0xODM1 + IC01IC0yMDYgLTEwIC0zODEgLTIwIC0zODggLTIyIC05Ci0zIC0xMiAxIC04IDEzIDMgOSAxNDUg + NDY2IDMxNiAxMDE1IGwzMTIgOTk4IC0xNjMgNTI3IGMtODkgMjkwIC0xNjMgNTI4Ci0xNjQgNTMw + IC0yIDMgLTM0OSA0MCAtMzUzIDM3IC0xIC0xIDI1IC0yMjUgNTggLTQ5OCAzMyAtMjczIDYwIC01 + MDggNjAgLTUyMgowIC0xNSAtMjQxIC00NDcgLTYxMSAtMTA5MyAtMzM3IC01ODcgLTYxMyAtMTA2 + OSAtNjE1IC0xMDcxIC00IC00IC0zMzQgLTI0Ci0xMzA0IC04MSAtMTk1IC0xMSAtNTUxIC0zMiAt + NzkwIC00NiAtNzM5IC00NCAtMTA5MyAtMjMgLTE0NjAgODggLTM3MyAxMTIKLTQ3OCAxNjEgLTYw + NiAyODIgbC05MSA4NSA1MyAyMzggYzI5IDEzMSA1MSAyMzkgNDkgMjQxIC0zIDMgLTIyOCAzMSAt + MjU3IDMyCi0zIDAgLTI2IC0zOCAtNTAgLTg1IC0yMyAtNDcgLTQ1IC04NSAtNDggLTg1IC0zIDAg + LTE0IDE3IC0yNiAzOCAtMTQ2IDI2MwotMjE3IDM5NiAtMzkwIDczNyAtMjEwIDQxMyAtNTA0IDEw + MjIgLTc1NCAxNTU4IC0xMjIgMjYxIC0xNDYgMzA3IC0xNjQgMzA2Ci0xMiAwIC0xMTAgLTI3IC0y + MjAgLTU5eiIvPgo8L2c+Cjwvc3ZnPgo=" + } + }; + record { "icrc1:decimals"; variant { Nat = 8 : nat } }; + record { "icrc1:name"; variant { Text = "$TOUR" } }; + record { "icrc1:symbol"; variant { Text = "TOUR" } }; + record { "icrc1:fee"; variant { Nat = 10_000 : nat } }; + record { "icrc1:max_memo_length"; variant { Nat = 80 : nat } }; + }; + minting_account = record { + owner = principal "y77j5-4vnxl-ywos7-qjtcr-6iopc-i2ql2-iwoem-ehvwk-wruju-fr7ib-mae"; + subaccount = null; + }; + initial_balances = vec { + record { + record { + owner = principal "zpdk5-e6ec5-izoeb-uzhwy-rl2ot-4ag42-im6yv-itg3x-inywa-j3bae-tqe"; + subaccount = null; + }; + 1_000_000_000_000_000 : nat; + }; + record { + record { + owner = principal "xigzi-mf2wo-xch5n-4dlsf-5tq6n-pke7b-7w2tx-2fv4h-l3yvi-3ycr2-pae"; + subaccount = null; + }; + 500_000_000_000_000: nat; + } + }; + fee_collector_account = opt record { + owner = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; + subaccount = null; + }; + archive_options = record { + num_blocks_to_archive = 1_000 : nat64; + max_transactions_per_response = null; + trigger_threshold = 2_000 : nat64; + more_controller_ids = opt vec { + principal "d2alm-ajpbz-hohks-j3k3y-ulxfm-fegz6-jwopx-d2eu7-3ycil-hnxqa-hae"; + }; + max_message_size_bytes = null; + cycles_for_archive_creation = opt (10_000_000_000_000 : nat64); + node_max_memory_size_bytes = null; + controller_id = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; + }; + max_memo_length = null; + token_name = "Smart Network Card"; + feature_flags = opt record { icrc2 = true }; + } + } +)' + +# dfx deploy icrc1_ledger_canister --ic --argument '(variant { +# Upgrade = opt record { +# change_archive_options = null; +# token_symbol = null; +# transfer_fee = null; +# metadata = opt vec { +# record { +# "icrc1:logo"; +# variant { +# Text = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNzYuNDg4NzkybW0iCiAgIGhlaWdodD0iODYuNDU0NjEzbW0iCiAgIHZpZXdCb3g9IjAgMCA3Ni40ODg3OTIgODYuNDU0NjE0IgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmc1IgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjEuMiAoMGEwMGNmNTMzOSwgMjAyMi0wMi0wNCkiCiAgIHNvZGlwb2RpOmRvY25hbWU9ImxvZ28uc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3NyIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSJmYWxzZSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0ibW0iCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjEuMDI5MjE2MiIKICAgICBpbmtzY2FwZTpjeD0iMTI1LjgyMzkxIgogICAgIGlua3NjYXBlOmN5PSIxNDkuNjI4NDMiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxMDM1IgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc3OCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iNTAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjExNjkiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgaW5rc2NhcGU6c25hcC1ncmlkcz0iZmFsc2UiCiAgICAgYm9yZGVybGF5ZXI9ImZhbHNlIgogICAgIHNob3dib3JkZXI9InRydWUiCiAgICAgaW5rc2NhcGU6c2hvd3BhZ2VzaGFkb3c9ImZhbHNlIgogICAgIGZpdC1tYXJnaW4tdG9wPSIwIgogICAgIGZpdC1tYXJnaW4tbGVmdD0iMCIKICAgICBmaXQtbWFyZ2luLXJpZ2h0PSIwIgogICAgIGZpdC1tYXJnaW4tYm90dG9tPSIwIiAvPgogIDxkZWZzCiAgICAgaWQ9ImRlZnMyIiAvPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkNhcGEgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI3Ljk2Nzc5NSwtNDIuOTYxMTQ1KSI+CiAgICA8cGF0aAogICAgICAgc29kaXBvZGk6dHlwZT0ic3RhciIKICAgICAgIHN0eWxlPSJmaWxsOiMwMGZmZmY7ZmlsbC1vcGFjaXR5OjA7c3Ryb2tlOiMzN2Q2ZWE7c3Ryb2tlLXdpZHRoOjE5LjE0MzM7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBpZD0icGF0aDg4NDQiCiAgICAgICBpbmtzY2FwZTpmbGF0c2lkZWQ9InRydWUiCiAgICAgICBzb2RpcG9kaTpzaWRlcz0iNiIKICAgICAgIHNvZGlwb2RpOmN4PSIxMTQuNTM3MTgiCiAgICAgICBzb2RpcG9kaTpjeT0iMzA1Ljk3OTgzIgogICAgICAgc29kaXBvZGk6cjE9Ijc3Ljc2MzQ5NiIKICAgICAgIHNvZGlwb2RpOnIyPSI2Ny4zNDUxNjEiCiAgICAgICBzb2RpcG9kaTphcmcxPSIwLjUxNjcwNDgyIgogICAgICAgc29kaXBvZGk6YXJnMj0iMS4wNDAzMDM2IgogICAgICAgaW5rc2NhcGU6cm91bmRlZD0iMCIKICAgICAgIGlua3NjYXBlOnJhbmRvbWl6ZWQ9IjAiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLjI2NDU4MzMzLDAsMCwwLjI2NDU4MzMzLDE4LjA4NDU3LC01Ljc3ODg2MzYpIgogICAgICAgaW5rc2NhcGU6dHJhbnNmb3JtLWNlbnRlci15PSItMS4wMTk5MzI4ZS0wNiIKICAgICAgIGQ9Im0gMTgyLjE0ODc5LDM0NC4zOTYzOCAtNjcuMDc1NTIsMzkuMzQ1MSAtNjcuNjExNjA4LC0zOC40MTY1NiAtMC41MzYwOTQsLTc3Ljc2MTY1IDY3LjA3NTUxMiwtMzkuMzQ1MDkgNjcuNjExNjEsMzguNDE2NTUgeiIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMzdkNmVhO3N0cm9rZS13aWR0aDo1LjA2NTtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSA2Ni4yNzgxMDMsODUuMzQyNjc2IDg0LjE2NzAwNSw5NS41MDcwNTcgMTAxLjkxNDA3LDg1LjA5NyAxMDEuNzcyMjMsNjQuNTIyNTYzIDgzLjg4MzMyNCw1NC4zNTgxODUiCiAgICAgICBpZD0icGF0aDIxNjE5IiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMzN2Q2ZWE7c3Ryb2tlLXdpZHRoOjUuMDY1O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDQ4LjUzMTAzOSw5NS43NTI3MzQgMC4xNDE4NCwyMC41NzQ0MzYgYyAwLDAgMTcuODg4OTA1LDEwLjE2NDM4IDE3Ljg4ODkwNSwxMC4xNjQzOCBsIDE3Ljc0NzA2NCwtMTAuNDEwMDYiCiAgICAgICBpZD0icGF0aDIxNzM0IiAvPgogICAgPGVsbGlwc2UKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMzN2Q2ZWE7c3Ryb2tlLXdpZHRoOjUuMDY1O3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBpZD0icGF0aDI3NDYyIgogICAgICAgY3g9IjkwLjI2NDgwMSIKICAgICAgIGN5PSIxMTIuMzI3MzQiCiAgICAgICByeD0iNS4wNjQyOTUzIgogICAgICAgcnk9IjUuMTU3NjM4MSIgLz4KICAgIDxlbGxpcHNlCiAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTojMzdkNmVhO3N0cm9rZS13aWR0aDo1LjA2NTtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIgogICAgICAgaWQ9InBhdGgyNzQ2Mi02IgogICAgICAgY3g9Ijc3Ljc2MzAwOCIKICAgICAgIGN5PSI1MC42NTEyODMiCiAgICAgICByeD0iNS4wNjQyOTUzIgogICAgICAgcnk9IjUuMTU3NjM4MSIgLz4KICA8L2c+Cjwvc3ZnPgo=" +# }; +# }; +# }; +# change_fee_collector = null; +# max_memo_length = null; +# token_name = null; +# feature_flags = null; +# } +# } +# )' + + +# Ejemplo de metadata de ckBTC +#( +# vec { +# record { +# "icrc1:logo"; +# variant { +# Text = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQ2IiBoZWlnaHQ9IjE0NiIgdmlld0JveD0iMCAwIDE0NiAxNDYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDYiIGhlaWdodD0iMTQ2IiByeD0iNzMiIGZpbGw9IiMzQjAwQjkiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNi4zODM3IDc3LjIwNTJDMTguNDM0IDEwNS4yMDYgNDAuNzk0IDEyNy41NjYgNjguNzk0OSAxMjkuNjE2VjEzNS45MzlDMzcuMzA4NyAxMzMuODY3IDEyLjEzMyAxMDguNjkxIDEwLjA2MDUgNzcuMjA1MkgxNi4zODM3WiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzExMF81NzIpIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNjguNzY0NiAxNi4zNTM0QzQwLjc2MzggMTguNDAzNiAxOC40MDM3IDQwLjc2MzcgMTYuMzUzNSA2OC43NjQ2TDEwLjAzMDMgNjguNzY0NkMxMi4xMDI3IDM3LjI3ODQgMzcuMjc4NSAxMi4xMDI2IDY4Ljc2NDYgMTAuMDMwMkw2OC43NjQ2IDE2LjM1MzRaIiBmaWxsPSIjMjlBQkUyIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTI5LjYxNiA2OC43MzQzQzEyNy41NjYgNDAuNzMzNSAxMDUuMjA2IDE4LjM3MzQgNzcuMjA1MSAxNi4zMjMyTDc3LjIwNTEgMTBDMTA4LjY5MSAxMi4wNzI0IDEzMy44NjcgMzcuMjQ4MiAxMzUuOTM5IDY4LjczNDNMMTI5LjYxNiA2OC43MzQzWiIgZmlsbD0idXJsKCNwYWludDFfbGluZWFyXzExMF81NzIpIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNzcuMjM1NCAxMjkuNTg2QzEwNS4yMzYgMTI3LjUzNiAxMjcuNTk2IDEwNS4xNzYgMTI5LjY0NyA3Ny4xNzQ5TDEzNS45NyA3Ny4xNzQ5QzEzMy44OTcgMTA4LjY2MSAxMDguNzIyIDEzMy44MzcgNzcuMjM1NCAxMzUuOTA5TDc3LjIzNTQgMTI5LjU4NloiIGZpbGw9IiMyOUFCRTIiLz4KPHBhdGggZD0iTTk5LjgyMTcgNjQuNzI0NUMxMDEuMDE0IDU2Ljc1MzggOTQuOTQ0NyA1Mi40Njg5IDg2LjY0NTUgNDkuNjEwNEw4OS4zMzc2IDM4LjgxM0w4Mi43NjQ1IDM3LjE3NUw4MC4xNDM1IDQ3LjY4NzlDNzguNDE1NSA0Ny4yNTczIDc2LjY0MDYgNDYuODUxMSA3NC44NzcxIDQ2LjQ0ODdMNzcuNTE2OCAzNS44NjY1TDcwLjk0NzQgMzQuMjI4NUw2OC4yNTM0IDQ1LjAyMjJDNjYuODIzIDQ0LjY5NjUgNjUuNDE4OSA0NC4zNzQ2IDY0LjA1NiA0NC4wMzU3TDY0LjA2MzUgNDQuMDAyTDU0Ljk5ODUgNDEuNzM4OEw1My4yNDk5IDQ4Ljc1ODZDNTMuMjQ5OSA0OC43NTg2IDU4LjEyNjkgNDkuODc2MiA1OC4wMjM5IDQ5Ljk0NTRDNjAuNjg2MSA1MC42MSA2MS4xNjcyIDUyLjM3MTUgNjEuMDg2NyA1My43NjhDNTguNjI3IDYzLjYzNDUgNTYuMTcyMSA3My40Nzg4IDUzLjcxMDQgODMuMzQ2N0M1My4zODQ3IDg0LjE1NTQgNTIuNTU5MSA4NS4zNjg0IDUwLjY5ODIgODQuOTA3OUM1MC43NjM3IDg1LjAwMzQgNDUuOTIwNCA4My43MTU1IDQ1LjkyMDQgODMuNzE1NUw0Mi42NTcyIDkxLjIzODlMNTEuMjExMSA5My4zNzFDNTIuODAyNSA5My43Njk3IDU0LjM2MTkgOTQuMTg3MiA1NS44OTcxIDk0LjU4MDNMNTMuMTc2OSAxMDUuNTAxTDU5Ljc0MjYgMTA3LjEzOUw2Mi40MzY2IDk2LjMzNDNDNjQuMjMwMSA5Ni44MjEgNjUuOTcxMiA5Ny4yNzAzIDY3LjY3NDkgOTcuNjkzNEw2NC45OTAyIDEwOC40NDhMNzEuNTYzNCAxMTAuMDg2TDc0LjI4MzYgOTkuMTg1M0M4NS40OTIyIDEwMS4zMDYgOTMuOTIwNyAxMDAuNDUxIDk3LjQ2ODQgOTAuMzE0MUMxMDAuMzI3IDgyLjE1MjQgOTcuMzI2MSA3Ny40NDQ1IDkxLjQyODggNzQuMzc0NUM5NS43MjM2IDczLjM4NDIgOTguOTU4NiA3MC41NTk0IDk5LjgyMTcgNjQuNzI0NVpNODQuODAzMiA4NS43ODIxQzgyLjc3MiA5My45NDM4IDY5LjAyODQgODkuNTMxNiA2NC41NzI3IDg4LjQyNTNMNjguMTgyMiA3My45NTdDNzIuNjM4IDc1LjA2ODkgODYuOTI2MyA3Ny4yNzA0IDg0LjgwMzIgODUuNzgyMVpNODYuODM2NCA2NC42MDY2Qzg0Ljk4MyA3Mi4wMzA3IDczLjU0NDEgNjguMjU4OCA2OS44MzM1IDY3LjMzNEw3My4xMDYgNTQuMjExN0M3Ni44MTY2IDU1LjEzNjQgODguNzY2NiA1Ni44NjIzIDg2LjgzNjQgNjQuNjA2NloiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMTEwXzU3MiIgeDE9IjUzLjQ3MzYiIHkxPSIxMjIuNzkiIHgyPSIxNC4wMzYyIiB5Mj0iODkuNTc4NiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBvZmZzZXQ9IjAuMjEiIHN0b3AtY29sb3I9IiNFRDFFNzkiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTIyNzg1Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8xMTBfNTcyIiB4MT0iMTIwLjY1IiB5MT0iNTUuNjAyMSIgeDI9IjgxLjIxMyIgeTI9IjIyLjM5MTQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agb2Zmc2V0PSIwLjIxIiBzdG9wLWNvbG9yPSIjRjE1QTI0Ii8+CjxzdG9wIG9mZnNldD0iMC42ODQxIiBzdG9wLWNvbG9yPSIjRkJCMDNCIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==" +# }; +# }; +# record { "icrc1:decimals"; variant { Nat = 8 : nat } }; +# record { "icrc1:name"; variant { Text = "ckBTC" } }; +# record { "icrc1:symbol"; variant { Text = "ckBTC" } }; +# record { "icrc1:fee"; variant { Nat = 10 : nat } }; +# record { "icrc1:max_memo_length"; variant { Nat = 80 : nat } }; +# }, +# ) \ No newline at end of file diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index 29df881..a547e2c 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -12,7 +12,8 @@ dfx canister call backend signUpAsHost '(record { firstName="Alberto"; lastName="Campos"; email="null"; - phone = opt 542238780892 + phone = opt 542238780892; + referralBy = null; })' # CreateHousing 1 echo "Alberto registra un housing ... " @@ -119,6 +120,9 @@ dfx canister call backend createHousing '(record { thumbnail = blob "Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABAAEAALKLKAKHUJHAA" })' +echo codigo de referidos de alberto... +export albertoCode=$(dfx canister call backend getMyReferralCode) +echo $albertoCode @@ -141,7 +145,8 @@ dfx canister call backend signUpAsHost '(record { firstName="Claudia"; lastName="Gimenez"; email="claugimenez@gmail.com"; - phone = opt 558789878522 + # phone = opt 558789878522; + referralCode = $albertoCode })' # ------------ Usuario Host 4 -------------- @@ -152,7 +157,8 @@ dfx canister call backend signUpAsUser '(record { firstName="Mario"; lastName="Pappa"; email="mariopapa@gmail.com"; - phone = opt 542235227692 + phone = opt 542235227692; + referralCode = opt ${albertoCode}; })' # ------------ Usuario Host 5 -------------- diff --git a/scripts-sh/deployToken.sh b/scripts-sh/deployToken.sh deleted file mode 100644 index e69de29..0000000 diff --git a/scripts-sh/verLogo.html b/scripts-sh/verLogo.html new file mode 100644 index 0000000..15fc524 --- /dev/null +++ b/scripts-sh/verLogo.html @@ -0,0 +1,60 @@ + + + + + + SVG Base64 Test + + +

SVG Base64 Test

+

Si ves una imagen abajo, tu conversión fue exitosa:

+ Test Image + + From 275a0713b47d0ecd41499c40fd29cacf758fceed Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Fri, 24 Jan 2025 00:22:57 -0300 Subject: [PATCH 41/57] icrc1_index_canister --- .gitignore | 3 ++ backend/constants.mo | 1 + backend/main.mo | 34 +++++++++++++-------- canister_ids.json | 5 --- dfx.json | 3 +- scripts-sh/deploy-token.sh | 62 ++++++++------------------------------ 6 files changed, 40 insertions(+), 68 deletions(-) delete mode 100644 canister_ids.json diff --git a/.gitignore b/.gitignore index 52f386c..d4c73a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.env # Node node_modules @@ -11,8 +12,10 @@ node_modules # DFX .dfx src/declarations +canister_ids.json # Azle .azle .mops + diff --git a/backend/constants.mo b/backend/constants.mo index fe9ac0a..35fb13d 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -9,6 +9,7 @@ module { public let UnauthorizedCaller = "Unauthorized user"; public let NotVerifiedUser = "The user is not verified"; public let NotUser = "Unregistered user"; + public let Anonymous = "Anonymous caller is not allowed"; public let NotHostUser = "User is not Host User"; public let CallerIsNotrequester = "The caller does not match the reservation requester"; public let InactiveHousing = "Housing is temporarily disabled"; diff --git a/backend/main.mo b/backend/main.mo index 7ed2514..442adb1 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -1,25 +1,28 @@ import Prim "mo:⛔"; -import Map "mo:map/Map"; -import Set "mo:map/Set"; -import { phash; nhash; n32hash; thash } "mo:map/Map"; -import Principal "mo:base/Principal"; -import Buffer "mo:base/Buffer"; -import Blob "mo:base/Blob"; -import Types "types"; -import { now } "mo:base/Time"; -import msg "constants"; -import { print } "mo:base/Debug"; import Array "mo:base/Array"; +import Blob "mo:base/Blob"; +import Buffer "mo:base/Buffer"; import Int "mo:base/Int"; +import Iter "mo:base/Iter"; import List "mo:base/List"; import Nat "mo:base/Nat"; import Nat8 "mo:base/Nat8"; import Nat64 "mo:base/Nat64"; +import Principal "mo:base/Principal"; import Text "mo:base/Text"; -import Iter "mo:base/Iter"; +import { now } "mo:base/Time"; +import { print } "mo:base/Debug"; + +import Map "mo:map/Map"; +import Set "mo:map/Set"; +import { phash; nhash; n32hash; thash } "mo:map/Map"; import Indexer_icp "./indexer_icp_token"; import AccountIdentifier "mo:account-identifier"; +import Types "types"; +import msg "constants"; + + shared ({ caller = DEPLOYER }) actor class Triourism () = this { type User = Types.User; @@ -87,7 +90,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { ///////////////////////////////////// Login Update functions //////////////////////////////////////// public shared ({ caller }) func signUpAsUser(data: Types.SignUpData) : async SignUpResult { - if(Principal.isAnonymous(caller)) { return #Err(msg.NotUser) }; + if(Principal.isAnonymous(caller)) { return #Err(msg.Anonymous) }; if (Map.has(users,phash, caller )){ return #Err("The caller is linked to an existing User Host") }; @@ -114,7 +117,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; public shared ({ caller }) func signUpAsHost(data: Types.SignUpData) : async SignUpResult { - if(Principal.isAnonymous(caller)) { return #Err(msg.NotUser) }; + if(Principal.isAnonymous(caller)) { return #Err(msg.Anonymous) }; if (Map.has(users,phash, caller )){ return #Err("The caller is linked to an existing User") }; @@ -1217,6 +1220,11 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; + + + public shared ({ caller }) func cancelReservation(reservationId: Nat): async (){ + //TODO: Implementar cancelacion de reservas + }; // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ // let housing = Map.get(housings, nhash, housingId); diff --git a/canister_ids.json b/canister_ids.json deleted file mode 100644 index 3a1f9ba..0000000 --- a/canister_ids.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "icrc1_ledger_canister": { - "ic": "d7ew5-caaaa-aaaam-aebqa-cai" - } -} \ No newline at end of file diff --git a/dfx.json b/dfx.json index 8507479..34dbfa3 100644 --- a/dfx.json +++ b/dfx.json @@ -19,7 +19,7 @@ "icrc1_index_canister": { "type": "custom", - "candid": "https://raw.githubusercontent.com/dfinity/ic/a62848817cec7ae50618a87a526c85d020283fd9/rs/rosetta-api/icrc1/index-ng/index-ng.did", + "candid": "https://raw.githubusercontent.com/dfinity/ic/a62848817cec7ae50618a87a526c85d020283fd9/rs/ledger_suite/icrc1/index-ng/index-ng.did", "wasm": "https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283fd9/canisters/ic-icrc1-index-ng.wasm.gz" }, @@ -55,6 +55,7 @@ } } }, + "output_env_file": ".env", "defaults": { "build": { "packtool": "mops sources" diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh index eda8ee1..f2b5021 100755 --- a/scripts-sh/deploy-token.sh +++ b/scripts-sh/deploy-token.sh @@ -1,6 +1,6 @@ ##No funciona la visualizacion del logo en la wallet -dfx deploy icrc1_ledger_canister --ic --argument '( variant { +dfx deploy icrc1_ledger_canister --argument '( variant { Init = record { decimals = opt (8 : nat8); token_symbol = "SNC"; @@ -8,54 +8,11 @@ dfx deploy icrc1_ledger_canister --ic --argument '( variant { metadata = vec { record { "icrc1:logo"; - variant {Text = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJ - QyAiLS8vVzNDLy9EVEQgU1ZHIDIwMDEwOTA0Ly9FTiIKICJodHRwOi8vd3d3LnczLm9yZy9UUi8y - MDAxL1JFQy1TVkctMjAwMTA5MDQvRFREL3N2ZzEwLmR0ZCI+CjxzdmcgdmVyc2lvbj0iMS4wIiB4 - bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiB3aWR0aD0iMTI4MC4wMDAwMDBwdCIg - aGVpZ2h0PSI4MzAuMDAwMDAwcHQiIHZpZXdCb3g9IjAgMCAxMjgwLjAwMDAwMCA4MzAuMDAwMDAw - IgogcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgbWVldCI+CjxtZXRhZGF0YT4KQ3JlYXRl - ZCBieSBwb3RyYWNlIDEuMTUsIHdyaXR0ZW4gYnkgUGV0ZXIgU2VsaW5nZXIgMjAwMS0yMDE3Cjwv - bWV0YWRhdGE+CjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLDgzMC4wMDAwMDApIHNj - YWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiMwMDAwMDAiIHN0cm9rZT0ibm9uZSI+Cjxw - YXRoIGQ9Ik0xNzQ2IDgyNDAgYy0xMDkgLTMzIC0yMDEgLTYxIC0yMDMgLTY0IC0yIC0yIDM4IC0y - MTAgOTEgLTQ2MyA1MgotMjUyIDE1NCAtNzQxIDIyNiAtMTA4OCA3MiAtMzQ2IDE1NyAtNzU4IDE5 - MCAtOTE1IDE4NCAtODgyIDI3MCAtMTMwMSAyNzAKLTEzMTAgMCAtOCAtMjQgLTE2IC0xMDQgLTM1 - IGwtMjkgLTYgNiAtMTA3IGM0IC01OSA5IC0xMjIgMTMgLTEzOSA2IC0zMSA1Ci0zMyAtNDIgLTU0 - IC0yNyAtMTIgLTIwNiAtNzMgLTM5OSAtMTM1IC03MDQgLTIzMSAtMTQxOSAtNDczIC0xNTg5IC01 - NDAgLTE4NAotNzIgLTIyNCAtMTYwIC0xMjMgLTI2OCA0MSAtNDMgNDYgLTQ1IDIwMiAtODYgbDE2 - MCAtNDIgMTgwIDU3IGM5OSAzMSAzMzEKMTA0IDUxNSAxNjIgMTg0IDU3IDQ4NiAxNTIgNjcwIDIx - MCA1NjUgMTc3IDYyMiAxOTQgNjM0IDE5MCA2IC0zIDUyIC00MCAxMDEKLTg0IDMzMCAtMjg5IDk3 - MiAtNzcyIDExODUgLTg5MSAzMCAtMTcgOTMgLTQwIDE0MCAtNTIgNDcgLTEyIDE2NCAtNDUgMjYw - Ci03NSAyODcgLTg4IDQ1MSAtMTM0IDUyMCAtMTQ0IDUzIC04IDQ4OSAtMjggNTUzIC0yNSA5IDAg - MTcgLTMgMTcgLTggMCAtNCAtNAotOCAtOSAtOCAtNSAwIC0yODYgLTEyMyAtNjIzIC0yNzQgLTcy - MiAtMzIzIC0xNjg0IC03NTIgLTI1NjcgLTExNDYgLTU4MAotMjU5IC02MzUgLTI4MiAtNjQ3IC0y - NjYgLTcgMTAgLTgxIDEzNyAtMTY0IDI4MyBsLTE1MyAyNjQgLTIxMSAtNyBjLTExNiAtMwotMjEy - IC03IC0yMTIgLTggLTEgMCA0NSAtOTUgMTAyIC0yMTEgMTM4IC0yODAgMjI4IC00ODQgMjc2IC02 - MzAgNDkgLTE0NyA2OAotMTg1IDEwOSAtMjE5IDc3IC02NSAxNzUgLTk0IDM0NCAtMTAzIGwxMjAg - LTYgMTQwMCA1NjEgYzEyNTYgNTAzIDE5NzEgNzkxCjI3NzMgMTExNyAxNDUgNTkgMjY1IDEwNSAy - NjYgMTAzIDEgLTEgOSAtMTkgMTYgLTM4IDIwIC00OSA3MyAtMTQwIDgyIC0xNDAgNAowIDEyIDE4 - IDE4IDM5IDEyIDQ1IDU1IDg3IDEwMCA5NSAxNiAzIDQ0IDkgNjAgMTIgNDEgOCA0MCAtMTMgLTYg - LTczIC03NgotMTAwIC0xMjcgLTIzMiAtMTA2IC0yNzkgMTMgLTI3IDM3IC0zMCA4NSAtMTAgbDMz - IDEzIDE4IC05MCBjMzMgLTE2MyAxMTUKLTI4NCAyMTcgLTMxOSAxMzMgLTQ3IDIzOCAtNjIgNDY5 - IC02NSAxMjQgLTEgMjY1IC03IDMxNCAtMTQgMTM1IC0xNyAyMTggMwoyNzYgNjYgMTQgMTUgNTgg - NTQgOTggODcgNjQgNTMgNzcgNzAgMTE2IDE1MSA1MyAxMTAgOTkgMjQ1IDEyMiAzNTkgMTQgNjgg - MTYKMTE0IDEwIDI1OCAtNCA5NiAtMTMgMjA2IC0yMSAyNDMgLTI2IDEyNiAtODggMjA2IC0yMDUg - MjY3IC0zMCAxNiAtNDggMjkgLTQwCjMwIDggMCAyMDkgMTYgNDQ1IDM1IDQzNCAzNiA2NDUgNDYg - MTMyMCA2NSA5NTEgMjcgMjAyOSAxMDUgMjI5MCAxNjUgMzA1IDcxCjY1OSAzMjAgOTQ4IDY2NyAx - MDIgMTIyIDE1NSAyNjcgMTQ0IDM5MCAtMTEgMTIyIC0xNDUgMzMxIC0zNjcgNTcyIC0zNiAzOQot - ODMgMTAyIC0xMDUgMTQwIC00NyA4MiAtMTYzIDIwNCAtMjU3IDI3MCAtNzUgNTMgLTM1OCAyMTYg - LTQ5MiAyODMgLTExNiA1OAotMjcwIDEwOSAtMzgxIDEyNSAtMTQxIDIwIC0xMzU1IDE3IC0xODM1 - IC01IC0yMDYgLTEwIC0zODEgLTIwIC0zODggLTIyIC05Ci0zIC0xMiAxIC04IDEzIDMgOSAxNDUg - NDY2IDMxNiAxMDE1IGwzMTIgOTk4IC0xNjMgNTI3IGMtODkgMjkwIC0xNjMgNTI4Ci0xNjQgNTMw - IC0yIDMgLTM0OSA0MCAtMzUzIDM3IC0xIC0xIDI1IC0yMjUgNTggLTQ5OCAzMyAtMjczIDYwIC01 - MDggNjAgLTUyMgowIC0xNSAtMjQxIC00NDcgLTYxMSAtMTA5MyAtMzM3IC01ODcgLTYxMyAtMTA2 - OSAtNjE1IC0xMDcxIC00IC00IC0zMzQgLTI0Ci0xMzA0IC04MSAtMTk1IC0xMSAtNTUxIC0zMiAt - NzkwIC00NiAtNzM5IC00NCAtMTA5MyAtMjMgLTE0NjAgODggLTM3MyAxMTIKLTQ3OCAxNjEgLTYw - NiAyODIgbC05MSA4NSA1MyAyMzggYzI5IDEzMSA1MSAyMzkgNDkgMjQxIC0zIDMgLTIyOCAzMSAt - MjU3IDMyCi0zIDAgLTI2IC0zOCAtNTAgLTg1IC0yMyAtNDcgLTQ1IC04NSAtNDggLTg1IC0zIDAg - LTE0IDE3IC0yNiAzOCAtMTQ2IDI2MwotMjE3IDM5NiAtMzkwIDczNyAtMjEwIDQxMyAtNTA0IDEw - MjIgLTc1NCAxNTU4IC0xMjIgMjYxIC0xNDYgMzA3IC0xNjQgMzA2Ci0xMiAwIC0xMTAgLTI3IC0y - MjAgLTU5eiIvPgo8L2c+Cjwvc3ZnPgo=" + variant {Text = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAg +MTAwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCI+CiAgICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUw +IiByPSI0MCIgZmlsbD0iYmx1ZSI+CiAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iciIg +ZnJvbT0iNDAiIHRvPSIyMCIgZHVyPSIxcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAg +ICA8L2NpcmNsZT4KPC9zdmc+Cgo=" } }; record { "icrc1:decimals"; variant { Nat = 8 : nat } }; @@ -107,6 +64,13 @@ dfx deploy icrc1_ledger_canister --ic --argument '( variant { } )' +dfx deploy icrc1_index_canister --argument '(opt variant { + Init = record { + ledger_id = "bkyz2-fmaaa-aaaaa-qaaaq-cai"; + retrieve_blocks_from_ledger_interval_seconds = null + } +})' + # dfx deploy icrc1_ledger_canister --ic --argument '(variant { # Upgrade = opt record { # change_archive_options = null; From 5627ddaa600fe1b67636791e186e3463ca3be4d9 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sat, 25 Jan 2025 04:09:42 -0300 Subject: [PATCH 42/57] Cancel reservation flow --- backend/main.mo | 114 ++++++++++++++++++++++++------ backend/types.mo | 22 +++++- scripts-sh/deploy-with-content.sh | 14 ++-- 3 files changed, 117 insertions(+), 33 deletions(-) diff --git a/backend/main.mo b/backend/main.mo index 442adb1..aa5e041 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -53,9 +53,17 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { // /////////////// WARNING modificar estas variables en produccion a los valores reales //// let nanoSecPerDay = 30 * 1_000_000_000; // Test Transcurso acelerado de los dias // let nanoSecPerDay = 86400 * 1_000_000_000; // Valor real de nanosegundos en un dia + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////// Global Configuration Parameters ////////////////////////////////////// + + stable var CancellationFeeCompensateBuyer: Nat64 = 5; //Percentage added to the buyer's refund stable var TimeToPay = 15 * 1_000_000_000; // Tiempo en nanosegundos para confirmar la reserva mediante pago + stable var MinDaysBeforeCheckinForCancellation = 4; // Minimo de dias antes del checkin para cancelar pagando CancellationFeeCompensateBuyer // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////// Core Data Structures /////////////////////// stable let admins = Set.new(); ignore Set.put(admins, phash, DEPLOYER); @@ -104,7 +112,8 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { let newUser: User = { data with verified = true; - reviewsIssued = List.nil(); + reviewsIssued = List.nil(); //Reservation IDs in reservationsPendingConfirmation + reservations = List.nil(); //Reservation IDs in reservationsHistory score = 0; }; ignore Map.put(users, phash, caller, newUser); @@ -1108,21 +1117,24 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { case null { #Err(msg.NotHousing)}; case ( ?housing ) { if(checkDisponibility(housingId, Prim.abs(checkIn), Prim.abs(checkOut))){ - lastReservationId += 1; + lastReservationId += 1; let amount = calculatePrice(housing.price, Prim.abs(checkOut - checkIn)); let reservation: Reservation = { date = now(); + timestampCheckIn = (now() - now() % nanoSecPerDay) + (checkIn * nanoSecPerDay) /* + (housing.checkIn * 60 * 60 * 1000000000) */; housingId; reservationId = lastReservationId; requester; + ownerHousing = housing.owner; checkIn; checkOut; guest; email; phone; - confirmated = false; + // confirmated = false; + status = #Pending; amount; - dataTransaction = null; + dataTransaction = Types.NullTrx; }; ignore Map.put(reservationsPendingConfirmation, nhash, lastReservationId, reservation); putRequestReservationToHousing(housingId, lastReservationId); @@ -1184,33 +1196,27 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { switch housing { case null { #Err(msg.NotHousing)}; case ( ?housing ) { + let currentReservation = { + reservation with + status = #Confirmed; + dataTransaction = txData + }; let calendary = { housing.calendary with reservations = Prim.Array_tabulate( housing.calendary.reservations.size() + 1, - func x = if (x == 0) { - { - reservation with - confirmated = true; - dataTransaction = ?txData - } - } else { - housing.calendary.reservations[x - 1] - } - ) + func x = if (x == 0) { currentReservation } else { housing.calendary.reservations[x - 1] }) }; print(debug_show(calendary)); - let reservationsPending = Array.filter( - housing.reservationsPending, - func x = x !=reservationId - ); + let reservationsPending = Array.filter(housing.reservationsPending, func x = x !=reservationId ); + ignore Map.put(reservationsHistory, nhash, reservationId, currentReservation); ignore Map.put( housings, nhash, reservation.housingId, { housing with calendary; reservationsPending } ); - #Ok({ reservation with confirmated = true }) + #Ok(currentReservation) } } } else { @@ -1219,11 +1225,73 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } } + }; + + public shared ({ caller }) func requestToCancelReservation(reservationId: Nat): async TransactionResponse { + + let reservation = Map.get(reservationsHistory, nhash, reservationId); + switch reservation { + case null { return #Err(msg.NotReservation)}; + case ( ?reservation ) { + if (reservation.ownerHousing != caller) { + return #Err(msg.CallerNotHousingOwner # " corresponding to reservation # " # Nat.toText(reservationId)) + }; + if (reservation.status == #Confirmed) { + if(reservation.timestampCheckIn <= now() + MinDaysBeforeCheckinForCancellation * nanoSecPerDay){ + return #Err("The reservation cannot be cancelled less than " # Nat.toText(MinDaysBeforeCheckinForCancellation) # " days"); + }; + let dataTransaction: TransactionParams = { + to = reservation.dataTransaction.from; + amount = reservation.dataTransaction.amount + (reservation.dataTransaction.amount * CancellationFeeCompensateBuyer) / 100; + }; + #Ok({transactionParams = dataTransaction; reservationId = reservationId}) + } else { + let status = switch (reservation.status) { + case (#Pending) { " Pending Reservation" }; + case (#Canceled) { " Canceled " }; + case ( #Ended ) { " Ended " }; + case _ {""} + }; + #Err("Reservation status is " # status) + } + } + }; }; - - public shared ({ caller }) func cancelReservation(reservationId: Nat): async (){ - //TODO: Implementar cancelacion de reservas + public shared ({ caller }) func confirmCancelReservation({reservationId: Nat; txData: DataTransaction}): async {#Ok: Reservation; #Err: Text}{ + let reservation = Map.get(reservationsHistory, nhash, reservationId); + switch reservation { + case null { return #Err(msg.NotReservation)}; + case ( ?reservation ) { + if (reservation.ownerHousing != caller) { + return #Err(msg.CallerNotHousingOwner # " corresponding to reservation # " # Nat.toText(reservationId)) + }; + if (reservation.status != #Confirmed) { + return #Err("Reservation status is not confirmed") + }; + let amountWithFee = reservation.dataTransaction.amount + (reservation.dataTransaction.amount * CancellationFeeCompensateBuyer) / 100; + if (await verifyTransaction(txData, amountWithFee)) { + ignore Map.put(reservationsHistory, nhash, reservationId, {reservation with status = #Canceled}); + let housing = Map.get(housings, nhash, reservation.housingId); + switch housing { + case null { return #Err(msg.NotHousing)}; + case ( ?housing ) { + let reservationsPending = Array.filter(housing.reservationsPending, func x = x != reservationId); + ignore Map.put( + housings, + nhash, + reservation.housingId, + { housing with reservationsPending } + ); + #Ok({reservation with status = #Canceled}) + } + }; + + } else { + #Err(msg.TransactionNotVerified) + } + } + }; }; // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ diff --git a/backend/types.mo b/backend/types.mo index 53a5588..164ad87 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -21,6 +21,7 @@ module { public type User = SignUpData and { verified: Bool; + reservations: List.List; score: Nat; reviewsIssued: List.List; }; @@ -224,15 +225,32 @@ module { phone: Nat; }; + public type ReservationStatus = { + #Pending; + #Confirmed; + #Canceled; + #InProgress; + #Ended; + }; + public type Reservation = ReservationDataInput and { date: Int; + timestampCheckIn: Int; // checkIn: Int; // Int es un supertipo de Nat por lo tanto no hay conflicto con el checkIn que se "hereda" de ReservationDataInput y queda como Int // checkOut: Int; // Idem reservationId: Nat; requester: Principal; - confirmated: Bool; + ownerHousing: Principal; + // confirmated: Bool; + status: ReservationStatus; amount: Nat; - dataTransaction: ?DataTransaction; + dataTransaction: DataTransaction; + }; + + public let NullTrx: DataTransaction = { + from = ""; + to = ""; + amount = 0 }; public type TransactionParams = { diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index a547e2c..dceb16f 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -145,8 +145,7 @@ dfx canister call backend signUpAsHost '(record { firstName="Claudia"; lastName="Gimenez"; email="claugimenez@gmail.com"; - # phone = opt 558789878522; - referralCode = $albertoCode + phone = opt 558789878522; })' # ------------ Usuario Host 4 -------------- @@ -158,7 +157,6 @@ dfx canister call backend signUpAsUser '(record { lastName="Pappa"; email="mariopapa@gmail.com"; phone = opt 542235227692; - referralCode = opt ${albertoCode}; })' # ------------ Usuario Host 5 -------------- @@ -211,7 +209,7 @@ dfx canister call backend requestReservation '(record { dfx identity use 0000TestUser6 dfx canister call backend requestReservation '(record { housingId = 1; - checkIn = 9; + checkIn = 6; checkOut = 11; guest = "Carlos"; email = "carlos@gmil.com"; @@ -223,9 +221,9 @@ dfx identity use 0000TestUser4 dfx canister call backend confirmReservation '(record { reservationId = 1; txData = record { - to = ""; + to = "walletHousingInRequest"; amount = 4_000_000_000; - from = "" + from = "walletUser" } })' @@ -234,9 +232,9 @@ dfx identity use 0000TestUser2 dfx canister call backend confirmReservation '(record { reservationId = 2; txData = record { - to = ""; + to = "walletHousingInRequest"; amount = 4_000_000_000; - from = "" + from = "walletUser" } })' From f9aeb3383d52452d9f4a424f83e5058a6ddb7e4d Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sun, 26 Jan 2025 00:26:12 -0300 Subject: [PATCH 43/57] getReservationByDay --- Anotaciones.md | 8 +++++ backend/main.mo | 59 ++++++++++++++++++++++++------- backend/types.mo | 11 +++++- scripts-sh/deploy-with-content.sh | 29 +++++++-------- 4 files changed, 80 insertions(+), 27 deletions(-) diff --git a/Anotaciones.md b/Anotaciones.md index 78b0ba1..ab3f115 100644 --- a/Anotaciones.md +++ b/Anotaciones.md @@ -53,3 +53,11 @@ En un contexto de MVP todos los usuarios serán inicializados por defecto como v #### Desventajas Para el Usuario: 1. Que el usuario salga de la plataforma con las manos vacias pudiendo salir con un problema solucionado es algo evitable. +#### Notas mentales. Volumen 2 +##### Comisiones por alojmiento +Para las comisiones por alojamiento se puede establecer un porcentage del monto final, el cuál será deducido del monto recibido por el Housing en funcion del precio publicado. +Para disminuir la friccion del usuario final, el cobro de la comisión puede hacerse directamente mediante un transfer_from luego de la recepcion de fondos en la wallet del Housing. +Para poder proceder con ese transfer_from es necesario que la wallet del housing, haya firmado un approve y para eso puede ser conveniente hacerlo durante la creacion del Housing. +En este proceso ya quedaría establecida la wallet del housing y además se habria adquirido la firma del approve en favor de la plataforma. +Actualmente la wallet receptora de fondos de un housing se calcula a partir del Principal ID del owner de ese Housing, de manera tal que es unicamente ese principal quien puede moverlos, lo cual está bien pero para ello hay que implementar una funcion con la que el usuario pueda desde la plataforma hacer transferencias de esos tokens hacia una wallet o hacia algun exchange. +La opcion de conectar una wallet durante la creacion del housing elimina la necesidad de desarrollar un mecanismo de withdraw extra ya que el dueño del Housing puede visualizar su balance o ser notificado automaticamente cada vez que recibe el pago por algun alojamiento. diff --git a/backend/main.mo b/backend/main.mo index aa5e041..657308b 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -59,9 +59,11 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { ////////////////////////////////// Global Configuration Parameters ////////////////////////////////////// stable var CancellationFeeCompensateBuyer: Nat64 = 5; //Percentage added to the buyer's refund + stable var ReservationFee: Nat64 = 10; //Percentage of the total reservation price stable var TimeToPay = 15 * 1_000_000_000; // Tiempo en nanosegundos para confirmar la reserva mediante pago - stable var MinDaysBeforeCheckinForCancellation = 4; // Minimo de dias antes del checkin para cancelar pagando CancellationFeeCompensateBuyer // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos + stable var MinDaysBeforeCheckinForCancellation = 4; // Minimo de dias antes del checkin para cancelar una reserva pagando CancellationFeeCompensateBuyer + //////////////////////////////// Core Data Structures /////////////////////// @@ -284,6 +286,25 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { #Ok } }; + public shared ({ caller }) func settings(): async ?Types.Settings{ + if(not isAdmin(caller)){ return null }; + ?{ + cancellationFeeCompensateBuyer = CancellationFeeCompensateBuyer; + reservationFee = ReservationFee; + timeToPay = TimeToPay; + minDaysBeforeCheckinForCancellation = MinDaysBeforeCheckinForCancellation; + } + }; + + public shared ({ caller }) func updateSettings(config: Types.Settings): async Bool{ + if(not isAdmin(caller)){ return false }; + CancellationFeeCompensateBuyer := config.cancellationFeeCompensateBuyer; + ReservationFee := config.reservationFee; + TimeToPay := config.timeToPay; + MinDaysBeforeCheckinForCancellation := config.minDaysBeforeCheckinForCancellation; + true + + }; /////////////////////////// Admin functions ////////////////////////////////////////////// /////////////////////////////// Verification process ///////////////////////////////////// @@ -672,7 +693,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { // }; - public shared query ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ + public shared ({ caller }) func getCalendarById(id: Nat): async {#Ok: Calendary; #Err: Text}{ switch (Map.get(housings, nhash, id)) { case (?housing) { if (housing.owner != caller ) {return #Err(msg.CallerNotHousingOwner)}; @@ -1069,7 +1090,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { result }; - func calculatePrice(price: ?Types.Price, daysInt: Int): Nat { + func calculatePrice(price: ?Types.Price, daysInt: Int): Nat64 { switch price { case null {assert (false); 0 }; case (?price) { @@ -1081,10 +1102,10 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { ); for(discount in discounts.vals()){ print(debug_show(discount)); - if(days < discount.minimumDays) { return (price.base * days) - (price.base * days * currentDiscount / 100) }; + if(days < discount.minimumDays) { return Nat64.fromNat((price.base * days) - (price.base * days * currentDiscount / 100)) }; currentDiscount := discount.discount; }; - return (price.base * days) - (price.base * days * currentDiscount / 100); + return Nat64.fromNat((price.base * days) - (price.base * days * currentDiscount / 100)); } } }; @@ -1143,7 +1164,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { // Se toma el account por defecto correspondiente al principal del dueño del Host // Se puede establecer otro account proporcionado por el usuario to = blobToText(AccountIdentifier.accountIdentifier(housing.owner, AccountIdentifier.defaultSubaccount())); - amount = Nat64.fromNat(amount); + amount; }; #Ok({transactionParams = dataTransaction; reservationId = lastReservationId}); } else { @@ -1188,7 +1209,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { switch reservation { case null { #Err(msg.NotReservation)}; case ( ?reservation ){ - if (await verifyTransaction(txData, Nat64.fromNat(reservation.amount))){ + if (await verifyTransaction(txData, reservation.amount)){ if(reservation.requester != caller) { return #Err(msg.CallerIsNotRequester # Nat.toText(reservationId)) }; @@ -1199,7 +1220,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { let currentReservation = { reservation with status = #Confirmed; - dataTransaction = txData + dataTransaction = {txData with amount = reservation.amount} }; let calendary = { housing.calendary with @@ -1223,12 +1244,11 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { #Err(msg.TransactionNotVerified) }; } - } - + } }; public shared ({ caller }) func requestToCancelReservation(reservationId: Nat): async TransactionResponse { - + // TODO Actualizar el estado de las reservas en general, antes de proceder let reservation = Map.get(reservationsHistory, nhash, reservationId); switch reservation { case null { return #Err(msg.NotReservation)}; @@ -1286,7 +1306,6 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { #Ok({reservation with status = #Canceled}) } }; - } else { #Err(msg.TransactionNotVerified) } @@ -1294,6 +1313,22 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; }; + public shared ({ caller }) func getReservationByDay(housingId: Nat, day: Nat): async {#Ok: Reservation; #Err: Text}{ + let housing = Map.get(housings, nhash, housingId); + switch housing { + case null { return #Err(msg.NotHousing)}; + case ( ?housing ) { + if (housing.owner != caller) { return #Err(msg.CallerNotHousingOwner)}; + for (reservation in housing.calendary.reservations.vals()) { + if (day >= reservation.checkIn and day < reservation.checkOut) { + return #Ok(reservation) + } + }; + return #Err("No reservation for this day") + } + } + }; + // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ // let housing = Map.get(housings, nhash, housingId); // switch housing { diff --git a/backend/types.mo b/backend/types.mo index 164ad87..35c5298 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -4,6 +4,15 @@ import List "mo:base/List"; module { + ///////////////////////////////// Settings /////////////////////////////////// + + public type Settings = { + cancellationFeeCompensateBuyer: Nat64; + minDaysBeforeCheckinForCancellation: Nat; + timeToPay: Nat; + reservationFee: Nat64; + }; + ///////////////////////////////// Users ///////////////////////////////////// public type SignUpData = { @@ -243,7 +252,7 @@ module { ownerHousing: Principal; // confirmated: Bool; status: ReservationStatus; - amount: Nat; + amount: Nat64; dataTransaction: DataTransaction; }; diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index dceb16f..b611cd1 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -185,7 +185,7 @@ dfx identity use 0000TestUser4 dfx canister call backend requestReservation '(record { housingId = 1; checkIn = 7; - checkOut = 9; + checkOut = 10; guest = "Mario"; email = "mario@gmil.com"; phone = 542236676567 @@ -197,20 +197,30 @@ dfx canister call backend requestReservation '(record { dfx identity use 0000TestUser2 dfx canister call backend requestReservation '(record { housingId = 1; - checkIn = 12; - checkOut = 15; + checkIn = 9; + checkOut = 11; guest = "Lucila"; email = "cucila@gmil.com"; phone = 556578787998 })' +# ------------ Usuario 2 confirma la reserva +dfx identity use 0000TestUser2 +dfx canister call backend confirmReservation '(record { + reservationId = 2; + txData = record { + to = "walletHousingInRequest"; + amount = 4_000_000_000; + from = "walletUser" + } +})' # ------------ Usuario 6 pide reserva para dentro de 9 dias y se queda 2 dfx identity use 0000TestUser6 dfx canister call backend requestReservation '(record { housingId = 1; checkIn = 6; - checkOut = 11; + checkOut = 12; guest = "Carlos"; email = "carlos@gmil.com"; phone = 536657090 @@ -227,16 +237,7 @@ dfx canister call backend confirmReservation '(record { } })' -# ------------ Usuario 2 confirma la reserva -dfx identity use 0000TestUser2 -dfx canister call backend confirmReservation '(record { - reservationId = 2; - txData = record { - to = "walletHousingInRequest"; - amount = 4_000_000_000; - from = "walletUser" - } -})' + # ------------ Usuario 6 confirma la reserva # dfx identity use 0000TestUser6 From 6945b5053935ae0c6e8b1e5538f956f6095a4706 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 30 Jan 2025 23:24:02 -0300 Subject: [PATCH 44/57] Rename private functions --- Anotaciones.md | 2 +- backend/main.mo | 44 +++++++++++-------------------- backend/types.mo | 2 +- scripts-sh/deploy-token.sh | 10 ++++--- scripts-sh/deploy-with-content.sh | 39 +++++++++++++-------------- 5 files changed, 42 insertions(+), 55 deletions(-) diff --git a/Anotaciones.md b/Anotaciones.md index ab3f115..2dca025 100644 --- a/Anotaciones.md +++ b/Anotaciones.md @@ -60,4 +60,4 @@ Para disminuir la friccion del usuario final, el cobro de la comisión puede hac Para poder proceder con ese transfer_from es necesario que la wallet del housing, haya firmado un approve y para eso puede ser conveniente hacerlo durante la creacion del Housing. En este proceso ya quedaría establecida la wallet del housing y además se habria adquirido la firma del approve en favor de la plataforma. Actualmente la wallet receptora de fondos de un housing se calcula a partir del Principal ID del owner de ese Housing, de manera tal que es unicamente ese principal quien puede moverlos, lo cual está bien pero para ello hay que implementar una funcion con la que el usuario pueda desde la plataforma hacer transferencias de esos tokens hacia una wallet o hacia algun exchange. -La opcion de conectar una wallet durante la creacion del housing elimina la necesidad de desarrollar un mecanismo de withdraw extra ya que el dueño del Housing puede visualizar su balance o ser notificado automaticamente cada vez que recibe el pago por algun alojamiento. +La opcion de conectar una wallet durante la creacion del housing elimina la necesidad de desarrollar un mecanismo de withdraw extra ya que el dueño del Housing puede visualizar su balance directamente en su plug wallet o ser notificado automaticamente cada vez que recibe el pago por algun alojamiento. diff --git a/backend/main.mo b/backend/main.mo index 657308b..d74c5cf 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -710,7 +710,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { return switch housing { case null { #Err(msg.NotHousing)}; case (?housing) { - let reservationsPending = cleanPendingVerifications(housingId, housing.reservationsPending); + let reservationsPending = cleanPendingVerifications(housingId); if(not housing.active and housing.owner != caller) { return #Err(msg.InactiveHousing) }; @@ -916,16 +916,16 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { checkOut = updateArray[x].checkOut - daysSinceLastUpdate }} ); - let busy = getUnaviabilityFromReservations(#ReservationType(updateArray)); - let pending = getUnaviabilityFromReservations(#IdsReservation(housing.reservationsPending)); + let busy = extractDaysNotAvailable(#ReservationType(updateArray)); + let pending = extractDaysNotAvailable(#IdsReservation(housing.reservationsPending)); let unavailability = {busy; pending}; let calendary = {dayZero = startOfCurrentDayGMT; reservations = updateArray}; let housingUpdate = {housing with calendary; unavailability}; ignore Map.put(housings, nhash, housingId, housingUpdate); return {calendary; unavailability}; } else { - let busy = getUnaviabilityFromReservations(#ReservationType(housing.calendary.reservations)); - let pending = getUnaviabilityFromReservations(#IdsReservation(housing.reservationsPending)); + let busy = extractDaysNotAvailable(#ReservationType(housing.calendary.reservations)); + let pending = extractDaysNotAvailable(#IdsReservation(housing.reservationsPending)); let unavailability = {busy; pending}; return {calendary = housing.calendary; unavailability } }; @@ -933,7 +933,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; - func getUnaviabilityFromReservations(input: {#ReservationType: [Reservation]; #IdsReservation : [Nat]}): [Int] { + func extractDaysNotAvailable(input: {#ReservationType: [Reservation]; #IdsReservation : [Nat]}): [Int] { let bufferDaysUnavailable = Buffer.fromArray([]); let reservationsArray = switch input { case ( #ReservationType(reservationsArray)){ reservationsArray }; @@ -960,11 +960,12 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; - func cleanPendingVerifications(housingId: Nat, ids: [Nat]): [Nat] { //Remueve las solicitudes no confirmadas y con el tiempo de confirmacion transcurrido; + func cleanPendingVerifications(housingId: Nat): [Nat] { //Remueve las solicitudes no confirmadas y con el tiempo de confirmacion transcurrido; // TODO Esta funcion viola los principios solid ya que se encarga de limpiar las solicitudes pendientes tanto del Map general // como tambien los id de solicitudes de dentro de las estructuras de los housing y ademas devuelve un array con los ids vigentes // correspondientes al HousingID pasado por parametro. Ealuar alguna refactorizacion - var reservationsPendingForId = ids; + // var reservationsPendingForId = ids; + var reservationsPendingForTheProvidedID: [Nat] = []; for ((id, reservation) in Map.toArray(reservationsPendingConfirmation).vals()) { if (now() > reservation.date + TimeToPay) { print("Solicitud de reserva " # Nat.toText(id) # " Eliminada por timeout"); @@ -979,17 +980,13 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { ); print("Id de solicitud " # Nat.toText(id) # " borrado\nreservas pendientes: "); print(debug_show(reservationsPending)); // revisar el filter - if (id == housingId ) { - reservationsPendingForId := reservationsPending; - print("Id de reserva: " # Nat.toText(housingId)); - print(debug_show(reservationsPendingForId)) - }; + if (id == housingId ) { reservationsPendingForTheProvidedID := reservationsPending }; ignore Map.put(housings, nhash, reservation.housingId, {housing with reservationsPending}); } } } }; - reservationsPendingForId + reservationsPendingForTheProvidedID }; func getPendingReservations(ids: [Nat]): {pendings: [Reservation]; pendingReservUpdate: [Nat]}{ @@ -1318,8 +1315,12 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { switch housing { case null { return #Err(msg.NotHousing)}; case ( ?housing ) { + //// Actualización del calendario //// + let { calendary } = updateCalendary(housingId); + // ignore Map.put(housings, nhash, housingId, {housing with calendary; unavailability}); + ///////////////////////////////////// if (housing.owner != caller) { return #Err(msg.CallerNotHousingOwner)}; - for (reservation in housing.calendary.reservations.vals()) { + for (reservation in calendary.reservations.vals()) { if (day >= reservation.checkIn and day < reservation.checkOut) { return #Ok(reservation) } @@ -1328,19 +1329,6 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } } }; - - // public shared query ({ caller }) func getReservations({housingId: Nat}): async {#Ok: [(Nat, Reservation)]; #Err: Text}{ - // let housing = Map.get(housings, nhash, housingId); - // switch housing { - // case null {#Err(msg.NotHousing)}; - // case ( ?housing ) { - // if(housing.owner != caller ){ - // return #Err(msg.CallerNotHousingOwner); - // }; - // #Ok(Map.toArray(housing.reservationRequests)) - // } - // } - // } }; diff --git a/backend/types.mo b/backend/types.mo index 35c5298..06bc1bd 100644 --- a/backend/types.mo +++ b/backend/types.mo @@ -83,7 +83,7 @@ module { reviews: List.List; calendary: Calendary; reservationsPending: [Nat]; - unavailability : {busy: [Int]; pending: [Int]} // busy es la lista de dias ocupados, siendo 0 el dia actual y notConfirmed es la lista de dias correspondientes a todas las solicitudes pendiente + unavailability : {busy: [Int]; pending: [Int]} }; public type HousingTypeInit = { diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh index f2b5021..8587649 100755 --- a/scripts-sh/deploy-token.sh +++ b/scripts-sh/deploy-token.sh @@ -3,7 +3,7 @@ dfx deploy icrc1_ledger_canister --argument '( variant { Init = record { decimals = opt (8 : nat8); - token_symbol = "SNC"; + token_symbol = "TOUR"; transfer_fee = 10_000 : nat; metadata = vec { record { @@ -58,7 +58,7 @@ ICA8L2NpcmNsZT4KPC9zdmc+Cgo=" controller_id = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; }; max_memo_length = null; - token_name = "Smart Network Card"; + token_name = "$TOUR"; feature_flags = opt record { icrc2 = true }; } } @@ -66,11 +66,13 @@ ICA8L2NpcmNsZT4KPC9zdmc+Cgo=" dfx deploy icrc1_index_canister --argument '(opt variant { Init = record { - ledger_id = "bkyz2-fmaaa-aaaaa-qaaaq-cai"; - retrieve_blocks_from_ledger_interval_seconds = null + ledger_id = principal "d7ew5-caaaa-aaaam-aebqa-cai"; + retrieve_blocks_from_ledger_interval_seconds = opt 30 } })' + + # dfx deploy icrc1_ledger_canister --ic --argument '(variant { # Upgrade = opt record { # change_archive_options = null; diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index b611cd1..bce8015 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -191,21 +191,30 @@ dfx canister call backend requestReservation '(record { phone = 542236676567 })' +# ------------ Usuario 4 confirma la reserva +dfx canister call backend confirmReservation '(record { + reservationId = 1; + txData = record { + to = "walletHousingInRequest"; + amount = 4_000_000_000; + from = "walletUser" + } +})' # ------------ Usuario 2 pide reserva para dentro de 12 dias y se queda 3 dfx identity use 0000TestUser2 dfx canister call backend requestReservation '(record { housingId = 1; - checkIn = 9; - checkOut = 11; + checkIn = 12; + checkOut = 14; guest = "Lucila"; email = "cucila@gmil.com"; phone = 556578787998 })' # ------------ Usuario 2 confirma la reserva -dfx identity use 0000TestUser2 + dfx canister call backend confirmReservation '(record { reservationId = 2; txData = record { @@ -220,32 +229,20 @@ dfx identity use 0000TestUser6 dfx canister call backend requestReservation '(record { housingId = 1; checkIn = 6; - checkOut = 12; + checkOut = 7; guest = "Carlos"; email = "carlos@gmil.com"; phone = 536657090 })' -# ------------ Usuario 4 confirma la reserva -dfx identity use 0000TestUser4 + + +# ------------ Usuario 6 confirma la reserva dfx canister call backend confirmReservation '(record { - reservationId = 1; + reservationId = 3; txData = record { to = "walletHousingInRequest"; amount = 4_000_000_000; from = "walletUser" } -})' - - - -# ------------ Usuario 6 confirma la reserva -# dfx identity use 0000TestUser6 -# dfx canister call backend confirmReservation '(record { -# reservationId = 3; -# txData = record { -# to = ""; -# amount = 4_000_000_000; -# from = "" -# } -# })' \ No newline at end of file +})' \ No newline at end of file From aea27e8f92dc2559e6beb0314e9db81e994a7b83 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 5 Feb 2025 21:14:33 -0300 Subject: [PATCH 45/57] icrc1-custom init --- tour/icrc1-custom.mo | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tour/icrc1-custom.mo diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo new file mode 100644 index 0000000..e69de29 From fc529f855fea520f4d4c2bce60da6bc9d714a9d1 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Tue, 11 Feb 2025 00:34:51 -0300 Subject: [PATCH 46/57] Deploy indexer Initial Distribution --- backend/main.mo | 2 +- backend/{mainOld.mo => mainOld.mo.txt} | 0 backend/{typesOld.mo => typesOld.mo.txt} | 0 dfx.json | 5 + interfaces/ic-management-interface.mo | 77 ++++++ mops.toml | 3 +- scripts-sh/deploy-token.sh | 266 +++++++++++++------- tour/icrc1-custom.mo | 296 +++++++++++++++++++++++ tour/indexer.mo | 130 ++++++++++ tour/types.mo | 120 +++++++++ 10 files changed, 815 insertions(+), 84 deletions(-) rename backend/{mainOld.mo => mainOld.mo.txt} (100%) rename backend/{typesOld.mo => typesOld.mo.txt} (100%) create mode 100644 interfaces/ic-management-interface.mo create mode 100644 tour/indexer.mo create mode 100644 tour/types.mo diff --git a/backend/main.mo b/backend/main.mo index d74c5cf..dfd75d4 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -835,7 +835,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; func encodeAmenities(a: Types.Amenities): Nat64 { - var result = 0: Nat64; //el bit mas significativo se ignora en la decodificación + var result = 0: Nat64; // El orden de los elementos de este array tiene que coincidir con el array para la decodificacion let arrayBools = [ a.freeWifi, diff --git a/backend/mainOld.mo b/backend/mainOld.mo.txt similarity index 100% rename from backend/mainOld.mo rename to backend/mainOld.mo.txt diff --git a/backend/typesOld.mo b/backend/typesOld.mo.txt similarity index 100% rename from backend/typesOld.mo rename to backend/typesOld.mo.txt diff --git a/dfx.json b/dfx.json index 34dbfa3..a783d88 100644 --- a/dfx.json +++ b/dfx.json @@ -11,6 +11,11 @@ }, + "tour": { + "type": "motoko", + "main": "tour/icrc1-custom.mo" + }, + "icrc1_ledger_canister": { "type": "custom", "candid": "https://raw.githubusercontent.com/dfinity/ic/233c1ee2ef68c1c8800b8151b2b9f38e17b8440a/rs/ledger_suite/icrc1/ledger/ledger.did", diff --git a/interfaces/ic-management-interface.mo b/interfaces/ic-management-interface.mo new file mode 100644 index 0000000..e6b6ecf --- /dev/null +++ b/interfaces/ic-management-interface.mo @@ -0,0 +1,77 @@ +import Prim "mo:⛔"; + +module { + public type canister_id = Principal; + + public type canister_settings = { + freezing_threshold : ?Nat; + controllers : ?[Principal]; + memory_allocation : ?Nat; + compute_allocation : ?Nat; + }; + + public type definite_canister_settings = { + freezing_threshold : Nat; + controllers : [Principal]; + memory_allocation : Nat; + compute_allocation : Nat; + }; + + public type wasm_module = [Nat8]; + + public type Self = actor { + canister_status : shared { canister_id : canister_id } -> async { + status : { #stopped; #stopping; #running }; + memory_size : Nat; + cycles : Nat; + settings : definite_canister_settings; + idle_cycles_burned_per_day : Nat; + module_hash : ?[Nat8]; + }; + + create_canister : shared { settings : ?canister_settings } -> async { + canister_id : canister_id; + }; + + delete_canister : shared { canister_id : canister_id } -> async (); + + deposit_cycles : shared { canister_id : canister_id } -> async (); + + install_code : shared { + arg : [Nat8]; + wasm_module : wasm_module; + mode : { #reinstall; #upgrade; #install }; + canister_id : canister_id; + } -> async (); + + start_canister : shared { canister_id : canister_id } -> async (); + + stop_canister : shared { canister_id : canister_id } -> async (); + + uninstall_code : shared { canister_id : canister_id } -> async (); + + update_settings : shared { + canister_id : Principal; + settings : canister_settings; + } -> async (); + + }; + + public func addController(canister_id : Principal, controller : Principal) : async () { + let self = actor("aaaaa-aa"): Self; + let currentSetings = (await self.canister_status({canister_id})).settings; + let controllers = Prim.Array_tabulate ( + currentSetings.controllers.size() + 1, + func i = if(i == 0) {controller} else {currentSetings.controllers[i - 1]} + ); + await self.update_settings({ + canister_id; + settings = { + freezing_threshold = ?currentSetings.freezing_threshold; + controllers = ?controllers; + memory_allocation = ?currentSetings.memory_allocation; + compute_allocation = ?currentSetings.compute_allocation + }; + }); + }; +}; diff --git a/mops.toml b/mops.toml index b6a92c9..c64c82e 100644 --- a/mops.toml +++ b/mops.toml @@ -1,4 +1,5 @@ [dependencies] base = "0.11.1" map = "9.0.1" -account-identifier = "1.0.2" \ No newline at end of file +account-identifier = "1.0.2" +icrc2-mo = "0.0.13" \ No newline at end of file diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh index 8587649..688cb08 100755 --- a/scripts-sh/deploy-token.sh +++ b/scripts-sh/deploy-token.sh @@ -1,46 +1,148 @@ -##No funciona la visualizacion del logo en la wallet -dfx deploy icrc1_ledger_canister --argument '( variant { +# dfx deploy icrc1_ledger_canister --argument '( variant { +# Init = record { +# decimals = opt (8 : nat8); +# token_symbol = "TOUR"; +# transfer_fee = 10_000 : nat; +# metadata = vec { +# record { +# "icrc1:logo"; +# variant {Text = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCI+CiAgICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgZmlsbD0iYmx1ZSI+CiAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iciIgZnJvbT0iNDAiIHRvPSIyMCIgZHVyPSIxcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAgICA8L2NpcmNsZT4KPC9zdmc+Cgo=" +# } +# }; +# record { "icrc1:decimals"; variant { Nat = 8 : nat } }; +# record { "icrc1:name"; variant { Text = "$TOUR" } }; +# record { "icrc1:symbol"; variant { Text = "TOUR" } }; +# record { "icrc1:fee"; variant { Nat = 10_000 : nat } }; +# record { "icrc1:max_memo_length"; variant { Nat = 80 : nat } }; +# }; +# minting_account = record { +# owner = principal "y77j5-4vnxl-ywos7-qjtcr-6iopc-i2ql2-iwoem-ehvwk-wruju-fr7ib-mae"; +# subaccount = null; +# }; +# initial_balances = vec { +# record { +# record { +# owner = principal "zpdk5-e6ec5-izoeb-uzhwy-rl2ot-4ag42-im6yv-itg3x-inywa-j3bae-tqe"; +# subaccount = null; +# }; +# 1_000_000_000_000_000 : nat; +# }; +# record { +# record { +# owner = principal "xigzi-mf2wo-xch5n-4dlsf-5tq6n-pke7b-7w2tx-2fv4h-l3yvi-3ycr2-pae"; +# subaccount = null; +# }; +# 500_000_000_000_000: nat; +# } +# }; +# fee_collector_account = opt record { +# owner = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; +# subaccount = null; +# }; +# archive_options = record { +# num_blocks_to_archive = 1_000 : nat64; +# max_transactions_per_response = null; +# trigger_threshold = 2_000 : nat64; +# more_controller_ids = opt vec { +# principal "d2alm-ajpbz-hohks-j3k3y-ulxfm-fegz6-jwopx-d2eu7-3ycil-hnxqa-hae"; +# }; +# max_message_size_bytes = null; +# cycles_for_archive_creation = opt (10_000_000_000_000 : nat64); +# node_max_memory_size_bytes = null; +# controller_id = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; +# }; +# max_memo_length = null; +# token_name = "$TOUR"; +# feature_flags = opt record { icrc2 = true }; +# } +# } +# )' + +# dfx deploy icrc1_index_canister --argument '(opt variant { +# Init = record { +# ledger_id = principal "br5f7-7uaaa-aaaaa-qaaca-cai"; +# retrieve_blocks_from_ledger_interval_seconds = opt 30 +# } +# })' + + + +# Ejemplo de metadata de ckBTC +#( +# vec { +# record { +# "icrc1:logo"; +# variant { +# Text = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQ2IiBoZWlnaHQ9IjE0NiIgdmlld0JveD0iMCAwIDE0NiAxNDYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDYiIGhlaWdodD0iMTQ2IiByeD0iNzMiIGZpbGw9IiMzQjAwQjkiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNi4zODM3IDc3LjIwNTJDMTguNDM0IDEwNS4yMDYgNDAuNzk0IDEyNy41NjYgNjguNzk0OSAxMjkuNjE2VjEzNS45MzlDMzcuMzA4NyAxMzMuODY3IDEyLjEzMyAxMDguNjkxIDEwLjA2MDUgNzcuMjA1MkgxNi4zODM3WiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzExMF81NzIpIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNjguNzY0NiAxNi4zNTM0QzQwLjc2MzggMTguNDAzNiAxOC40MDM3IDQwLjc2MzcgMTYuMzUzNSA2OC43NjQ2TDEwLjAzMDMgNjguNzY0NkMxMi4xMDI3IDM3LjI3ODQgMzcuMjc4NSAxMi4xMDI2IDY4Ljc2NDYgMTAuMDMwMkw2OC43NjQ2IDE2LjM1MzRaIiBmaWxsPSIjMjlBQkUyIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTI5LjYxNiA2OC43MzQzQzEyNy41NjYgNDAuNzMzNSAxMDUuMjA2IDE4LjM3MzQgNzcuMjA1MSAxNi4zMjMyTDc3LjIwNTEgMTBDMTA4LjY5MSAxMi4wNzI0IDEzMy44NjcgMzcuMjQ4MiAxMzUuOTM5IDY4LjczNDNMMTI5LjYxNiA2OC43MzQzWiIgZmlsbD0idXJsKCNwYWludDFfbGluZWFyXzExMF81NzIpIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNzcuMjM1NCAxMjkuNTg2QzEwNS4yMzYgMTI3LjUzNiAxMjcuNTk2IDEwNS4xNzYgMTI5LjY0NyA3Ny4xNzQ5TDEzNS45NyA3Ny4xNzQ5QzEzMy44OTcgMTA4LjY2MSAxMDguNzIyIDEzMy44MzcgNzcuMjM1NCAxMzUuOTA5TDc3LjIzNTQgMTI5LjU4NloiIGZpbGw9IiMyOUFCRTIiLz4KPHBhdGggZD0iTTk5LjgyMTcgNjQuNzI0NUMxMDEuMDE0IDU2Ljc1MzggOTQuOTQ0NyA1Mi40Njg5IDg2LjY0NTUgNDkuNjEwNEw4OS4zMzc2IDM4LjgxM0w4Mi43NjQ1IDM3LjE3NUw4MC4xNDM1IDQ3LjY4NzlDNzguNDE1NSA0Ny4yNTczIDc2LjY0MDYgNDYuODUxMSA3NC44NzcxIDQ2LjQ0ODdMNzcuNTE2OCAzNS44NjY1TDcwLjk0NzQgMzQuMjI4NUw2OC4yNTM0IDQ1LjAyMjJDNjYuODIzIDQ0LjY5NjUgNjUuNDE4OSA0NC4zNzQ2IDY0LjA1NiA0NC4wMzU3TDY0LjA2MzUgNDQuMDAyTDU0Ljk5ODUgNDEuNzM4OEw1My4yNDk5IDQ4Ljc1ODZDNTMuMjQ5OSA0OC43NTg2IDU4LjEyNjkgNDkuODc2MiA1OC4wMjM5IDQ5Ljk0NTRDNjAuNjg2MSA1MC42MSA2MS4xNjcyIDUyLjM3MTUgNjEuMDg2NyA1My43NjhDNTguNjI3IDYzLjYzNDUgNTYuMTcyMSA3My40Nzg4IDUzLjcxMDQgODMuMzQ2N0M1My4zODQ3IDg0LjE1NTQgNTIuNTU5MSA4NS4zNjg0IDUwLjY5ODIgODQuOTA3OUM1MC43NjM3IDg1LjAwMzQgNDUuOTIwNCA4My43MTU1IDQ1LjkyMDQgODMuNzE1NUw0Mi42NTcyIDkxLjIzODlMNTEuMjExMSA5My4zNzFDNTIuODAyNSA5My43Njk3IDU0LjM2MTkgOTQuMTg3MiA1NS44OTcxIDk0LjU4MDNMNTMuMTc2OSAxMDUuNTAxTDU5Ljc0MjYgMTA3LjEzOUw2Mi40MzY2IDk2LjMzNDNDNjQuMjMwMSA5Ni44MjEgNjUuOTcxMiA5Ny4yNzAzIDY3LjY3NDkgOTcuNjkzNEw2NC45OTAyIDEwOC40NDhMNzEuNTYzNCAxMTAuMDg2TDc0LjI4MzYgOTkuMTg1M0M4NS40OTIyIDEwMS4zMDYgOTMuOTIwNyAxMDAuNDUxIDk3LjQ2ODQgOTAuMzE0MUMxMDAuMzI3IDgyLjE1MjQgOTcuMzI2MSA3Ny40NDQ1IDkxLjQyODggNzQuMzc0NUM5NS43MjM2IDczLjM4NDIgOTguOTU4NiA3MC41NTk0IDk5LjgyMTcgNjQuNzI0NVpNODQuODAzMiA4NS43ODIxQzgyLjc3MiA5My45NDM4IDY5LjAyODQgODkuNTMxNiA2NC41NzI3IDg4LjQyNTNMNjguMTgyMiA3My45NTdDNzIuNjM4IDc1LjA2ODkgODYuOTI2MyA3Ny4yNzA0IDg0LjgwMzIgODUuNzgyMVpNODYuODM2NCA2NC42MDY2Qzg0Ljk4MyA3Mi4wMzA3IDczLjU0NDEgNjguMjU4OCA2OS44MzM1IDY3LjMzNEw3My4xMDYgNTQuMjExN0M3Ni44MTY2IDU1LjEzNjQgODguNzY2NiA1Ni44NjIzIDg2LjgzNjQgNjQuNjA2NloiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMTEwXzU3MiIgeDE9IjUzLjQ3MzYiIHkxPSIxMjIuNzkiIHgyPSIxNC4wMzYyIiB5Mj0iODkuNTc4NiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBvZmZzZXQ9IjAuMjEiIHN0b3AtY29sb3I9IiNFRDFFNzkiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTIyNzg1Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8xMTBfNTcyIiB4MT0iMTIwLjY1IiB5MT0iNTUuNjAyMSIgeDI9IjgxLjIxMyIgeTI9IjIyLjM5MTQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agb2Zmc2V0PSIwLjIxIiBzdG9wLWNvbG9yPSIjRjE1QTI0Ii8+CjxzdG9wIG9mZnNldD0iMC42ODQxIiBzdG9wLWNvbG9yPSIjRkJCMDNCIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==" +# }; +# }; +# record { "icrc1:decimals"; variant { Nat = 8 : nat } }; +# record { "icrc1:name"; variant { Text = "ckBTC" } }; +# record { "icrc1:symbol"; variant { Text = "ckBTC" } }; +# record { "icrc1:fee"; variant { Nat = 10 : nat } }; +# record { "icrc1:max_memo_length"; variant { Nat = 80 : nat } }; +# }, +# ) +# dfx deploy tour --argument '( +# record { +# fee = opt variant { Fixed = 10_000 : nat }; +# advanced_settings = null; +# max_memo = opt (32 : nat); +# decimals = 8 : nat8; +# metadata = null; +# minting_account = opt record { +# owner = principal "xigzi-mf2wo-xch5n-4dlsf-5tq6n-pke7b-7w2tx-2fv4h-l3yvi-3ycr2-pae"; +# subaccount = null; +# }; +# logo = null; +# permitted_drift = null; +# name = opt "Triourism"; +# settle_to_accounts = null; +# fee_collector = opt record { +# owner = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; +# subaccount = null; +# }; +# transaction_window = null; +# min_burn_amount = opt (50_000 : nat); +# max_supply = null; +# max_accounts = null; +# symbol = opt "TRI"; +# }, +# record { +# fee = null; +# advanced_settings = null; +# max_allowance = null; +# max_approvals = null; +# max_approvals_per_account = null; +# settle_to_approvals = null; +# } +# )' + +dfx deploy tour --argument '( + variant { Init = record { - decimals = opt (8 : nat8); - token_symbol = "TOUR"; + decimals = 8 : nat8; + token_symbol = "TOUR4"; transfer_fee = 10_000 : nat; metadata = vec { - record { + record { "icrc1:logo"; - variant {Text = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAg -MTAwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCI+CiAgICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUw -IiByPSI0MCIgZmlsbD0iYmx1ZSI+CiAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iciIg -ZnJvbT0iNDAiIHRvPSIyMCIgZHVyPSIxcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAg -ICA8L2NpcmNsZT4KPC9zdmc+Cgo=" - } + variant { + Text = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCI+CiAgICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgZmlsbD0iYmx1ZSI+CiAgICAgICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iciIgZnJvbT0iNDAiIHRvPSIyMCIgZHVyPSIxcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIC8+CiAgICA8L2NpcmNsZT4KPC9zdmc+Cgo=" + }; }; record { "icrc1:decimals"; variant { Nat = 8 : nat } }; - record { "icrc1:name"; variant { Text = "$TOUR" } }; - record { "icrc1:symbol"; variant { Text = "TOUR" } }; + record { "icrc1:name"; variant { Text = "TOUR4" } }; + record { "icrc1:symbol"; variant { Text = "$TOUR4" } }; record { "icrc1:fee"; variant { Nat = 10_000 : nat } }; - record { "icrc1:max_memo_length"; variant { Nat = 80 : nat } }; + record { "icrc1:max_memo_length"; variant { Nat = 32 : nat } }; }; minting_account = record { owner = principal "y77j5-4vnxl-ywos7-qjtcr-6iopc-i2ql2-iwoem-ehvwk-wruju-fr7ib-mae"; subaccount = null; }; - initial_balances = vec { - record { - record { - owner = principal "zpdk5-e6ec5-izoeb-uzhwy-rl2ot-4ag42-im6yv-itg3x-inywa-j3bae-tqe"; - subaccount = null; - }; - 1_000_000_000_000_000 : nat; - }; - record { - record { - owner = principal "xigzi-mf2wo-xch5n-4dlsf-5tq6n-pke7b-7w2tx-2fv4h-l3yvi-3ycr2-pae"; - subaccount = null; - }; - 500_000_000_000_000: nat; - } - }; + initial_balances = vec {}; fee_collector_account = opt record { owner = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; subaccount = null; @@ -57,57 +159,57 @@ ICA8L2NpcmNsZT4KPC9zdmc+Cgo=" node_max_memory_size_bytes = null; controller_id = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; }; - max_memo_length = null; - token_name = "$TOUR"; + max_supply = 100_000_000_000_000 : nat; + max_memo_length = opt (32 : nat); + token_name = "$TOUR4"; feature_flags = opt record { icrc2 = true }; } - } -)' - -dfx deploy icrc1_index_canister --argument '(opt variant { - Init = record { - ledger_id = principal "d7ew5-caaaa-aaaam-aebqa-cai"; - retrieve_blocks_from_ledger_interval_seconds = opt 30 - } -})' - - - -# dfx deploy icrc1_ledger_canister --ic --argument '(variant { -# Upgrade = opt record { -# change_archive_options = null; -# token_symbol = null; -# transfer_fee = null; -# metadata = opt vec { -# record { -# "icrc1:logo"; -# variant { -# Text = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNzYuNDg4NzkybW0iCiAgIGhlaWdodD0iODYuNDU0NjEzbW0iCiAgIHZpZXdCb3g9IjAgMCA3Ni40ODg3OTIgODYuNDU0NjE0IgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmc1IgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjEuMiAoMGEwMGNmNTMzOSwgMjAyMi0wMi0wNCkiCiAgIHNvZGlwb2RpOmRvY25hbWU9ImxvZ28uc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3NyIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSJmYWxzZSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0ibW0iCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjEuMDI5MjE2MiIKICAgICBpbmtzY2FwZTpjeD0iMTI1LjgyMzkxIgogICAgIGlua3NjYXBlOmN5PSIxNDkuNjI4NDMiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxMDM1IgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc3OCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iNTAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjExNjkiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgaW5rc2NhcGU6c25hcC1ncmlkcz0iZmFsc2UiCiAgICAgYm9yZGVybGF5ZXI9ImZhbHNlIgogICAgIHNob3dib3JkZXI9InRydWUiCiAgICAgaW5rc2NhcGU6c2hvd3BhZ2VzaGFkb3c9ImZhbHNlIgogICAgIGZpdC1tYXJnaW4tdG9wPSIwIgogICAgIGZpdC1tYXJnaW4tbGVmdD0iMCIKICAgICBmaXQtbWFyZ2luLXJpZ2h0PSIwIgogICAgIGZpdC1tYXJnaW4tYm90dG9tPSIwIiAvPgogIDxkZWZzCiAgICAgaWQ9ImRlZnMyIiAvPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkNhcGEgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI3Ljk2Nzc5NSwtNDIuOTYxMTQ1KSI+CiAgICA8cGF0aAogICAgICAgc29kaXBvZGk6dHlwZT0ic3RhciIKICAgICAgIHN0eWxlPSJmaWxsOiMwMGZmZmY7ZmlsbC1vcGFjaXR5OjA7c3Ryb2tlOiMzN2Q2ZWE7c3Ryb2tlLXdpZHRoOjE5LjE0MzM7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBpZD0icGF0aDg4NDQiCiAgICAgICBpbmtzY2FwZTpmbGF0c2lkZWQ9InRydWUiCiAgICAgICBzb2RpcG9kaTpzaWRlcz0iNiIKICAgICAgIHNvZGlwb2RpOmN4PSIxMTQuNTM3MTgiCiAgICAgICBzb2RpcG9kaTpjeT0iMzA1Ljk3OTgzIgogICAgICAgc29kaXBvZGk6cjE9Ijc3Ljc2MzQ5NiIKICAgICAgIHNvZGlwb2RpOnIyPSI2Ny4zNDUxNjEiCiAgICAgICBzb2RpcG9kaTphcmcxPSIwLjUxNjcwNDgyIgogICAgICAgc29kaXBvZGk6YXJnMj0iMS4wNDAzMDM2IgogICAgICAgaW5rc2NhcGU6cm91bmRlZD0iMCIKICAgICAgIGlua3NjYXBlOnJhbmRvbWl6ZWQ9IjAiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLjI2NDU4MzMzLDAsMCwwLjI2NDU4MzMzLDE4LjA4NDU3LC01Ljc3ODg2MzYpIgogICAgICAgaW5rc2NhcGU6dHJhbnNmb3JtLWNlbnRlci15PSItMS4wMTk5MzI4ZS0wNiIKICAgICAgIGQ9Im0gMTgyLjE0ODc5LDM0NC4zOTYzOCAtNjcuMDc1NTIsMzkuMzQ1MSAtNjcuNjExNjA4LC0zOC40MTY1NiAtMC41MzYwOTQsLTc3Ljc2MTY1IDY3LjA3NTUxMiwtMzkuMzQ1MDkgNjcuNjExNjEsMzguNDE2NTUgeiIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMzdkNmVhO3N0cm9rZS13aWR0aDo1LjA2NTtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSA2Ni4yNzgxMDMsODUuMzQyNjc2IDg0LjE2NzAwNSw5NS41MDcwNTcgMTAxLjkxNDA3LDg1LjA5NyAxMDEuNzcyMjMsNjQuNTIyNTYzIDgzLjg4MzMyNCw1NC4zNTgxODUiCiAgICAgICBpZD0icGF0aDIxNjE5IiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMzN2Q2ZWE7c3Ryb2tlLXdpZHRoOjUuMDY1O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDQ4LjUzMTAzOSw5NS43NTI3MzQgMC4xNDE4NCwyMC41NzQ0MzYgYyAwLDAgMTcuODg4OTA1LDEwLjE2NDM4IDE3Ljg4ODkwNSwxMC4xNjQzOCBsIDE3Ljc0NzA2NCwtMTAuNDEwMDYiCiAgICAgICBpZD0icGF0aDIxNzM0IiAvPgogICAgPGVsbGlwc2UKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOiMzN2Q2ZWE7c3Ryb2tlLXdpZHRoOjUuMDY1O3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBpZD0icGF0aDI3NDYyIgogICAgICAgY3g9IjkwLjI2NDgwMSIKICAgICAgIGN5PSIxMTIuMzI3MzQiCiAgICAgICByeD0iNS4wNjQyOTUzIgogICAgICAgcnk9IjUuMTU3NjM4MSIgLz4KICAgIDxlbGxpcHNlCiAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTojMzdkNmVhO3N0cm9rZS13aWR0aDo1LjA2NTtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIgogICAgICAgaWQ9InBhdGgyNzQ2Mi02IgogICAgICAgY3g9Ijc3Ljc2MzAwOCIKICAgICAgIGN5PSI1MC42NTEyODMiCiAgICAgICByeD0iNS4wNjQyOTUzIgogICAgICAgcnk9IjUuMTU3NjM4MSIgLz4KICA8L2c+Cjwvc3ZnPgo=" -# }; -# }; -# }; -# change_fee_collector = null; -# max_memo_length = null; -# token_name = null; -# feature_flags = null; -# } -# } -# )' - - -# Ejemplo de metadata de ckBTC -#( -# vec { -# record { -# "icrc1:logo"; -# variant { -# Text = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQ2IiBoZWlnaHQ9IjE0NiIgdmlld0JveD0iMCAwIDE0NiAxNDYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDYiIGhlaWdodD0iMTQ2IiByeD0iNzMiIGZpbGw9IiMzQjAwQjkiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNi4zODM3IDc3LjIwNTJDMTguNDM0IDEwNS4yMDYgNDAuNzk0IDEyNy41NjYgNjguNzk0OSAxMjkuNjE2VjEzNS45MzlDMzcuMzA4NyAxMzMuODY3IDEyLjEzMyAxMDguNjkxIDEwLjA2MDUgNzcuMjA1MkgxNi4zODM3WiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzExMF81NzIpIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNjguNzY0NiAxNi4zNTM0QzQwLjc2MzggMTguNDAzNiAxOC40MDM3IDQwLjc2MzcgMTYuMzUzNSA2OC43NjQ2TDEwLjAzMDMgNjguNzY0NkMxMi4xMDI3IDM3LjI3ODQgMzcuMjc4NSAxMi4xMDI2IDY4Ljc2NDYgMTAuMDMwMkw2OC43NjQ2IDE2LjM1MzRaIiBmaWxsPSIjMjlBQkUyIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTI5LjYxNiA2OC43MzQzQzEyNy41NjYgNDAuNzMzNSAxMDUuMjA2IDE4LjM3MzQgNzcuMjA1MSAxNi4zMjMyTDc3LjIwNTEgMTBDMTA4LjY5MSAxMi4wNzI0IDEzMy44NjcgMzcuMjQ4MiAxMzUuOTM5IDY4LjczNDNMMTI5LjYxNiA2OC43MzQzWiIgZmlsbD0idXJsKCNwYWludDFfbGluZWFyXzExMF81NzIpIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNzcuMjM1NCAxMjkuNTg2QzEwNS4yMzYgMTI3LjUzNiAxMjcuNTk2IDEwNS4xNzYgMTI5LjY0NyA3Ny4xNzQ5TDEzNS45NyA3Ny4xNzQ5QzEzMy44OTcgMTA4LjY2MSAxMDguNzIyIDEzMy44MzcgNzcuMjM1NCAxMzUuOTA5TDc3LjIzNTQgMTI5LjU4NloiIGZpbGw9IiMyOUFCRTIiLz4KPHBhdGggZD0iTTk5LjgyMTcgNjQuNzI0NUMxMDEuMDE0IDU2Ljc1MzggOTQuOTQ0NyA1Mi40Njg5IDg2LjY0NTUgNDkuNjEwNEw4OS4zMzc2IDM4LjgxM0w4Mi43NjQ1IDM3LjE3NUw4MC4xNDM1IDQ3LjY4NzlDNzguNDE1NSA0Ny4yNTczIDc2LjY0MDYgNDYuODUxMSA3NC44NzcxIDQ2LjQ0ODdMNzcuNTE2OCAzNS44NjY1TDcwLjk0NzQgMzQuMjI4NUw2OC4yNTM0IDQ1LjAyMjJDNjYuODIzIDQ0LjY5NjUgNjUuNDE4OSA0NC4zNzQ2IDY0LjA1NiA0NC4wMzU3TDY0LjA2MzUgNDQuMDAyTDU0Ljk5ODUgNDEuNzM4OEw1My4yNDk5IDQ4Ljc1ODZDNTMuMjQ5OSA0OC43NTg2IDU4LjEyNjkgNDkuODc2MiA1OC4wMjM5IDQ5Ljk0NTRDNjAuNjg2MSA1MC42MSA2MS4xNjcyIDUyLjM3MTUgNjEuMDg2NyA1My43NjhDNTguNjI3IDYzLjYzNDUgNTYuMTcyMSA3My40Nzg4IDUzLjcxMDQgODMuMzQ2N0M1My4zODQ3IDg0LjE1NTQgNTIuNTU5MSA4NS4zNjg0IDUwLjY5ODIgODQuOTA3OUM1MC43NjM3IDg1LjAwMzQgNDUuOTIwNCA4My43MTU1IDQ1LjkyMDQgODMuNzE1NUw0Mi42NTcyIDkxLjIzODlMNTEuMjExMSA5My4zNzFDNTIuODAyNSA5My43Njk3IDU0LjM2MTkgOTQuMTg3MiA1NS44OTcxIDk0LjU4MDNMNTMuMTc2OSAxMDUuNTAxTDU5Ljc0MjYgMTA3LjEzOUw2Mi40MzY2IDk2LjMzNDNDNjQuMjMwMSA5Ni44MjEgNjUuOTcxMiA5Ny4yNzAzIDY3LjY3NDkgOTcuNjkzNEw2NC45OTAyIDEwOC40NDhMNzEuNTYzNCAxMTAuMDg2TDc0LjI4MzYgOTkuMTg1M0M4NS40OTIyIDEwMS4zMDYgOTMuOTIwNyAxMDAuNDUxIDk3LjQ2ODQgOTAuMzE0MUMxMDAuMzI3IDgyLjE1MjQgOTcuMzI2MSA3Ny40NDQ1IDkxLjQyODggNzQuMzc0NUM5NS43MjM2IDczLjM4NDIgOTguOTU4NiA3MC41NTk0IDk5LjgyMTcgNjQuNzI0NVpNODQuODAzMiA4NS43ODIxQzgyLjc3MiA5My45NDM4IDY5LjAyODQgODkuNTMxNiA2NC41NzI3IDg4LjQyNTNMNjguMTgyMiA3My45NTdDNzIuNjM4IDc1LjA2ODkgODYuOTI2MyA3Ny4yNzA0IDg0LjgwMzIgODUuNzgyMVpNODYuODM2NCA2NC42MDY2Qzg0Ljk4MyA3Mi4wMzA3IDczLjU0NDEgNjguMjU4OCA2OS44MzM1IDY3LjMzNEw3My4xMDYgNTQuMjExN0M3Ni44MTY2IDU1LjEzNjQgODguNzY2NiA1Ni44NjIzIDg2LjgzNjQgNjQuNjA2NloiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfMTEwXzU3MiIgeDE9IjUzLjQ3MzYiIHkxPSIxMjIuNzkiIHgyPSIxNC4wMzYyIiB5Mj0iODkuNTc4NiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBvZmZzZXQ9IjAuMjEiIHN0b3AtY29sb3I9IiNFRDFFNzkiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTIyNzg1Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQxX2xpbmVhcl8xMTBfNTcyIiB4MT0iMTIwLjY1IiB5MT0iNTUuNjAyMSIgeDI9IjgxLjIxMyIgeTI9IjIyLjM5MTQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agb2Zmc2V0PSIwLjIxIiBzdG9wLWNvbG9yPSIjRjE1QTI0Ii8+CjxzdG9wIG9mZnNldD0iMC42ODQxIiBzdG9wLWNvbG9yPSIjRkJCMDNCIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==" -# }; -# }; -# record { "icrc1:decimals"; variant { Nat = 8 : nat } }; -# record { "icrc1:name"; variant { Text = "ckBTC" } }; -# record { "icrc1:symbol"; variant { Text = "ckBTC" } }; -# record { "icrc1:fee"; variant { Nat = 10 : nat } }; -# record { "icrc1:max_memo_length"; variant { Nat = 80 : nat } }; -# }, -# ) \ No newline at end of file + }, + record { + metadata = vec {}; + min_burn_amount = null; + max_supply = 100_000_000_000_000 : nat; + distribution = opt record { + allocations = vec { + record { + categoryName = "Founders"; + holders = vec { + record { + owner = principal "xyhzp-zrjop-dxido-ehezj-ipucb-todkp-5reb5-oaxey-q2nce-4ss2t-yqe"; + hasVesting = true; + allocatedAmount = 1_000_000_000 : nat; + }; + record { + owner = principal "qcirp-tviue-bxtvo-bniam-zfaku-5yy25-h2dwp-cex5m-ojvxu-5b4zd-fae"; + hasVesting = true; + allocatedAmount = 500_000_000 : nat; + }; + record { + owner = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; + hasVesting = true; + allocatedAmount = 500_000_000 : nat; + }; + }; + }; + record { + categoryName = "Investors"; + holders = vec { + record { + owner = principal "47enf-gshcb-jwiou-py5pm-nwzgv-y65n2-p6rsv-rknt2-wnl2o-2hcrc-4ae"; + hasVesting = true; + allocatedAmount = 4_500_000_000 : nat; + }; + }; + }; + }; + vestingSchemme = variant { + timeBasedVesting = record { + duration = 2_700_000_000_000 : nat; + cliff = null; + releaseRate = 100000000 : nat; + releaseInterval = 1_080_000_000_000 : nat; + } + }; + }; + }, +)' \ No newline at end of file diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index e69de29..d975325 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -0,0 +1,296 @@ +import ExperimentalCycles "mo:base/ExperimentalCycles"; +import Principal "mo:base/Principal"; +import Nat64 "mo:base/Nat64"; +import { now } "mo:base/Time"; +import { print } "mo:base/Debug"; + +import ICRC1 "mo:icrc1-mo/ICRC1"; +import ICRC2 "mo:icrc2-mo/ICRC2"; +import Types "types"; +import Vec "mo:vector"; +import Indexer "indexer"; +import Array "mo:base/Array"; +import Int "mo:base/Int"; +import IC "../interfaces/ic-management-interface"; + + +shared ({ caller = _owner }) actor class CustomToken( + // init_args1 : ICRC1.InitArgs, + // init_args2 : ICRC2.InitArgs, + ledgerArgs : Types.LedgerArgument, + customArgs : { + distribution: ?Types.InitialDistribution; + max_supply : Nat; + metadata : [(Text, Types.MetadataValue)]; + min_burn_amount : ?Nat; + }, +) = this { + + stable var icrc1_args : ?ICRC1.InitArgs = null; + stable var icrc2_args : ?ICRC2.InitArgs = null; + switch ledgerArgs { + case (#Init(initArgs)) { + icrc1_args := ?{ + decimals = initArgs.decimals; + advanced_settings = null; + fee = ?#Fixed(initArgs.transfer_fee); + minting_account = ?initArgs.minting_account; + fee_collector = initArgs.fee_collector_account; + logo = null; + max_accounts = null; + max_memo = initArgs.max_memo_length; + max_supply = ?customArgs.max_supply; + metadata = null; + min_burn_amount = customArgs.min_burn_amount; + name = ?initArgs.token_name; + permitted_drift = null; + settle_to_accounts = null; + symbol = ?initArgs.token_symbol; + transaction_window = null; + }; + icrc2_args := ?{ + advanced_settings = null; + fee = ?#Fixed(initArgs.transfer_fee); + max_allowance = null; + max_approvals = null; + max_approvals_per_account = null; + settle_to_approvals = null; + }; + + }; + + case (#Upgrade(_)) { + assert false; + }; + }; + + stable let icrc1_migration_state = ICRC1.init(ICRC1.initialState(), #v0_1_0(#id), icrc1_args, _owner); + + let #v0_1_0(#data(icrc1_state_current)) = icrc1_migration_state; + + private var _icrc1 : ?ICRC1.ICRC1 = null; + + private func get_icrc1_state() : ICRC1.CurrentState { + return icrc1_state_current; + }; + + private func get_icrc1_environment() : ICRC1.Environment { + { + get_time = null; + get_fee = null; + add_ledger_transaction = null; + can_transfer = null; + }; + }; + + func icrc1() : ICRC1.ICRC1 { + switch (_icrc1) { + case (null) { + let initclass : ICRC1.ICRC1 = ICRC1.ICRC1(?icrc1_migration_state, Principal.fromActor(this), get_icrc1_environment()); + _icrc1 := ?initclass; + initclass; + }; + case (?val) val; + }; + }; + + stable let icrc2_migration_state = ICRC2.init(ICRC2.initialState(), #v0_1_0(#id), icrc2_args, _owner); + + let #v0_1_0(#data(icrc2_state_current)) = icrc2_migration_state; + + private var _icrc2 : ?ICRC2.ICRC2 = null; + + private func get_icrc2_state() : ICRC2.CurrentState { + return icrc2_state_current; + }; + + private func get_icrc2_environment() : ICRC2.Environment { + { + icrc1 = icrc1(); + get_fee = null; + can_approve = null; + can_transfer_from = null; + }; + }; + + func icrc2() : ICRC2.ICRC2 { + switch (_icrc2) { + case (null) { + let initclass : ICRC2.ICRC2 = ICRC2.ICRC2(?icrc2_migration_state, Principal.fromActor(this), get_icrc2_environment()); + _icrc2 := ?initclass; + initclass; + }; + case (?val) val; + }; + }; + + stable var _indexer : ?Indexer.Indexer = null; + + + + func pushTrxToIndexer(trxResult : ICRC1.TransferResult) : async ICRC1.TransferResult { + switch (trxResult) { + case (#Err(_)) {}; + case (#Ok(index)) { + let local_transactions = icrc1().get_local_transactions(); + let _trx = Vec.get(local_transactions, index); + switch (_indexer) { + case (?indexer) { + indexer.on_transaction(_trx, index); + }; + case (null) {}; + }; + }; + }; + trxResult; + }; + + ///// Deploy indexer ///// + + private func deploy_indexer() : async Principal { + ExperimentalCycles.add(2_000_000_000_000); + let indexer = await Indexer.Indexer(); + _indexer := ?indexer; + let indexerCanisterId = Principal.fromActor(indexer); + // Agregamos al _owner como controlador del indexer + await IC.addController(Principal.fromActor(indexer), _owner); + indexerCanisterId; + }; + + ////// Deploy de canister indexer y distribucion inicial /////// + stable var initialized = false; + public shared ({ caller }) func initialize(): async (){ + assert (caller == _owner); + assert (not initialized); + let indexerCanisterId = await deploy_indexer(); + print( "Indexer canister deployed at " # debug_show(indexerCanisterId)); + switch ledgerArgs { + case (#Init(_)) { + switch (customArgs.distribution) { + case (?dist) { + for(distItem in dist.allocations.vals()){ + for ({ allocatedAmount; hasVesting; owner} in distItem.holders.vals()){ + let mintArgs = { + to: Types.Account = {owner ; subaccount = null}; + amount = allocatedAmount; + memo = null; + created_at_time = ?Nat64.fromNat(Int.abs(now())); + }; + let minting_account = get_icrc1_state().minting_account; + ignore await* icrc1().mint(minting_account.owner, mintArgs); + // TODO mapear holder para verificacion de estado de vesting + } + } + }; + case (_) {}; + }; + }; + case (_) { } + }; + initialized := true + }; + + // Custom functions + + public query func indexerCanister(): async ?Principal { + switch (_indexer) { + case null { null }; + case (?indexer) { + ?Principal.fromActor(indexer); + } + } + }; + + public shared query ({ caller }) func balance(subaccount : ?Blob) : async Nat { + icrc1().balance_of({ owner = caller; subaccount }); + }; + + /// Functions for the ICRC1 token standard + public shared query func icrc1_name() : async Text { + icrc1().name(); + }; + + public shared query func icrc1_symbol() : async Text { + icrc1().symbol(); + }; + + public shared query func icrc1_decimals() : async Nat8 { + icrc1().decimals(); + }; + + public shared query func icrc1_fee() : async ICRC1.Balance { + icrc1().fee(); + }; + + public shared query func icrc1_metadata() : async [ICRC1.MetaDatum] { + icrc1().metadata(); + }; + + public shared query func icrc1_total_supply() : async ICRC1.Balance { + icrc1().total_supply(); + }; + + public shared query func icrc1_minting_account() : async ?ICRC1.Account { + ?icrc1().minting_account(); + }; + + public shared query func icrc1_balance_of(args : ICRC1.Account) : async ICRC1.Balance { + icrc1().balance_of(args); + }; + + public shared query func icrc1_supported_standards() : async [ICRC1.SupportedStandard] { + icrc1().supported_standards(); + }; + + public shared ({ caller }) func icrc1_transfer(args : ICRC1.TransferArgs) : async ICRC1.TransferResult { + let trxResult = await* icrc1().transfer(caller, args); + ignore pushTrxToIndexer(trxResult); + trxResult + }; + + public shared ({ caller }) func mint(args : ICRC1.Mint) : async ICRC1.TransferResult { + let trxResult = await* icrc1().mint(caller, args); + await pushTrxToIndexer(trxResult); + }; + + public shared ({ caller }) func burn(args : ICRC1.BurnArgs) : async ICRC1.TransferResult { + let trxResult = await* icrc1().burn(caller, args); + await pushTrxToIndexer(trxResult); + }; + + public query func icrc2_allowance(args : ICRC2.AllowanceArgs) : async ICRC2.Allowance { + return icrc2().allowance(args.spender, args.account, false); + }; + + public shared ({ caller }) func icrc2_approve(args : ICRC2.ApproveArgs) : async ICRC2.ApproveResponse { + await* icrc2().approve(caller, args); + }; + + public shared ({ caller }) func icrc2_transfer_from(args : ICRC2.TransferFromArgs) : async ICRC2.TransferFromResponse { + let trxResult = await* icrc2().transfer_from(caller, args); + switch trxResult { + case (#Err(_)) { trxResult }; + case (#Ok(index)) { await pushTrxToIndexer(#Ok(index)) }; + }; + }; + + public query func getTransactionRange(start : Nat, _end : ?Nat) : async [ICRC1.Transaction] { + let local_transactions = icrc1().get_local_transactions(); + let end = switch _end { + case null { + Vec.size(local_transactions) + }; + case (?val) { + if (val > Vec.size(local_transactions)) { Vec.size(local_transactions) } else { val }; + }; + }; + Array.tabulate(end - start, func i = Vec.get(local_transactions, start + i)); + }; + + // Deposit cycles into this canister. + public shared func deposit_cycles() : async () { + let amount = ExperimentalCycles.available(); + let accepted = ExperimentalCycles.accept(amount); + assert (accepted == amount); + }; +}; diff --git a/tour/indexer.mo b/tour/indexer.mo new file mode 100644 index 0000000..1dbdfb8 --- /dev/null +++ b/tour/indexer.mo @@ -0,0 +1,130 @@ +import Principal "mo:base/Principal"; +import Map "mo:map/Map"; +// import { phash } "mo:map/Map"; +// import TrieMap "mo:base/TrieMap"; + +import ICRC1 "mo:icrc1-mo/ICRC1"; +import ICRC2 "mo:icrc2-mo/ICRC2"; +import { print } "mo:base/Debug"; +import Array "mo:base/Array"; + +/* + get_blocks : shared query GetBlocksRequest -> async GetBlocksResponse; + get_fee_collectors_ranges : shared query () -> async FeeCollectorRanges; + list_subaccounts : shared query ListSubaccountsArgs -> async [SubAccount]; + status : shared query () -> async Status; +*/ + + +shared ({caller = LedgerCanisterId}) actor class Indexer() = this { + + let ledger = actor( Principal.toText(LedgerCanisterId) ): actor { + icrc1_decimals: shared query () -> async Nat8; + icrc1_fee: shared query () -> async Nat; + // icrc1_metadata: shared query () -> async [ICRC1.MetaDatum]; + // icrc1_total_supply: shared query () -> async ICRC1.Balance; + icrc1_minting_account: shared query () -> async ?ICRC1.Account; + icrc1_balance_of: shared query ICRC1.Account -> async ICRC1.Balance; + icrc1_supported_standards: shared query () -> async [ICRC1.SupportedStandard]; + icrc1_transfer: shared ICRC1.TransferArgs -> async ICRC1.TransferResult; + mint: shared ICRC1.Mint -> async ICRC1.TransferResult; + burn: shared ICRC1.BurnArgs -> async ICRC1.TransferResult; + icrc2_allowance: query ICRC2.AllowanceArgs -> async ICRC2.Allowance; + icrc2_approve: shared ICRC2.ApproveArgs -> async ICRC2.ApproveResponse; + icrc2_transfer_from: shared ICRC2.TransferFromArgs -> async ICRC2.TransferFromResponse; + getTransactionRange: query (Nat, ?Nat) -> async [ICRC1.Transaction]; + }; + + type TokenTransferredListener = ICRC1.TokenTransferredListener; + type Account = {owner: Principal; subaccount: ?Blob}; + + stable let accountsTransactions = Map.new(); + stable var transactions: [ICRC1.Transaction] = []; + + private func pull_missing_transactions(): async () { + let _transactionsPulled: [ICRC1.Transaction] = await ledger.getTransactionRange(transactions.size(), null); + transactions := Array.tabulate( + transactions.size() + _transactionsPulled.size(), + func i = if (i < transactions.size()) { transactions[i] } else { _transactionsPulled[i - transactions.size()] } + ); + for (trx in _transactionsPulled.vals()) { + index_transaction(trx) + }; + }; + + private func index_transaction(trx: ICRC1.Transaction) { + let accounts = switch (trx.kind) { + case "TRANSFER" { + switch (trx.transfer) { + case null { [] }; + case (?data) { [data.from, data.to] } + }; + }; + case "MINT" { + + switch (trx.mint) { + case null { [] }; + case (?data) { [data.to] } + } + }; + case "BURN" { + switch (trx.burn) { + case null { [] }; + case (?data) { [data.from] } + } + }; + case _ { [] } + }; + for (account in accounts.vals() ){ + let trxsPrevious = Map.get(accountsTransactions, ICRC1.ahash, account); + switch (trxsPrevious) { + case null { + ignore Map.put(accountsTransactions, ICRC1.ahash, account, [trx]) + }; + case (?trxsPrevious) { + let updatedTrxs = Array.tabulate( + trxsPrevious.size() + 1, + func i = if (i == 0) { trx } else { trxsPrevious[i - 1] } + ); + ignore Map.put(accountsTransactions, ICRC1.ahash, account, updatedTrxs) + }; + }; + }; + }; + + public shared ({ caller }) func on_transaction(trx: ICRC1.Transaction, index: Nat) : () { + assert( caller == LedgerCanisterId); + assert( index >= transactions.size()); + if (index == transactions.size()) { + index_transaction(trx) + } else { + await pull_missing_transactions(); + }; + }; + + public query func get_account_transactions(account: Account): async [ICRC1.Transaction] { + switch (Map.get(accountsTransactions, ICRC1.ahash, account)){ + case null { [] }; + case (?trxs) { trxs }; + }; + }; + + public func icrc1_balance_of(a: ICRC1.Account): async ICRC1.Balance{ + await ledger.icrc1_balance_of(a) + }; + + // public query func get_account_transactions({max_results : Nat; start : ?Nat; account : Account}): async GetTransactionsResult{ + + // }; + + public query func ledger_id(): async Principal{ + LedgerCanisterId + }; + + + + + +} + + diff --git a/tour/types.mo b/tour/types.mo new file mode 100644 index 0000000..38e6a90 --- /dev/null +++ b/tour/types.mo @@ -0,0 +1,120 @@ +module { + + // public type CustomArgs = { + + // }; + + public type InitialDistribution = { + allocations : [{ categoryName : Text; holders : [InitialHolder] }]; + vestingSchemme: { + #timeBasedVesting: TimeBasedVesting; + #mintBasedVesting: MintBasedVesting; + } + }; + + public type InitialHolder = { + owner : Principal; + allocatedAmount : Nat; + hasVesting : Bool; + }; + + public type TimeBasedVesting = { + cliff : ?Nat; // Comienzo del periodo de vesting. Si es null se toma la fecha del deploy + duration : Nat; // Duración del periodo de vesting desde el cliff + releaseRate : Nat; // Cantidad de tokens a liberar por periodo luego del periodo de vesting + releaseInterval : Nat; // Intervalo de tiempo en segundos entre cada liberación + }; + + ///// Revisar regla //////////////////// + public type MintBasedVesting = { + triggers : [{ totalSupply : Nat; releaseAmount : Nat }]; + withdrawalRatio : Nat; //relacion entre el totalSupply actual y el maximo que se puede retirar en un solo trigger + }; + public func mintBasedVestingValidate(mintBasedVesting : MintBasedVesting) : Bool { + var lastTrigger = { totalSupply = 0; releaseAmount = 0 }; + let ratio = if (mintBasedVesting.withdrawalRatio < 500) { + 500; + } else { + mintBasedVesting.withdrawalRatio; + }; + for (t in mintBasedVesting.triggers.vals()) { + if ( + t.totalSupply <= lastTrigger.totalSupply or + t.releaseAmount <= lastTrigger.releaseAmount or + t.totalSupply < t.releaseAmount * ratio + ) { + return false; + }; + lastTrigger := t; + }; + true; + }; + //////////////////////////////////////// + + public type VestingRule = { + timeBasedVesting : ?TimeBasedVesting; + mintBasedVesting : ?MintBasedVesting; + }; + + public type Account = { owner : Principal; subaccount : ?Blob }; + + public type ArchiveOptions = { + num_blocks_to_archive : Nat64; + max_transactions_per_response : ?Nat64; + trigger_threshold : Nat64; + more_controller_ids : ?[Principal]; + max_message_size_bytes : ?Nat64; + cycles_for_archive_creation : ?Nat64; + node_max_memory_size_bytes : ?Nat64; + controller_id : Principal; + }; + public type FeatureFlags = { icrc2 : Bool }; + + public type LedgerArgument = { #Upgrade : ?UpgradeArgs; #Init : InitArgs }; + + public type ChangeArchiveOptions = { + num_blocks_to_archive : ?Nat64; + max_transactions_per_response : ?Nat64; + trigger_threshold : ?Nat64; + more_controller_ids : ?[Principal]; + max_message_size_bytes : ?Nat64; + cycles_for_archive_creation : ?Nat64; + node_max_memory_size_bytes : ?Nat64; + controller_id : ?Principal; + }; + + public type ChangeFeeCollector = { #SetTo : Account; #Unset }; + + public type UpgradeArgs = { + change_archive_options : ?ChangeArchiveOptions; + token_symbol : ?Text; + transfer_fee : ?Nat; + metadata : ?[(Text, MetadataValue)]; + change_fee_collector : ?ChangeFeeCollector; + max_memo_length : ?Nat; + token_name : ?Text; + feature_flags : ?FeatureFlags; + }; + + public type InitArgs = { + decimals : Nat8; + token_symbol : Text; + max_supply : Nat; + transfer_fee : Nat; + metadata : [(Text, MetadataValue)]; + minting_account : Account; + initial_balances : [(Account, Nat)]; + fee_collector_account : ?Account; + archive_options : ArchiveOptions; + max_memo_length : ?Nat; + token_name : Text; + feature_flags : ?FeatureFlags; + }; + + public type MetadataValue = { + #Int : Int; + #Nat : Nat; + #Blob : Blob; + #Text : Text; + }; +}; From a23439729c1004f1693592dfdf6faedcc41f9469 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Tue, 11 Feb 2025 01:26:17 -0300 Subject: [PATCH 47/57] vestingVerification in progress --- tour/icrc1-custom.mo | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index d975325..0284059 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -3,6 +3,8 @@ import Principal "mo:base/Principal"; import Nat64 "mo:base/Nat64"; import { now } "mo:base/Time"; import { print } "mo:base/Debug"; +import Map "mo:map/Map"; +import { phash } "mo:map/Map"; import ICRC1 "mo:icrc1-mo/ICRC1"; import ICRC2 "mo:icrc2-mo/ICRC2"; @@ -178,7 +180,14 @@ shared ({ caller = _owner }) actor class CustomToken( }; let minting_account = get_icrc1_state().minting_account; ignore await* icrc1().mint(minting_account.owner, mintArgs); - // TODO mapear holder para verificacion de estado de vesting + + // Mapeo holder/amount para verificacion de estado de vesting + + let totalAmount = switch(Map.get(holdersVesting, phash, owner)){ + case null { allocatedAmount }; + case ( ?previousAmount ) {allocatedAmount + previousAmount } // Caso de que un mismo principal se encuentre en dos categorias distintas + }; + ignore Map.put(holdersVesting, phash, owner, totalAmount); } } }; @@ -190,6 +199,30 @@ shared ({ caller = _owner }) actor class CustomToken( initialized := true }; + //// vesting validations //// + + stable let holdersVesting = Map.new(); + + func vestingVerification(caller: Principal, trx: ICRC1.TransferArgs): {#Ok; #Err: ICRC1.TransferError} { + // TODO ver esquema y status actual del vesting + + let balance = icrc1().balance_of({ owner = caller; subaccount = null }); + let blocked_amount = switch ( Map.get(holdersVesting, phash, caller) ){ + case null { 0 }; + case ( ?value ) { value } + }; + if(balance >= blocked_amount + trx.amount + icrc1().fee()) { + #Ok + } else { + #Err(#VestingRestriction({ + blocked_amount; + available_amount = balance - blocked_amount + })) + } + }; + + + // Custom functions public query func indexerCanister(): async ?Principal { @@ -243,6 +276,10 @@ shared ({ caller = _owner }) actor class CustomToken( }; public shared ({ caller }) func icrc1_transfer(args : ICRC1.TransferArgs) : async ICRC1.TransferResult { + switch(vestingVerification(caller, args)) { + case ( #Err(e) ) { return #Err(e) }; + case _ { } + }; let trxResult = await* icrc1().transfer(caller, args); ignore pushTrxToIndexer(trxResult); trxResult From 011d5a863ccb1090bc9999f1d73db1cb9de972fe Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Tue, 11 Feb 2025 01:41:33 -0300 Subject: [PATCH 48/57] Fix hasVesting in distribution --- tour/icrc1-custom.mo | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index 0284059..2b52d62 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -181,13 +181,14 @@ shared ({ caller = _owner }) actor class CustomToken( let minting_account = get_icrc1_state().minting_account; ignore await* icrc1().mint(minting_account.owner, mintArgs); - // Mapeo holder/amount para verificacion de estado de vesting - - let totalAmount = switch(Map.get(holdersVesting, phash, owner)){ - case null { allocatedAmount }; - case ( ?previousAmount ) {allocatedAmount + previousAmount } // Caso de que un mismo principal se encuentre en dos categorias distintas - }; - ignore Map.put(holdersVesting, phash, owner, totalAmount); + // Mapeo holder/amount para permitir o denegar transacciones durante periodo de vesting + if ( hasVesting ){ + let totalAmount = switch(Map.get(holdersVesting, phash, owner)){ + case null { allocatedAmount }; + case ( ?previousAmount ) {allocatedAmount + previousAmount } // Caso de que un mismo principal se encuentre en dos categorias distintas + }; + ignore Map.put(holdersVesting, phash, owner, totalAmount); + } } } }; @@ -205,7 +206,7 @@ shared ({ caller = _owner }) actor class CustomToken( func vestingVerification(caller: Principal, trx: ICRC1.TransferArgs): {#Ok; #Err: ICRC1.TransferError} { // TODO ver esquema y status actual del vesting - + let balance = icrc1().balance_of({ owner = caller; subaccount = null }); let blocked_amount = switch ( Map.get(holdersVesting, phash, caller) ){ case null { 0 }; @@ -221,8 +222,6 @@ shared ({ caller = _owner }) actor class CustomToken( } }; - - // Custom functions public query func indexerCanister(): async ?Principal { From 969f44adaeda952391c25542adb02e8444171373 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Tue, 11 Feb 2025 02:51:46 -0300 Subject: [PATCH 49/57] vestingVerification for transferFrom --- tour/icrc1-custom.mo | 8 ++++++-- tour/types.mo | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index 2b52d62..d2a75c3 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -204,7 +204,7 @@ shared ({ caller = _owner }) actor class CustomToken( stable let holdersVesting = Map.new(); - func vestingVerification(caller: Principal, trx: ICRC1.TransferArgs): {#Ok; #Err: ICRC1.TransferError} { + func vestingVerification(caller: Principal, trx: ICRC1.TransferArgs): {#Ok; #Err: Types.TransferError} { // TODO ver esquema y status actual del vesting let balance = icrc1().balance_of({ owner = caller; subaccount = null }); @@ -274,7 +274,7 @@ shared ({ caller = _owner }) actor class CustomToken( icrc1().supported_standards(); }; - public shared ({ caller }) func icrc1_transfer(args : ICRC1.TransferArgs) : async ICRC1.TransferResult { + public shared ({ caller }) func icrc1_transfer(args : ICRC1.TransferArgs) : async Types.TransferResult { switch(vestingVerification(caller, args)) { case ( #Err(e) ) { return #Err(e) }; case _ { } @@ -303,6 +303,10 @@ shared ({ caller = _owner }) actor class CustomToken( }; public shared ({ caller }) func icrc2_transfer_from(args : ICRC2.TransferFromArgs) : async ICRC2.TransferFromResponse { + switch(vestingVerification(args.from.owner, {args with from_subaccount = args.from.subaccount}: ICRC1.TransferArgs)) { + case ( #Err(e) ) { return #Err(e) }; + case _ { } + }; let trxResult = await* icrc2().transfer_from(caller, args); switch trxResult { case (#Err(_)) { trxResult }; diff --git a/tour/types.mo b/tour/types.mo index 38e6a90..8a8d76d 100644 --- a/tour/types.mo +++ b/tour/types.mo @@ -1,3 +1,5 @@ +import ICRC1 "mo:icrc1-mo/ICRC1"; + module { // public type CustomArgs = { @@ -49,6 +51,20 @@ module { }; true; }; + + // Custom Errors + + public type TxIndex = Nat; + public type TransferResult = { + #Ok : TxIndex; + #Err : TransferError; + }; + public type TransferError = ICRC1.TransferError or { + #VestingRestriction : { + blocked_amount : Nat; + available_amount : Nat; + }; + }; //////////////////////////////////////// public type VestingRule = { From 91b3f6b2ada2a42d09334683c8a275ad244ce582 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 12 Feb 2025 15:56:33 -0300 Subject: [PATCH 50/57] Extract distribution function from initialize function; add categoryName to map vestingHolders --- package.json | 1 + scripts-sh/deploy-token.sh | 135 ++++++++++++++++------------ scripts-sh/test-token.sh | 180 +++++++++++++++++++++++++++++++++++++ tour/icrc1-custom.mo | 149 +++++++++++++++++------------- tour/tokenomic.mo | 67 ++++++++++++++ tour/types.mo | 68 +++----------- 6 files changed, 426 insertions(+), 174 deletions(-) create mode 100755 scripts-sh/test-token.sh create mode 100644 tour/tokenomic.mo diff --git a/package.json b/package.json index 7d4b7f4..0754a81 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "scripts": { "deploy-with-content": "chmod +x scripts-sh/deploy-with-content.sh; ./scripts-sh/deploy-with-content.sh", "deploy-token": "chmod +x scripts-sh/deploy-token.sh; ./scripts-sh/deploy-token.sh", + "test-token": "chmod +x scripts-sh/test-token.sh; ./scripts-sh/test-token.sh", "build": "turbo run build", "preclean": "turbo run clean", "clean": "rm -rf .dfx && rm -rf .turbo && rm -rf node_modules & rm -rf src/declarations" diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh index 688cb08..c761cd1 100755 --- a/scripts-sh/deploy-token.sh +++ b/scripts-sh/deploy-token.sh @@ -67,7 +67,7 @@ # })' - +######################################################################## # Ejemplo de metadata de ckBTC #( # vec { @@ -84,40 +84,47 @@ # record { "icrc1:max_memo_length"; variant { Nat = 80 : nat } }; # }, # ) -# dfx deploy tour --argument '( -# record { -# fee = opt variant { Fixed = 10_000 : nat }; -# advanced_settings = null; -# max_memo = opt (32 : nat); -# decimals = 8 : nat8; -# metadata = null; -# minting_account = opt record { -# owner = principal "xigzi-mf2wo-xch5n-4dlsf-5tq6n-pke7b-7w2tx-2fv4h-l3yvi-3ycr2-pae"; -# subaccount = null; -# }; -# logo = null; -# permitted_drift = null; -# name = opt "Triourism"; -# settle_to_accounts = null; -# fee_collector = opt record { -# owner = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; -# subaccount = null; -# }; -# transaction_window = null; -# min_burn_amount = opt (50_000 : nat); -# max_supply = null; -# max_accounts = null; -# symbol = opt "TRI"; -# }, -# record { -# fee = null; -# advanced_settings = null; -# max_allowance = null; -# max_approvals = null; -# max_approvals_per_account = null; -# settle_to_approvals = null; -# } -# )' +###################################################################### +dfx identity new 0000InvNonVesting +dfx identity use 0000InvNonVesting +export InvNonVesting=$(dfx identity get-principal) + +dfx identity new 0000InvVesting +dfx identity use 0000InvVesting +export InvVesting=$(dfx identity get-principal) + +dfx identity new 0000Founder01 +dfx identity use 0000Founder01 +export Founder01=$(dfx identity get-principal) + +dfx identity new 0000Founder02 +dfx identity use 0000Founder02 +export Founder02=$(dfx identity get-principal) + +dfx identity new 0000Founder03 +dfx identity use 0000Founder03 +export Founder03=$(dfx identity get-principal) + +dfx identity new 0000Minter +dfx identity use 0000Minter +export Minter=$(dfx identity get-principal) + +dfx identity new 0000FeeCollector +dfx identity use 0000FeeCollector +export FeeCollector=$(dfx identity get-principal) + +dfx identity new 0000Controller +dfx identity use 0000Controller +export Controller=$(dfx identity get-principal) + + + + +dfx identity new 0000Deployer +dfx identity use 0000Deployer +export deployer=$(dfx identity get-principal) + + dfx deploy tour --argument '( variant { @@ -139,25 +146,23 @@ dfx deploy tour --argument '( record { "icrc1:max_memo_length"; variant { Nat = 32 : nat } }; }; minting_account = record { - owner = principal "y77j5-4vnxl-ywos7-qjtcr-6iopc-i2ql2-iwoem-ehvwk-wruju-fr7ib-mae"; + owner = principal "'$Minter'"; subaccount = null; }; initial_balances = vec {}; fee_collector_account = opt record { - owner = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; + owner = principal "'$FeeCollector'"; subaccount = null; }; archive_options = record { num_blocks_to_archive = 1_000 : nat64; max_transactions_per_response = null; trigger_threshold = 2_000 : nat64; - more_controller_ids = opt vec { - principal "d2alm-ajpbz-hohks-j3k3y-ulxfm-fegz6-jwopx-d2eu7-3ycil-hnxqa-hae"; - }; + more_controller_ids = null; max_message_size_bytes = null; cycles_for_archive_creation = opt (10_000_000_000_000 : nat64); node_max_memory_size_bytes = null; - controller_id = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; + controller_id = principal "'$Controller'"; }; max_supply = 100_000_000_000_000 : nat; max_memo_length = opt (32 : nat); @@ -175,41 +180,55 @@ dfx deploy tour --argument '( categoryName = "Founders"; holders = vec { record { - owner = principal "xyhzp-zrjop-dxido-ehezj-ipucb-todkp-5reb5-oaxey-q2nce-4ss2t-yqe"; + owner = principal "'$Founder01'"; hasVesting = true; - allocatedAmount = 1_000_000_000 : nat; + allocatedAmount = 1_234_000_000 : nat; }; record { - owner = principal "qcirp-tviue-bxtvo-bniam-zfaku-5yy25-h2dwp-cex5m-ojvxu-5b4zd-fae"; + owner = principal "'$Founder02'"; hasVesting = true; - allocatedAmount = 500_000_000 : nat; + allocatedAmount = 567_800_000 : nat; }; record { - owner = principal "epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae"; + owner = principal "'$Founder03'"; hasVesting = true; - allocatedAmount = 500_000_000 : nat; + allocatedAmount = 901_200_000 : nat; }; }; + vestingSchemme = variant { + timeBasedVesting = record { + cliff = opt 1739329277; + intervalDuration = 30; + intervalQty = 4 : nat8; + } + } }; record { categoryName = "Investors"; holders = vec { record { - owner = principal "47enf-gshcb-jwiou-py5pm-nwzgv-y65n2-p6rsv-rknt2-wnl2o-2hcrc-4ae"; + owner = principal "'$InvVesting'"; hasVesting = true; - allocatedAmount = 4_500_000_000 : nat; + allocatedAmount = 4_000_000_000 : nat; }; + record { + owner = principal "'$InvNonVesting'"; + hasVesting = false; + allocatedAmount = 1_000_000_000 : nat; + }; + }; + vestingSchemme = variant { + timeBasedVesting = record { + cliff = opt 1739332877; + intervalDuration = 30; + intervalQty = 3 : nat8; + } + } }; }; - vestingSchemme = variant { - timeBasedVesting = record { - duration = 2_700_000_000_000 : nat; - cliff = null; - releaseRate = 100000000 : nat; - releaseInterval = 1_080_000_000_000 : nat; - } - }; }; }, -)' \ No newline at end of file +)' + +dfx canister call tour initialize diff --git a/scripts-sh/test-token.sh b/scripts-sh/test-token.sh new file mode 100755 index 0000000..862acae --- /dev/null +++ b/scripts-sh/test-token.sh @@ -0,0 +1,180 @@ +#!/bin/bash + +# Función para ejecutar pruebas con colores +run_test() { + DESCRIPTION="$1" + EXPECTED="$2" + COMMAND="$3" + + echo "-------------- $DESCRIPTION --------------" + echo "Valor esperado: $EXPECTED" + + # Ejecutar el comando y capturar la salida + RESULT=$(eval "$COMMAND") + + echo "Valor obtenido: $RESULT" + + # Verificar si el resultado contiene el valor esperado + if echo "$RESULT" | grep -q "$EXPECTED"; then + echo -e "\e[32m✔ Test exitoso\e[0m" # Verde + else + echo -e "\e[31m✘ Test fallido\e[0m" # Rojo + fi + echo "" +} + +# Configuración de identidades +dfx identity use 0000InvNonVesting +export InvNonVesting=$(dfx identity get-principal) + +dfx identity use 0000InvVesting +export InvVesting=$(dfx identity get-principal) + +dfx identity use 0000Founder01 +export Founder01=$(dfx identity get-principal) + +dfx identity use 0000Founder02 +export Founder02=$(dfx identity get-principal) + +dfx identity use 0000Founder03 +export Founder03=$(dfx identity get-principal) + +dfx identity use 0000Minter +export Minter=$(dfx identity get-principal) + +dfx identity use 0000FeeCollector +export FeeCollector=$(dfx identity get-principal) + +dfx identity use 0000Controller +export Controller=$(dfx identity get-principal) + +# Test balance luego de distribución +run_test "Test total_supply luego de distribución" \ + "(7_703_000_000 : nat)" \ + "dfx canister call tour icrc1_total_supply" + +# Test usuario con vesting intentando transferir tokens +dfx identity use 0000InvVesting +run_test "Test usuario con vesting quiere transferir 500_000_000 tokens" \ + "( + variant { + Err = variant { + VestingRestriction = record { + blocked_amount = 4_000_000_000 : nat; + available_amount = 0 : nat; + } + } + }, + )" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"smdkv-lcdvc-ukvh6-dhgfm-fej7o-76jhm-zbpoj-lq3jh-lfcn7-62pow-2qe\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 500_000_000 : nat; + }, + )'" + +# Test usuario sin vesting realizando transferencia +dfx identity use 0000InvNonVesting +run_test "Test usuario sin vesting quiere transferir 500_000_000 tokens a founder1" \ + "(variant { Ok = 5 : nat })" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder01\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 500_000_000 : nat; + }, + )'" + +# Test mint y verificación de balance +dfx identity use 0000Minter +run_test "El minter hace un mint de 2_000_000_000 tokens en favor de founder1" \ + "(variant { Ok = 6 : nat })" \ + "dfx canister call tour mint '( + record { + to = record { owner = principal \"$Founder01\"; subaccount = null; }; + memo = null; + created_at_time = null; + amount = 2_000_000_000 : nat; + }, + )'" +run_test "Check total_supply luego del mint" \ + "(9_703_000_000 : nat)" \ + "dfx canister call tour icrc1_total_supply" +run_test "Verificación de balance de founder1" \ + "(3_734_000_000 : nat)" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$Founder01\" }, + )'" + +run_test "Verificación de balance de inversor sin vesting luego de su transferencia hacia founder1" \ + "(499_990_000 : nat)" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$InvNonVesting\" }, + )'" + +# Founder 1 intenta transferir más de lo permitido +dfx identity use 0000Founder01 +run_test "Founder 1 quiere transferir 3_000_000_000 a founder 2 (bloqueado por vesting)" \ + "( + variant { + Err = variant { + VestingRestriction = record { + blocked_amount = 1_234_000_000 : nat; + available_amount = 2_500_000_000 : nat; + } + } + }, + )" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder02\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 3_000_000_000 : nat; + }, + )'" + +run_test "Founder 1 quiere transferir 2_500_000_000 a founder 2 (bloqueado por vesting)" \ + "( + variant { + Err = variant { + VestingRestriction = record { + blocked_amount = 1_234_000_000 : nat; + available_amount = 2_500_000_000 : nat; + } + } + }, + )" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder02\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 2_500_000_000 : nat; + }, + )'" + +run_test "Founder 1 quiere transferir 2_499_990_000 a founder 2 (exitosa)" \ + "(variant { Ok = 7 : nat })" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder02\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 2_499_990_000 : nat; + }, + )'" + diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index d2a75c3..d8cbb3a 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -5,23 +5,23 @@ import { now } "mo:base/Time"; import { print } "mo:base/Debug"; import Map "mo:map/Map"; import { phash } "mo:map/Map"; - + import ICRC1 "mo:icrc1-mo/ICRC1"; import ICRC2 "mo:icrc2-mo/ICRC2"; import Types "types"; +import Tokenomic "tokenomic"; import Vec "mo:vector"; import Indexer "indexer"; import Array "mo:base/Array"; import Int "mo:base/Int"; import IC "../interfaces/ic-management-interface"; - shared ({ caller = _owner }) actor class CustomToken( // init_args1 : ICRC1.InitArgs, // init_args2 : ICRC2.InitArgs, ledgerArgs : Types.LedgerArgument, customArgs : { - distribution: ?Types.InitialDistribution; + distribution : ?Tokenomic.InitialDistribution; max_supply : Nat; metadata : [(Text, Types.MetadataValue)]; min_burn_amount : ?Nat; @@ -58,7 +58,7 @@ shared ({ caller = _owner }) actor class CustomToken( max_approvals_per_account = null; settle_to_approvals = null; }; - + }; case (#Upgrade(_)) { @@ -128,8 +128,6 @@ shared ({ caller = _owner }) actor class CustomToken( stable var _indexer : ?Indexer.Indexer = null; - - func pushTrxToIndexer(trxResult : ICRC1.TransferResult) : async ICRC1.TransferResult { switch (trxResult) { case (#Err(_)) {}; @@ -150,6 +148,8 @@ shared ({ caller = _owner }) actor class CustomToken( ///// Deploy indexer ///// private func deploy_indexer() : async Principal { + switch _indexer { + case null { ExperimentalCycles.add(2_000_000_000_000); let indexer = await Indexer.Indexer(); _indexer := ?indexer; @@ -157,80 +157,101 @@ shared ({ caller = _owner }) actor class CustomToken( // Agregamos al _owner como controlador del indexer await IC.addController(Principal.fromActor(indexer), _owner); indexerCanisterId; + }; + case (?pid) { Principal.fromActor(pid) }; + }; + }; + + func distribution(allocations : [Tokenomic.Allocation]): async {#Ok; #Err: Text} { + if (distributionComplete) { return #Err("Distribution is already complete")}; + for (distItem in allocations.vals()) { + for ({ allocatedAmount; hasVesting; owner } in distItem.holders.vals()) { + let mintArgs = { + to : Types.Account = { owner; subaccount = null }; + amount = allocatedAmount; + memo = null; + created_at_time = ?Nat64.fromNat(Int.abs(now())); + }; + let minting_account = get_icrc1_state().minting_account; + ignore await* icrc1().mint(minting_account.owner, mintArgs); + + // Mapeo holder/amount para permitir o denegar transacciones durante periodo de vesting + if (Map.has(holdersVesting, phash, owner)) { + return #Err("Hay un mismo principal en mas de una categoría de distribución"); + }; + + if (hasVesting) { + ignore Map.put( + holdersVesting, + phash, + owner, + { value = allocatedAmount; categoryName = distItem.categoryName }, + ); + }; + }; + }; + distributionComplete := true; + #Ok + }; ////// Deploy de canister indexer y distribucion inicial /////// - stable var initialized = false; - public shared ({ caller }) func initialize(): async (){ + stable var distributionComplete = false; + + public shared ({ caller }) func initialize() : async { #Ok; #Err : Text } { assert (caller == _owner); - assert (not initialized); + let indexerCanisterId = await deploy_indexer(); - print( "Indexer canister deployed at " # debug_show(indexerCanisterId)); + print("Indexer canister deployed at " # debug_show (indexerCanisterId)); switch ledgerArgs { case (#Init(_)) { switch (customArgs.distribution) { case (?dist) { - for(distItem in dist.allocations.vals()){ - for ({ allocatedAmount; hasVesting; owner} in distItem.holders.vals()){ - let mintArgs = { - to: Types.Account = {owner ; subaccount = null}; - amount = allocatedAmount; - memo = null; - created_at_time = ?Nat64.fromNat(Int.abs(now())); - }; - let minting_account = get_icrc1_state().minting_account; - ignore await* icrc1().mint(minting_account.owner, mintArgs); - - // Mapeo holder/amount para permitir o denegar transacciones durante periodo de vesting - if ( hasVesting ){ - let totalAmount = switch(Map.get(holdersVesting, phash, owner)){ - case null { allocatedAmount }; - case ( ?previousAmount ) {allocatedAmount + previousAmount } // Caso de que un mismo principal se encuentre en dos categorias distintas - }; - ignore Map.put(holdersVesting, phash, owner, totalAmount); - } - } - } + return await distribution(dist.allocations); }; - case (_) {}; - }; + case (_) { return #Ok }; + }; }; - case (_) { } + case (_) { return #Ok }; }; - initialized := true + }; + + /////////////////////// vesting validations ///////////////////// + + stable let holdersVesting = Map.new(); - //// vesting validations //// + // func calculateBlockedAmount(initial: Nat, scheme: Tokenomic.VestingSchemme): Nat { - stable let holdersVesting = Map.new(); + // }; - func vestingVerification(caller: Principal, trx: ICRC1.TransferArgs): {#Ok; #Err: Types.TransferError} { + func checkVestingRestrictions(caller : Principal, trx : ICRC1.TransferArgs) : { + #Ok; + #Err : Types.TransferError; + } { // TODO ver esquema y status actual del vesting let balance = icrc1().balance_of({ owner = caller; subaccount = null }); - let blocked_amount = switch ( Map.get(holdersVesting, phash, caller) ){ - case null { 0 }; - case ( ?value ) { value } + let blocked_amount = switch (Map.get(holdersVesting, phash, caller)) { + case null { return #Ok; 0 }; + case (?{ value; categoryName }) { value }; }; - if(balance >= blocked_amount + trx.amount + icrc1().fee()) { - #Ok + if (balance >= blocked_amount + trx.amount + icrc1().fee()) { + #Ok; } else { - #Err(#VestingRestriction({ - blocked_amount; - available_amount = balance - blocked_amount - })) - } + #Err(#VestingRestriction({ blocked_amount; available_amount = balance - blocked_amount })); + }; }; - + // Custom functions - public query func indexerCanister(): async ?Principal { + public query func indexerCanister() : async ?Principal { switch (_indexer) { case null { null }; case (?indexer) { ?Principal.fromActor(indexer); - } - } + }; + }; }; public shared query ({ caller }) func balance(subaccount : ?Blob) : async Nat { @@ -275,13 +296,13 @@ shared ({ caller = _owner }) actor class CustomToken( }; public shared ({ caller }) func icrc1_transfer(args : ICRC1.TransferArgs) : async Types.TransferResult { - switch(vestingVerification(caller, args)) { - case ( #Err(e) ) { return #Err(e) }; - case _ { } + switch (checkVestingRestrictions(caller, args)) { + case (#Err(e)) { return #Err(e) }; + case _ {}; }; let trxResult = await* icrc1().transfer(caller, args); ignore pushTrxToIndexer(trxResult); - trxResult + trxResult; }; public shared ({ caller }) func mint(args : ICRC1.Mint) : async ICRC1.TransferResult { @@ -298,14 +319,18 @@ shared ({ caller = _owner }) actor class CustomToken( return icrc2().allowance(args.spender, args.account, false); }; - public shared ({ caller }) func icrc2_approve(args : ICRC2.ApproveArgs) : async ICRC2.ApproveResponse { + public shared ({ caller }) func icrc2_approve(args : ICRC2.ApproveArgs) : async Types.ApproveResponse { + switch (checkVestingRestrictions(caller, { args with to = args.spender } : ICRC1.TransferArgs)) { + case (#Err(#VestingRestriction(e))) { return #Err(#VestingRestriction(e)) }; + case _ {}; + }; await* icrc2().approve(caller, args); }; public shared ({ caller }) func icrc2_transfer_from(args : ICRC2.TransferFromArgs) : async ICRC2.TransferFromResponse { - switch(vestingVerification(args.from.owner, {args with from_subaccount = args.from.subaccount}: ICRC1.TransferArgs)) { - case ( #Err(e) ) { return #Err(e) }; - case _ { } + switch (checkVestingRestrictions(args.from.owner, { args with from_subaccount = args.from.subaccount } : ICRC1.TransferArgs)) { + case (#Err(e)) { return #Err(e) }; + case _ {}; }; let trxResult = await* icrc2().transfer_from(caller, args); switch trxResult { @@ -317,8 +342,8 @@ shared ({ caller = _owner }) actor class CustomToken( public query func getTransactionRange(start : Nat, _end : ?Nat) : async [ICRC1.Transaction] { let local_transactions = icrc1().get_local_transactions(); let end = switch _end { - case null { - Vec.size(local_transactions) + case null { + Vec.size(local_transactions); }; case (?val) { if (val > Vec.size(local_transactions)) { Vec.size(local_transactions) } else { val }; diff --git a/tour/tokenomic.mo b/tour/tokenomic.mo new file mode 100644 index 0000000..72019da --- /dev/null +++ b/tour/tokenomic.mo @@ -0,0 +1,67 @@ +import Nat8 "mo:base/Nat8"; + +module { + + public type InitialDistribution = { + allocations : [Allocation]; + }; + + public type Allocation = { + categoryName : Text; + holders : [InitialHolder]; + vestingSchemme : VestingSchemme; + }; + + public type VestingSchemme = { + #timeBasedVesting : TimeBasedVesting; + #mintBasedVesting : MintBasedVesting; + }; + + public type InitialHolder = { + owner : Principal; + allocatedAmount : Nat; + hasVesting : Bool; + }; + + public type TimeBasedVesting = { + cliff : ?Nat; // Comienzo del periodo de vesting. Si es null se toma la fecha del deploy. Timestamp seg + // duration : Nat; // Duración del periodo de vesting desde el cliff // comentado por redundante + // releaseRate : Nat; // Cantidad de tokens a liberar por periodo luego del periodo de vesting. Opcion de nombre maxAmountPerRelease + // El releaseRate se calcularia como ```amount / (intervalQty + 1)``` + intervalDuration : Nat; // Intervalo de tiempo en dias entre cada liberación + intervalQty : Nat8; // Cantidad de intervalos. ```duration = intervalQty * releaseInterval``` + }; + + public type VestingRule = { + timeBasedVesting : ?TimeBasedVesting; + mintBasedVesting : ?MintBasedVesting; + }; + + ///// Revisar regla //////////////////// + + public type MintBasedVesting = { + triggers : [{ totalSupply : Nat; releaseAmount : Nat }]; + withdrawalRatio : Nat; //relacion entre el totalSupply actual y el maximo que se puede retirar en un solo trigger + }; + + public func mintBasedVestingValidate(mintBasedVesting : MintBasedVesting) : Bool { + var lastTrigger = { totalSupply = 0; releaseAmount = 0 }; + let ratio = if (mintBasedVesting.withdrawalRatio < 500) { + 500; + } else { + mintBasedVesting.withdrawalRatio; + }; + for (t in mintBasedVesting.triggers.vals()) { + if ( + t.totalSupply <= lastTrigger.totalSupply or + t.releaseAmount <= lastTrigger.releaseAmount or + t.totalSupply < t.releaseAmount * ratio + ) { + return false; + }; + lastTrigger := t; + }; + true; + }; + +}; diff --git a/tour/types.mo b/tour/types.mo index 8a8d76d..4c75790 100644 --- a/tour/types.mo +++ b/tour/types.mo @@ -1,57 +1,8 @@ import ICRC1 "mo:icrc1-mo/ICRC1"; +import ICRC2 "mo:icrc2-mo/ICRC2"; module { - // public type CustomArgs = { - - // }; - - public type InitialDistribution = { - allocations : [{ categoryName : Text; holders : [InitialHolder] }]; - vestingSchemme: { - #timeBasedVesting: TimeBasedVesting; - #mintBasedVesting: MintBasedVesting; - } - }; - - public type InitialHolder = { - owner : Principal; - allocatedAmount : Nat; - hasVesting : Bool; - }; - - public type TimeBasedVesting = { - cliff : ?Nat; // Comienzo del periodo de vesting. Si es null se toma la fecha del deploy - duration : Nat; // Duración del periodo de vesting desde el cliff - releaseRate : Nat; // Cantidad de tokens a liberar por periodo luego del periodo de vesting - releaseInterval : Nat; // Intervalo de tiempo en segundos entre cada liberación - }; - - ///// Revisar regla //////////////////// - public type MintBasedVesting = { - triggers : [{ totalSupply : Nat; releaseAmount : Nat }]; - withdrawalRatio : Nat; //relacion entre el totalSupply actual y el maximo que se puede retirar en un solo trigger - }; - public func mintBasedVestingValidate(mintBasedVesting : MintBasedVesting) : Bool { - var lastTrigger = { totalSupply = 0; releaseAmount = 0 }; - let ratio = if (mintBasedVesting.withdrawalRatio < 500) { - 500; - } else { - mintBasedVesting.withdrawalRatio; - }; - for (t in mintBasedVesting.triggers.vals()) { - if ( - t.totalSupply <= lastTrigger.totalSupply or - t.releaseAmount <= lastTrigger.releaseAmount or - t.totalSupply < t.releaseAmount * ratio - ) { - return false; - }; - lastTrigger := t; - }; - true; - }; - // Custom Errors public type TxIndex = Nat; @@ -65,13 +16,22 @@ module { available_amount : Nat; }; }; - //////////////////////////////////////// - public type VestingRule = { - timeBasedVesting : ?TimeBasedVesting; - mintBasedVesting : ?MintBasedVesting; + public type ApproveResponse = { + #Ok : Nat; + #Err : ApproveError }; + public type ApproveError = ICRC2.ApproveError or { + #VestingRestriction : { + blocked_amount : Nat; + available_amount : Nat; + }; + }; + //////////////////////////////////////// + + + public type Account = { owner : Principal; subaccount : ?Blob }; public type ArchiveOptions = { From 44ae6b4269e7c0a9ac9f3b12b798373ab68f197a Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Thu, 13 Feb 2025 01:33:22 -0300 Subject: [PATCH 51/57] Test distribition; test Vesting scheme --- scripts-sh/deploy-token.sh | 12 ++-- scripts-sh/test-token.sh | 133 ++++++++++++++++++++++++++++++++++- tour/icrc1-custom.mo | 137 +++++++++++++++++++++++++++++++------ tour/tokenomic.mo | 15 +++- 4 files changed, 266 insertions(+), 31 deletions(-) diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh index c761cd1..a2a3634 100755 --- a/scripts-sh/deploy-token.sh +++ b/scripts-sh/deploy-token.sh @@ -124,7 +124,9 @@ dfx identity new 0000Deployer dfx identity use 0000Deployer export deployer=$(dfx identity get-principal) - +timestamp=$(date +%s) +cliff1=$((timestamp + 80)) +cliff2=$((timestamp + 120)) dfx deploy tour --argument '( variant { @@ -195,9 +197,9 @@ dfx deploy tour --argument '( allocatedAmount = 901_200_000 : nat; }; }; - vestingSchemme = variant { + vestingScheme = variant { timeBasedVesting = record { - cliff = opt 1739329277; + cliff = opt ('"$cliff1"'); intervalDuration = 30; intervalQty = 4 : nat8; } @@ -218,9 +220,9 @@ dfx deploy tour --argument '( }; }; - vestingSchemme = variant { + vestingScheme = variant { timeBasedVesting = record { - cliff = opt 1739332877; + cliff = opt ('"$cliff2"'); intervalDuration = 30; intervalQty = 3 : nat8; } diff --git a/scripts-sh/test-token.sh b/scripts-sh/test-token.sh index 862acae..9f1c09f 100755 --- a/scripts-sh/test-token.sh +++ b/scripts-sh/test-token.sh @@ -1,6 +1,5 @@ #!/bin/bash -# Función para ejecutar pruebas con colores run_test() { DESCRIPTION="$1" EXPECTED="$2" @@ -68,7 +67,7 @@ run_test "Test usuario con vesting quiere transferir 500_000_000 tokens" \ )" \ "dfx canister call tour icrc1_transfer '( record { - to = record { owner = principal \"smdkv-lcdvc-ukvh6-dhgfm-fej7o-76jhm-zbpoj-lq3jh-lfcn7-62pow-2qe\"; subaccount = null; }; + to = record { owner = principal \"$Founder01\"; subaccount = null; }; fee = null; memo = null; from_subaccount = null; @@ -104,9 +103,11 @@ run_test "El minter hace un mint de 2_000_000_000 tokens en favor de founder1" \ amount = 2_000_000_000 : nat; }, )'" +dfx canister call tour icrc1_balance_of "(record { owner = principal \"$Founder01\" })" run_test "Check total_supply luego del mint" \ "(9_703_000_000 : nat)" \ "dfx canister call tour icrc1_total_supply" + run_test "Verificación de balance de founder1" \ "(3_734_000_000 : nat)" \ "dfx canister call tour icrc1_balance_of '( @@ -178,3 +179,131 @@ run_test "Founder 1 quiere transferir 2_499_990_000 a founder 2 (exitosa)" \ }, )'" + +echo -e "\n\n============= PRUEBAS DE VESTING PROGRESIVO =============\n" + +# Función para esperar mostrando cuenta regresiva +wait_with_countdown() { + local seconds=$1 + echo -e "\n[⏳] Esperando $seconds segundos..." + while [ $seconds -gt 0 ]; do + echo -ne "Tiempo restante: $seconds segundos\r" + sleep 1 + ((seconds--)) + done + echo -e "\n[✅] Continuando con las pruebas\n" +} + +# Obtener balances iniciales de referencia +INITIAL_FOUNDER01_BALANCE=$(dfx canister call tour icrc1_balance_of "(record { owner = principal \"$Founder01\" })" | grep -oP '[0-9_]+(?= : nat)') +INITIAL_INVESTOR_BALANCE=$(dfx canister call tour icrc1_balance_of "(record { owner = principal \"$InvVesting\" })" | grep -oP '[0-9_]+(?= : nat)') + +# Simular paso del tiempo hasta el cliff de Founders (180 segundos) +wait_with_countdown 85 + +# Test 1: Primer desbloqueo de vesting para Founders +run_test "Post-cliff Founders: Primer desbloqueo parcial" \ + "(2_500_000_000 : nat)" \ + "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$Founder01\\\" })\"" + +# Test 2: Transferencia permitida dentro del monto desbloqueado +dfx identity use 0000Founder01 +run_test "Founder1 transfiere 500M usando tokens desbloqueados" \ + "(variant { Ok = 8 : nat })" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder02\" }; + amount = 500_000_000 : nat; + } + )'" + +# Avanzar primer intervalo de vesting (30 segundos) +wait_with_countdown 30 + +# Test 3: Segundo desbloqueo de vesting +run_test "Post-interval 1 Founders: Segundo desbloqueo" \ + "(3_000_000_000 : nat)" \ + "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$Founder01\\\" })\"" + +# Test 4: Intentar transferir monto mayor al desbloqueado +run_test "Founder1 intenta transferir excediendo límite" \ + "VestingRestriction" \ + "dfx canister call tour icrc1_transfer '( + record { + to = principal \"$Founder03\"; + amount = 3_000_000_000 : nat; + } + )'" + +# Avanzar segundo intervalo de vesting (30 segundos) +wait_with_countdown 30 + +# Test 5: Tercer desbloqueo +run_test "Post-interval 2 Founders: Tercer desbloqueo" \ + "(3_500_000_000 : nat)" \ + "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$Founder01\\\" })\"" + +# Avanzar tercer intervalo (30 segundos) +wait_with_countdown 30 + +# Test 6: Cuarto desbloqueo (final) +run_test "Post-interval 3 Founders: Desbloqueo total" \ + "(4_000_000_000 : nat)" \ + "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$Founder01\\\" })\"" + +# Test 7: Transferencia total permitida +run_test "Founder1 transfere saldo completo" \ + "(variant { Ok = 9 : nat })" \ + "dfx canister call tour icrc1_transfer '( + record { + to = principal \"$Founder03\"; + amount = 3_500_000_000 : nat; + } + )'" + +# ============= PRUEBAS PARA INVESTORS ============= +echo -e "\n\n[🚀] Iniciando pruebas de vesting para Investors\n" + +# Esperar hasta cliff de Investors (360 segundos desde inicio) +wait_with_countdown 120 # Ya han pasado 180 + 90 = 270, necesitamos 90 más para llegar a 360 + +# Test 8: Desbloqueo inicial Investors +run_test "Post-cliff Investors: Desbloqueo inicial" \ + "(1_000_000_000 : nat)" \ + "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$InvVesting\\\" })\"" + +# Test 9: Transferencia parcial Investor +dfx identity use 0000InvVesting +run_test "Investor vesting transfiere 500M" \ + "(variant { Ok = 10 : nat })" \ + "dfx canister call tour icrc1_transfer '( + record { + to = principal \"$InvNonVesting\"; + amount = 500_000_000 : nat; + } + )'" + +# Avanzar intervalo Investors (30 segundos) +wait_with_countdown 30 + +# Test 10: Segundo desbloqueo Investor +run_test "Post-interval 1 Investors: Segundo desbloqueo" \ + "(2_000_000_000 : nat)" \ + "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$InvVesting\\\" })\"" + +# Avanzar último intervalo (30 segundos) +wait_with_countdown 30 + +# Test 11: Desbloqueo total Investor +run_test "Post-interval 2 Investors: Desbloqueo completo" \ + "(3_000_000_000 : nat)" \ + "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$InvVesting\\\" })\"" + +# Test final: Verificación de balances acumulativos +echo -e "\n[📊] Balance final Founder01:" +dfx canister call tour icrc1_balance_of "(record { owner = principal \"$Founder01\" })" + +echo -e "\n[📊] Balance final Investor Vesting:" +dfx canister call tour icrc1_balance_of "(record { owner = principal \"$InvVesting\" })" + +echo -e "\n[🎉] Todas las pruebas de vesting completadas exitosamente!" \ No newline at end of file diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index d8cbb3a..96c3ccf 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -13,7 +13,11 @@ import Tokenomic "tokenomic"; import Vec "mo:vector"; import Indexer "indexer"; import Array "mo:base/Array"; +import Iter "mo:base/Iter"; import Int "mo:base/Int"; +import Nat8 "mo:base/Nat8"; +import Buffer "mo:base/Buffer"; +import Nat "mo:base/Nat"; import IC "../interfaces/ic-management-interface"; shared ({ caller = _owner }) actor class CustomToken( @@ -28,6 +32,15 @@ shared ({ caller = _owner }) actor class CustomToken( }, ) = this { + ///////////////////////////////// WARNING /////////////////////////////////////// + + // let NanosPerDay = 24 * 60 * 60 * 1_000_000_000; // Valor definitivo + let NanosPerDay = 1_000_000_000; // Valor para pruebas 1 dia = 1 segundo + + stable var distributionTimestamp: Int = 0; + + ////////////////////////////////////////////////////////////////////////////////// + stable var icrc1_args : ?ICRC1.InitArgs = null; stable var icrc2_args : ?ICRC2.InitArgs = null; switch ledgerArgs { @@ -68,6 +81,14 @@ shared ({ caller = _owner }) actor class CustomToken( stable let icrc1_migration_state = ICRC1.init(ICRC1.initialState(), #v0_1_0(#id), icrc1_args, _owner); + ///////////////////////////////////// Flags ////////////////////////////////////// + + stable var distributionComplete = false; + stable var vestingSchemes: [{scheme : Tokenomic.VestingScheme; ended : Bool }] = []; + stable var isLedgerReady = false; + + ////// + let #v0_1_0(#data(icrc1_state_current)) = icrc1_migration_state; private var _icrc1 : ?ICRC1.ICRC1 = null; @@ -145,7 +166,7 @@ shared ({ caller = _owner }) actor class CustomToken( trxResult; }; - ///// Deploy indexer ///// + /////////////////// Deploy indexer ////////////////// private func deploy_indexer() : async Principal { switch _indexer { @@ -162,9 +183,15 @@ shared ({ caller = _owner }) actor class CustomToken( }; }; + //////////////// Initial Distribution //////////////// + func distribution(allocations : [Tokenomic.Allocation]): async {#Ok; #Err: Text} { if (distributionComplete) { return #Err("Distribution is already complete")}; for (distItem in allocations.vals()) { + vestingSchemes := Array.tabulate<{scheme : Tokenomic.VestingScheme; ended : Bool }>( + vestingSchemes.size() + 1, + func i = if (i == 0) { {scheme = distItem.vestingScheme; ended = false} } else { vestingSchemes[i-1] } + ); for ({ allocatedAmount; hasVesting; owner } in distItem.holders.vals()) { let mintArgs = { to : Types.Account = { owner; subaccount = null }; @@ -176,27 +203,27 @@ shared ({ caller = _owner }) actor class CustomToken( ignore await* icrc1().mint(minting_account.owner, mintArgs); // Mapeo holder/amount para permitir o denegar transacciones durante periodo de vesting - if (Map.has(holdersVesting, phash, owner)) { + if (Map.has(holdersVesting, phash, owner)) { return #Err("Hay un mismo principal en mas de una categoría de distribución"); }; if (hasVesting) { - ignore Map.put( + ignore Map.put( holdersVesting, phash, owner, - { value = allocatedAmount; categoryName = distItem.categoryName }, + { value = allocatedAmount; schemeIndex = vestingSchemes.size() - 1}, ); }; }; }; distributionComplete := true; + distributionTimestamp := now(); #Ok }; ////// Deploy de canister indexer y distribucion inicial /////// - stable var distributionComplete = false; public shared ({ caller }) func initialize() : async { #Ok; #Err : Text } { assert (caller == _owner); @@ -216,26 +243,47 @@ shared ({ caller = _owner }) actor class CustomToken( }; }; - - /////////////////////// vesting validations ///////////////////// - - stable let holdersVesting = Map.new(); - // func calculateBlockedAmount(initial: Nat, scheme: Tokenomic.VestingSchemme): Nat { - - // }; + /////////////////////// vesting validations ///////////////////// - func checkVestingRestrictions(caller : Principal, trx : ICRC1.TransferArgs) : { - #Ok; - #Err : Types.TransferError; - } { - // TODO ver esquema y status actual del vesting + stable let holdersVesting = Map.new(); + + func calculateBlockedAmount(p: Principal): Nat { + switch (Map.get(holdersVesting, phash, p)) { + case null { return 0 }; + case (?{ value; schemeIndex }) { + if(vestingSchemes[schemeIndex].ended) { + return 0; + } else { + switch (vestingSchemes[schemeIndex].scheme) { + case (#timeBasedVesting(scheme)) { + // let currentTime = now(); + let { period } = getCurrentPeriodVesting(scheme); + print(debug_show(period)); + + if(period == 0) { + return value + } + else { + print("Value: " # debug_show(value)); + print("Blocked: " # debug_show((value * period) / Nat8.toNat(scheme.intervalQty + 1))); + return value - (value * period) / Nat8.toNat(scheme.intervalQty + 1) + } + }; + case (_){ return 0 } // Otros esquemas a implementar + }; + value + } + }; + }; + + }; + func checkVestingRestrictions(caller : Principal, trx : ICRC1.TransferArgs) : { #Ok; #Err : Types.TransferError; } { + // TODO ver esquema y status actual del vesting let balance = icrc1().balance_of({ owner = caller; subaccount = null }); - let blocked_amount = switch (Map.get(holdersVesting, phash, caller)) { - case null { return #Ok; 0 }; - case (?{ value; categoryName }) { value }; - }; + let blocked_amount = calculateBlockedAmount(caller); + if (blocked_amount == 0 ) { return #Ok }; if (balance >= blocked_amount + trx.amount + icrc1().fee()) { #Ok; } else { @@ -258,7 +306,54 @@ shared ({ caller = _owner }) actor class CustomToken( icrc1().balance_of({ owner = caller; subaccount }); }; + func getCurrentPeriodVesting (scheme: Tokenomic.TimeBasedVesting) : {period: Nat; cliff: Int} { + let currentTime = Int.abs(now()); + let cliff = switch (scheme.cliff) { + case null { distributionTimestamp }; + case ( ?c ) { c * 1_000_000_000 } + }; + print("El timestamp actual en nSeg es: +" # debug_show(currentTime)); + print("El inicio del vesting (CLIFF) es : " # debug_show(cliff)); + var period = 0; + var passedTime = 0: Int; + passedTime := cliff - currentTime; + while (currentTime > cliff + period * scheme.intervalDuration * NanosPerDay and period < Nat8.toNat(scheme.intervalQty)) { + print("El timestamp que da inicio al period " # debug_show(period) # " es " # debug_show(cliff + period * scheme.intervalDuration * NanosPerDay)); + print("Periodo " # Nat.toText(period) # " transcurrido!"); + period += 1; + }; + return {period; cliff} + }; + + public query func vestingsStatus(): async [Tokenomic.VestingState] { + let states = Buffer.fromArray([]); + for (vst in vestingSchemes.vals()){ + switch (vst.scheme) { + case (#timeBasedVesting(scheme)) { + let {period; cliff} = getCurrentPeriodVesting(scheme); + let state: Tokenomic.VestingState = { + currentPeriodOverTotal = (period, Nat8.toNat(scheme.intervalQty)); + isBeforeCliff = period == 0; + isFullyVested = period == Nat8.toNat(scheme.intervalQty); + nextReleaseTime = if(period == Nat8.toNat(scheme.intervalQty)) { null } else { ? (cliff + period * scheme.intervalDuration * NanosPerDay) }; + remainingAmount = 0; + vestedAmount = 0; + }; + states.add(state); + + }; + case _{ }; + }; + }; + Buffer.toArray(states) + }; + /// Functions for the ICRC1 token standard + + public shared query func is_ledger_ready(): async Bool { + isLedgerReady + }; + public shared query func icrc1_name() : async Text { icrc1().name(); }; diff --git a/tour/tokenomic.mo b/tour/tokenomic.mo index 72019da..3145f57 100644 --- a/tour/tokenomic.mo +++ b/tour/tokenomic.mo @@ -9,14 +9,23 @@ module { public type Allocation = { categoryName : Text; holders : [InitialHolder]; - vestingSchemme : VestingSchemme; + vestingScheme : VestingScheme; }; - public type VestingSchemme = { + public type VestingScheme = { #timeBasedVesting : TimeBasedVesting; #mintBasedVesting : MintBasedVesting; }; + public type VestingState = { + isBeforeCliff : Bool; + isFullyVested : Bool; // Si ya se liberaron todos los tokens (currentTime >= endTime) + currentPeriodOverTotal: (Nat, Nat); + vestedAmount : Nat; // Cantidad total liberada hasta ahora + remainingAmount : Nat; // Cantidad aún bloqueada + nextReleaseTime : ?Int; // Timestamp del próximo release (null si ya terminó) + }; + public type InitialHolder = { owner : Principal; allocatedAmount : Nat; @@ -24,7 +33,7 @@ module { }; public type TimeBasedVesting = { - cliff : ?Nat; // Comienzo del periodo de vesting. Si es null se toma la fecha del deploy. Timestamp seg + cliff : ?Int; // Comienzo del periodo de vesting. Si es null se toma la fecha del deploy. Timestamp seg // duration : Nat; // Duración del periodo de vesting desde el cliff // comentado por redundante // releaseRate : Nat; // Cantidad de tokens a liberar por periodo luego del periodo de vesting. Opcion de nombre maxAmountPerRelease // El releaseRate se calcularia como ```amount / (intervalQty + 1)``` From 891d805660a8295dd4074b2ac6fe26321e8c2d20 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sat, 15 Feb 2025 19:58:05 -0300 Subject: [PATCH 52/57] Minter actor class in progress | TestToken post cliff OK --- scripts-sh/deploy-token.sh | 30 ++-- scripts-sh/test-token.sh | 345 ++++++++++++++++++++++++------------- tour/icrc1-custom.mo | 53 +++--- tour/minter-canister.mo | 81 +++++++++ tour/tokenomic.mo | 1 + 5 files changed, 348 insertions(+), 162 deletions(-) create mode 100644 tour/minter-canister.mo diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh index a2a3634..93d36b8 100755 --- a/scripts-sh/deploy-token.sh +++ b/scripts-sh/deploy-token.sh @@ -125,8 +125,8 @@ dfx identity use 0000Deployer export deployer=$(dfx identity get-principal) timestamp=$(date +%s) -cliff1=$((timestamp + 80)) -cliff2=$((timestamp + 120)) +cliffFounders=$((timestamp + 83)) #Primos +cliffInvestors=$((timestamp + 120)) #No Primos para que no coincida ningun release de una categoria con uno de la otra dfx deploy tour --argument '( variant { @@ -153,8 +153,8 @@ dfx deploy tour --argument '( }; initial_balances = vec {}; fee_collector_account = opt record { - owner = principal "'$FeeCollector'"; - subaccount = null; + owner = principal "'$Minter'"; + subaccount = opt blob "FeeCollector00000000000000000000"; }; archive_options = record { num_blocks_to_archive = 1_000 : nat64; @@ -184,24 +184,24 @@ dfx deploy tour --argument '( record { owner = principal "'$Founder01'"; hasVesting = true; - allocatedAmount = 1_234_000_000 : nat; + allocatedAmount = 20_000_000_000 : nat; }; record { owner = principal "'$Founder02'"; hasVesting = true; - allocatedAmount = 567_800_000 : nat; + allocatedAmount = 20_000_000_000 : nat; }; record { owner = principal "'$Founder03'"; hasVesting = true; - allocatedAmount = 901_200_000 : nat; + allocatedAmount = 30_000_000_000 : nat; }; }; vestingScheme = variant { timeBasedVesting = record { - cliff = opt ('"$cliff1"'); - intervalDuration = 30; - intervalQty = 4 : nat8; + cliff = opt ('"$cliffFounders"'); + intervalDuration = 23; + intervalQty = 12 : nat8; } } }; @@ -211,20 +211,20 @@ dfx deploy tour --argument '( record { owner = principal "'$InvVesting'"; hasVesting = true; - allocatedAmount = 4_000_000_000 : nat; + allocatedAmount = 450_000_000_000 : nat; }; record { owner = principal "'$InvNonVesting'"; hasVesting = false; - allocatedAmount = 1_000_000_000 : nat; + allocatedAmount = 480_000_000_000 : nat; }; }; vestingScheme = variant { timeBasedVesting = record { - cliff = opt ('"$cliff2"'); - intervalDuration = 30; - intervalQty = 3 : nat8; + cliff = opt ('"$cliffInvestors"'); + intervalDuration = 27; + intervalQty = 6 : nat8; } } }; diff --git a/scripts-sh/test-token.sh b/scripts-sh/test-token.sh index 9f1c09f..be93ba1 100755 --- a/scripts-sh/test-token.sh +++ b/scripts-sh/test-token.sh @@ -48,8 +48,11 @@ dfx identity use 0000Controller export Controller=$(dfx identity get-principal) # Test balance luego de distribución + +echo -e "\n\n===== PRUEBAS PREVIAS AL INICIO DE VESTING distribucion con vesting bloqueada =====\n" + run_test "Test total_supply luego de distribución" \ - "(7_703_000_000 : nat)" \ + "(1_000_000_000_000 : nat)" \ "dfx canister call tour icrc1_total_supply" # Test usuario con vesting intentando transferir tokens @@ -59,7 +62,7 @@ run_test "Test usuario con vesting quiere transferir 500_000_000 tokens" \ variant { Err = variant { VestingRestriction = record { - blocked_amount = 4_000_000_000 : nat; + blocked_amount = 450_000_000_000 : nat; available_amount = 0 : nat; } } @@ -78,7 +81,7 @@ run_test "Test usuario con vesting quiere transferir 500_000_000 tokens" \ # Test usuario sin vesting realizando transferencia dfx identity use 0000InvNonVesting -run_test "Test usuario sin vesting quiere transferir 500_000_000 tokens a founder1" \ +run_test "Test usuario SIN VESTING puede transferir 500_000_000 tokens a founder1" \ "(variant { Ok = 5 : nat })" \ "dfx canister call tour icrc1_transfer '( record { @@ -91,6 +94,24 @@ run_test "Test usuario sin vesting quiere transferir 500_000_000 tokens a founde }, )'" +run_test "Verificación de balance de usuario sin vesting luego de la transferencia" \ + "(479_499_990_000 : nat)" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$InvNonVesting\" }, + )'" + +run_test "Verificación de balance de founder1 luego de la transferencia" \ + "(20_500_000_000 : nat)" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$Founder01\" }, + )'" + +run_test "Verificacion de balance del fee_collector luego de una transaccion" \ + "(10_000 : nat)" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$Minter\"; subaccount = opt blob \"FeeCollector00000000000000000000\"}, + )'" + # Test mint y verificación de balance dfx identity use 0000Minter run_test "El minter hace un mint de 2_000_000_000 tokens en favor de founder1" \ @@ -103,22 +124,17 @@ run_test "El minter hace un mint de 2_000_000_000 tokens en favor de founder1" \ amount = 2_000_000_000 : nat; }, )'" -dfx canister call tour icrc1_balance_of "(record { owner = principal \"$Founder01\" })" -run_test "Check total_supply luego del mint" \ - "(9_703_000_000 : nat)" \ - "dfx canister call tour icrc1_total_supply" run_test "Verificación de balance de founder1" \ - "(3_734_000_000 : nat)" \ + "(22_500_000_000 : nat)" \ "dfx canister call tour icrc1_balance_of '( record { owner = principal \"$Founder01\" }, )'" -run_test "Verificación de balance de inversor sin vesting luego de su transferencia hacia founder1" \ - "(499_990_000 : nat)" \ - "dfx canister call tour icrc1_balance_of '( - record { owner = principal \"$InvNonVesting\" }, - )'" +run_test "Check total_supply luego del mint" \ + "(1_002_000_000_000 : nat)" \ + "dfx canister call tour icrc1_total_supply" + # Founder 1 intenta transferir más de lo permitido dfx identity use 0000Founder01 @@ -127,7 +143,7 @@ run_test "Founder 1 quiere transferir 3_000_000_000 a founder 2 (bloqueado por v variant { Err = variant { VestingRestriction = record { - blocked_amount = 1_234_000_000 : nat; + blocked_amount = 20_000_000_000 : nat; available_amount = 2_500_000_000 : nat; } } @@ -149,7 +165,7 @@ run_test "Founder 1 quiere transferir 2_500_000_000 a founder 2 (bloqueado por v variant { Err = variant { VestingRestriction = record { - blocked_amount = 1_234_000_000 : nat; + blocked_amount = 20_000_000_000 : nat; available_amount = 2_500_000_000 : nat; } } @@ -182,128 +198,213 @@ run_test "Founder 1 quiere transferir 2_499_990_000 a founder 2 (exitosa)" \ echo -e "\n\n============= PRUEBAS DE VESTING PROGRESIVO =============\n" +echo -e "\n============= Verificación del tiempo restante para el siguiente release =============\n" +now=$(date +%s) + +#_____________ +mapfile -t next_release_entries < <(dfx canister call tour vestingsStatus | grep -E 'categoryName =|nextReleaseTime =' | awk -F '= ' '{print $2}' | sed 's/;//g' | tr -d '"') + +min=5000000000000000000 +nextCategory="" +now=$(date +%s) +currentCategory="" + +# Iterar sobre las entradas extraídas +for ((i=0; i<${#next_release_entries[@]}; i++)); do + entry="${next_release_entries[i]}" + + # Si la entrada es un categoryName, guardarlo temporalmente + if [[ "$entry" =~ ^[A-Za-z]+$ ]]; then + currentCategory="$entry" + + # Si la entrada es un timestamp, procesarlo + elif [[ "$entry" != "null" ]]; then + num=$(echo "$entry" | grep -oP '\d+(_\d+)*' | tr -d '_') + + if [[ -n "$num" && "$num" -lt "$min" ]]; then + min="$num" + nextCategory="$currentCategory" + fi + fi +done + +# Convertir timestamp a segundos +min_seconds=$((min / 1000000000)) +secondsWaiting=$((min_seconds - now)) + +# Imprimir resultados +echo "La proxima liberación de fondos corresponde a la categoria: $nextCategory" +echo "Segundos restantes: $secondsWaiting" + + + # Función para esperar mostrando cuenta regresiva wait_with_countdown() { local seconds=$1 echo -e "\n[⏳] Esperando $seconds segundos..." - while [ $seconds -gt 0 ]; do - echo -ne "Tiempo restante: $seconds segundos\r" + while [ $seconds -gt -1 ]; do + echo -ne " Tiempo restante: $seconds segundos\r" sleep 1 ((seconds--)) done echo -e "\n[✅] Continuando con las pruebas\n" } -# Obtener balances iniciales de referencia -INITIAL_FOUNDER01_BALANCE=$(dfx canister call tour icrc1_balance_of "(record { owner = principal \"$Founder01\" })" | grep -oP '[0-9_]+(?= : nat)') -INITIAL_INVESTOR_BALANCE=$(dfx canister call tour icrc1_balance_of "(record { owner = principal \"$InvVesting\" })" | grep -oP '[0-9_]+(?= : nat)') - # Simular paso del tiempo hasta el cliff de Founders (180 segundos) -wait_with_countdown 85 - -# Test 1: Primer desbloqueo de vesting para Founders -run_test "Post-cliff Founders: Primer desbloqueo parcial" \ - "(2_500_000_000 : nat)" \ - "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$Founder01\\\" })\"" - -# Test 2: Transferencia permitida dentro del monto desbloqueado -dfx identity use 0000Founder01 -run_test "Founder1 transfiere 500M usando tokens desbloqueados" \ - "(variant { Ok = 8 : nat })" \ - "dfx canister call tour icrc1_transfer '( - record { - to = record { owner = principal \"$Founder02\" }; - amount = 500_000_000 : nat; - } - )'" - -# Avanzar primer intervalo de vesting (30 segundos) -wait_with_countdown 30 - -# Test 3: Segundo desbloqueo de vesting -run_test "Post-interval 1 Founders: Segundo desbloqueo" \ - "(3_000_000_000 : nat)" \ - "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$Founder01\\\" })\"" - -# Test 4: Intentar transferir monto mayor al desbloqueado -run_test "Founder1 intenta transferir excediendo límite" \ - "VestingRestriction" \ - "dfx canister call tour icrc1_transfer '( - record { - to = principal \"$Founder03\"; - amount = 3_000_000_000 : nat; - } - )'" - -# Avanzar segundo intervalo de vesting (30 segundos) -wait_with_countdown 30 - -# Test 5: Tercer desbloqueo -run_test "Post-interval 2 Founders: Tercer desbloqueo" \ - "(3_500_000_000 : nat)" \ - "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$Founder01\\\" })\"" - -# Avanzar tercer intervalo (30 segundos) -wait_with_countdown 30 - -# Test 6: Cuarto desbloqueo (final) -run_test "Post-interval 3 Founders: Desbloqueo total" \ - "(4_000_000_000 : nat)" \ - "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$Founder01\\\" })\"" - -# Test 7: Transferencia total permitida -run_test "Founder1 transfere saldo completo" \ - "(variant { Ok = 9 : nat })" \ - "dfx canister call tour icrc1_transfer '( - record { - to = principal \"$Founder03\"; - amount = 3_500_000_000 : nat; - } - )'" - -# ============= PRUEBAS PARA INVESTORS ============= -echo -e "\n\n[🚀] Iniciando pruebas de vesting para Investors\n" +wait_with_countdown $((secondsWaiting + 1)) -# Esperar hasta cliff de Investors (360 segundos desde inicio) -wait_with_countdown 120 # Ya han pasado 180 + 90 = 270, necesitamos 90 más para llegar a 360 +echo -e "\n\n============= PRUEBAS DE TRANSACCIONES EN CASOS LÍMITE PARA FOUNDERS =============\n" -# Test 8: Desbloqueo inicial Investors -run_test "Post-cliff Investors: Desbloqueo inicial" \ - "(1_000_000_000 : nat)" \ - "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$InvVesting\\\" })\"" +# Verificar si la próxima categoría es Founders +if [[ "$nextCategory" == "Founders" ]]; then + echo "La próxima liberación de fondos es para la categoría Founders. Procediendo con las pruebas..." -# Test 9: Transferencia parcial Investor -dfx identity use 0000InvVesting -run_test "Investor vesting transfiere 500M" \ - "(variant { Ok = 10 : nat })" \ - "dfx canister call tour icrc1_transfer '( - record { - to = principal \"$InvNonVesting\"; - amount = 500_000_000 : nat; - } - )'" + # Seleccionar una identidad Founder + dfx identity use 0000Founder03 -# Avanzar intervalo Investors (30 segundos) -wait_with_countdown 30 - -# Test 10: Segundo desbloqueo Investor -run_test "Post-interval 1 Investors: Segundo desbloqueo" \ - "(2_000_000_000 : nat)" \ - "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$InvVesting\\\" })\"" - -# Avanzar último intervalo (30 segundos) -wait_with_countdown 30 - -# Test 11: Desbloqueo total Investor -run_test "Post-interval 2 Investors: Desbloqueo completo" \ - "(3_000_000_000 : nat)" \ - "dfx canister call tour vesting_available_amount \"(record { owner = principal \\\"$InvVesting\\\" })\"" + run_test "Founder03 intenta transferir el monto exacto disponible" \ + "( + variant { + Err = variant { + VestingRestriction = record { + blocked_amount = 27_500_000_000 : nat; + available_amount = 2_500_000_000 : nat; + } + } + }, + )" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder02\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 2_500_000_000 : nat; + }, + )'" + + # Intentar transferir justo el valor disponible menos la comisión + run_test "Founder03 intenta transferir el valor disponible exacto menos la comisión" \ + "(variant { Ok = 8 : nat })" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder02\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 2_490_990_000 : nat; + }, + )'" + + + run_test "Verificación de balance de founder1 luego de la transferencia" \ + "(20_000_000_000 : nat)" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$Founder01\" }, + )'" + + # Verificar el balance del receptor (Founder02) después de la transferencia + run_test "Verificación de balance de Founder02 después de la transferencia" \ + "(24_990_980_000 : nat" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$Founder02\" }, + )'" + + # Verificar el balance del fee_collector después de la transferencia + run_test "Verificación de balance del fee_collector después de la transferencia" \ + "(30_000 : nat)" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$Minter\"; subaccount = opt blob \"FeeCollector00000000000000000000\"}, + )'" + +else + echo "La próxima liberación de fondos no es para la categoría Founders. Realizando pruebas para Investors..." + + # Seleccionar una identidad Investor + dfx identity use 0000InvVesting + export Investor=$(dfx identity get-principal) + + run_test "Investor intenta transferir un valor por encima del disponible" \ + "(variant { Err = variant { InsufficientFunds = record { balance = $available_balance_investor : nat } } })" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder01\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 50000000001 : nat; + }, + )'" + + # Intentar transferir justo el valor disponible menos la comisión + run_test "Investor intenta transferir el valor disponible exacto menos la comisión" \ + "(variant { Ok = 9 : nat })" \ + "dfx canister call tour icrc1_transfer '( + record { + to = record { owner = principal \"$Founder01\"; subaccount = null; }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = null; + amount = 545454545454554 : nat; + }, + )'" + +¡ + run_test "Verificación de balance disponible de Investor después de la transferencia" \ + "(0 : nat)" \ + "echo $available_balance_investor" + + # Verificar el balance del receptor (Founder01) después de la transferencia + run_test "Verificación de balance de Founder01 después de la transferencia" \ + "($((available_balance_investor - fee)) : nat" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$Founder01\" }, + )'" + + # Verificar el balance del fee_collector después de la transferencia + run_test "Verificación de balance del fee_collector después de la transferencia" \ + "($((10_000 + fee)) : nat)" \ + "dfx canister call tour icrc1_balance_of '( + record { owner = principal \"$Minter\"; subaccount = opt blob \"FeeCollector00000000000000000000\"}, + )'" +fi + +mapfile -t next_release_entries < <(dfx canister call tour vestingsStatus | grep -E 'categoryName =|nextReleaseTime =' | awk -F '= ' '{print $2}' | sed 's/;//g' | tr -d '"') + +min=5000000000000000000 +nextCategory="" +now=$(date +%s) +currentCategory="" + +# Iterar sobre las entradas extraídas +for ((i=0; i<${#next_release_entries[@]}; i++)); do + entry="${next_release_entries[i]}" + + # Si la entrada es un categoryName, guardarlo temporalmente + if [[ "$entry" =~ ^[A-Za-z]+$ ]]; then + currentCategory="$entry" + + # Si la entrada es un timestamp, procesarlo + elif [[ "$entry" != "null" ]]; then + num=$(echo "$entry" | grep -oP '\d+(_\d+)*' | tr -d '_') + + if [[ -n "$num" && "$num" -lt "$min" ]]; then + min="$num" + nextCategory="$currentCategory" + fi + fi +done -# Test final: Verificación de balances acumulativos -echo -e "\n[📊] Balance final Founder01:" -dfx canister call tour icrc1_balance_of "(record { owner = principal \"$Founder01\" })" +# Convertir timestamp a segundos +min_seconds=$((min / 1000000000)) +secondsWaiting=$((min_seconds - now)) -echo -e "\n[📊] Balance final Investor Vesting:" -dfx canister call tour icrc1_balance_of "(record { owner = principal \"$InvVesting\" })" +# Imprimir resultados +echo "La proxima liberación de fondos corresponde a la categoria: $nextCategory" +echo "Segundos restantes: $secondsWaiting" -echo -e "\n[🎉] Todas las pruebas de vesting completadas exitosamente!" \ No newline at end of file +wait_with_countdown $((secondsWaiting + 1)) \ No newline at end of file diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index 96c3ccf..0b6bedd 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -35,7 +35,7 @@ shared ({ caller = _owner }) actor class CustomToken( ///////////////////////////////// WARNING /////////////////////////////////////// // let NanosPerDay = 24 * 60 * 60 * 1_000_000_000; // Valor definitivo - let NanosPerDay = 1_000_000_000; // Valor para pruebas 1 dia = 1 segundo + let NanosPerDay = 2 * 1_000_000_000; // Valor para pruebas 1 dia = 4 segundo stable var distributionTimestamp: Int = 0; @@ -84,7 +84,7 @@ shared ({ caller = _owner }) actor class CustomToken( ///////////////////////////////////// Flags ////////////////////////////////////// stable var distributionComplete = false; - stable var vestingSchemes: [{scheme : Tokenomic.VestingScheme; ended : Bool }] = []; + stable var vestingSchemes: [{categoryName: Text; scheme : Tokenomic.VestingScheme; ended : Bool }] = []; stable var isLedgerReady = false; ////// @@ -168,7 +168,7 @@ shared ({ caller = _owner }) actor class CustomToken( /////////////////// Deploy indexer ////////////////// - private func deploy_indexer() : async Principal { + func deploy_indexer() : async Principal { switch _indexer { case null { ExperimentalCycles.add(2_000_000_000_000); @@ -188,9 +188,14 @@ shared ({ caller = _owner }) actor class CustomToken( func distribution(allocations : [Tokenomic.Allocation]): async {#Ok; #Err: Text} { if (distributionComplete) { return #Err("Distribution is already complete")}; for (distItem in allocations.vals()) { - vestingSchemes := Array.tabulate<{scheme : Tokenomic.VestingScheme; ended : Bool }>( + vestingSchemes := Array.tabulate<{categoryName: Text; scheme: Tokenomic.VestingScheme; ended : Bool }>( vestingSchemes.size() + 1, - func i = if (i == 0) { {scheme = distItem.vestingScheme; ended = false} } else { vestingSchemes[i-1] } + func i = if (i == vestingSchemes.size()) { + {categoryName = distItem.categoryName; scheme = distItem.vestingScheme; ended = false} + } + else { + vestingSchemes[i] + } ); for ({ allocatedAmount; hasVesting; owner } in distItem.holders.vals()) { let mintArgs = { @@ -220,16 +225,17 @@ shared ({ caller = _owner }) actor class CustomToken( distributionComplete := true; distributionTimestamp := now(); #Ok - }; ////// Deploy de canister indexer y distribucion inicial /////// + + public shared ({ caller }) func initialize() : async { #Ok; #Err : Text } { assert (caller == _owner); - let indexerCanisterId = await deploy_indexer(); print("Indexer canister deployed at " # debug_show (indexerCanisterId)); + switch ledgerArgs { case (#Init(_)) { switch (customArgs.distribution) { @@ -257,17 +263,13 @@ shared ({ caller = _owner }) actor class CustomToken( } else { switch (vestingSchemes[schemeIndex].scheme) { case (#timeBasedVesting(scheme)) { - // let currentTime = now(); let { period } = getCurrentPeriodVesting(scheme); - print(debug_show(period)); if(period == 0) { return value } else { - print("Value: " # debug_show(value)); - print("Blocked: " # debug_show((value * period) / Nat8.toNat(scheme.intervalQty + 1))); - return value - (value * period) / Nat8.toNat(scheme.intervalQty + 1) + return value - ((value * period) / Nat8.toNat(scheme.intervalQty)) } }; case (_){ return 0 } // Otros esquemas a implementar @@ -275,12 +277,10 @@ shared ({ caller = _owner }) actor class CustomToken( value } }; - }; - + }; }; func checkVestingRestrictions(caller : Principal, trx : ICRC1.TransferArgs) : { #Ok; #Err : Types.TransferError; } { - // TODO ver esquema y status actual del vesting let balance = icrc1().balance_of({ owner = caller; subaccount = null }); let blocked_amount = calculateBlockedAmount(caller); if (blocked_amount == 0 ) { return #Ok }; @@ -306,22 +306,23 @@ shared ({ caller = _owner }) actor class CustomToken( icrc1().balance_of({ owner = caller; subaccount }); }; + // public shared ({ caller }) func available(): async Nat { + // icrc1().balance_of({ owner = caller; subaccount = null }) - get; + // }; + func getCurrentPeriodVesting (scheme: Tokenomic.TimeBasedVesting) : {period: Nat; cliff: Int} { let currentTime = Int.abs(now()); let cliff = switch (scheme.cliff) { case null { distributionTimestamp }; case ( ?c ) { c * 1_000_000_000 } }; - print("El timestamp actual en nSeg es: +" # debug_show(currentTime)); - print("El inicio del vesting (CLIFF) es : " # debug_show(cliff)); - var period = 0; - var passedTime = 0: Int; - passedTime := cliff - currentTime; - while (currentTime > cliff + period * scheme.intervalDuration * NanosPerDay and period < Nat8.toNat(scheme.intervalQty)) { - print("El timestamp que da inicio al period " # debug_show(period) # " es " # debug_show(cliff + period * scheme.intervalDuration * NanosPerDay)); - print("Periodo " # Nat.toText(period) # " transcurrido!"); - period += 1; - }; + var period = Int.abs( + if (currentTime < cliff) { 0 } + else { + let p = ((currentTime - cliff) / (scheme.intervalDuration * NanosPerDay) + 1 ); + if ( p <= Nat8.toNat(scheme.intervalQty) ) { p } else { Nat8.toNat(scheme.intervalQty) } + } + ); return {period; cliff} }; @@ -331,7 +332,9 @@ shared ({ caller = _owner }) actor class CustomToken( switch (vst.scheme) { case (#timeBasedVesting(scheme)) { let {period; cliff} = getCurrentPeriodVesting(scheme); + let state: Tokenomic.VestingState = { + categoryName = vst.categoryName; currentPeriodOverTotal = (period, Nat8.toNat(scheme.intervalQty)); isBeforeCliff = period == 0; isFullyVested = period == Nat8.toNat(scheme.intervalQty); diff --git a/tour/minter-canister.mo b/tour/minter-canister.mo new file mode 100644 index 0000000..f29cfad --- /dev/null +++ b/tour/minter-canister.mo @@ -0,0 +1,81 @@ +import Principal "mo:base/Principal"; +import Blob "mo:base/Blob"; +import Text "mo:base/Text"; +import Ledger "icrc1-custom"; + +/// Este canister debe ser desplegado antes que el ledger + +shared ({ caller = Deployer}) actor class() = this { + + //////////////// Si crece mover a un archivo de tipos /////// + type Account = {owner: Principal; subaccount: ?Blob}; + + public type FeesDispersionTable = { + toBurnPermille: Nat; + receivers: [{account: Account; permille: Nat}] // Permille es a mil lo que porcentage a 100 + }; + + //////////////// Si crece mover a un modulo de Utils //////// + + func feesDispersionValidate(d: FeesDispersionTable): Bool { + var result = d.toBurnPermille; + for (r in d.receivers.vals()) { + result += r.permille + }; + result < 1000; + }; + + ///////////////////// Variables de estado /////////////////// + + let NULL_ADDRESS = "aaaaa-aa"; + let fees_collector_subaccount: ?Blob = ? "0\\1\\2\\3\\4\\5\\6\\7\\8\\9\\10\\11\\12\\13\\14\\15\\16\\17\\18\\19\\20\\21\\22\\23\\24\\25\\26\\27\\28\\29\\30\\31\\32"; + + stable var LedgerActor = actor(NULL_ADDRESS): Ledger.CustomToken; + stable var OldLedgerActor = actor(NULL_ADDRESS): Ledger.CustomToken; // Junto con restorePreviousLedger posiblemente innecesario + stable var feesDispersionTable: FeesDispersionTable = {toBurnPermille = 0; receivers = []}; + stable let fee_collector: Account = {owner = Principal.fromActor(this); subaccount = fees_collector_subaccount }; + + /////////////////////// Settings ////////////////////////////////////////////////////////////////// + + public shared ({ caller }) func setLedger(lerdgerCanisterId: Principal): async {#Ok; #Err: Text}{ + assert(caller == Deployer); + OldLedgerActor := LedgerActor; + LedgerActor := actor(Principal.toText(lerdgerCanisterId)): Ledger.CustomToken; + #Ok + }; + + public shared ({ caller }) func restorePreviousLedger(): async {#Ok; #Err: Text}{ + assert(caller == Deployer); + if (Principal.fromActor(OldLedgerActor) != Principal.fromText("aaaaa-aa")) { + LedgerActor := OldLedgerActor; + OldLedgerActor := actor(NULL_ADDRESS): Ledger.CustomToken; + return #Ok + }; + #Err("No hay registros de ledgers configurados previamente al actual") + }; + + public shared ({ caller }) func setFeesDispersionTable(d: FeesDispersionTable): async {#Ok; #Err: Text}{ + assert (caller == Deployer); + if ( not feesDispersionValidate(d)) { return #Err("La suma de todos los items debe ser menor o igual a 1000") }; + feesDispersionTable := d; + #Ok + }; + + ////////////////////////////////////// Getters ///////////////////////////////////////////// epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae + + public query func getFeeCollector(): async Account { + fee_collector + }; + + func ledgerReady(): Bool { + Principal.fromActor(LedgerActor) != Principal.fromText(NULL_ADDRESS) + }; + + + + + + + + +} \ No newline at end of file diff --git a/tour/tokenomic.mo b/tour/tokenomic.mo index 3145f57..e9001f5 100644 --- a/tour/tokenomic.mo +++ b/tour/tokenomic.mo @@ -18,6 +18,7 @@ module { }; public type VestingState = { + categoryName: Text; isBeforeCliff : Bool; isFullyVested : Bool; // Si ya se liberaron todos los tokens (currentTime >= endTime) currentPeriodOverTotal: (Nat, Nat); From 154eb4b7421651473ab54949b6f1547ed54c5c1f Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Sun, 16 Feb 2025 02:03:14 -0300 Subject: [PATCH 53/57] Fix optional max_supply --- scripts-sh/deploy-token.sh | 4 ++-- scripts-sh/test-token.sh | 7 ++++--- tour/icrc1-custom.mo | 4 ++-- tour/minter-canister.mo | 19 +++++++++++++++---- tour/types.mo | 2 +- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh index 93d36b8..bedc25f 100755 --- a/scripts-sh/deploy-token.sh +++ b/scripts-sh/deploy-token.sh @@ -166,7 +166,7 @@ dfx deploy tour --argument '( node_max_memory_size_bytes = null; controller_id = principal "'$Controller'"; }; - max_supply = 100_000_000_000_000 : nat; + max_supply = null; max_memo_length = opt (32 : nat); token_name = "$TOUR4"; feature_flags = opt record { icrc2 = true }; @@ -175,7 +175,7 @@ dfx deploy tour --argument '( record { metadata = vec {}; min_burn_amount = null; - max_supply = 100_000_000_000_000 : nat; + max_supply = null; distribution = opt record { allocations = vec { record { diff --git a/scripts-sh/test-token.sh b/scripts-sh/test-token.sh index be93ba1..2eb3f09 100755 --- a/scripts-sh/test-token.sh +++ b/scripts-sh/test-token.sh @@ -250,8 +250,7 @@ wait_with_countdown() { echo -e "\n[✅] Continuando con las pruebas\n" } -# Simular paso del tiempo hasta el cliff de Founders (180 segundos) -wait_with_countdown $((secondsWaiting + 1)) +wait_with_countdown $((secondsWaiting)) echo -e "\n\n============= PRUEBAS DE TRANSACCIONES EN CASOS LÍMITE PARA FOUNDERS =============\n" @@ -407,4 +406,6 @@ secondsWaiting=$((min_seconds - now)) echo "La proxima liberación de fondos corresponde a la categoria: $nextCategory" echo "Segundos restantes: $secondsWaiting" -wait_with_countdown $((secondsWaiting + 1)) \ No newline at end of file +wait_with_countdown $((secondsWaiting + 1)) + + diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index 0b6bedd..38a606d 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -26,7 +26,7 @@ shared ({ caller = _owner }) actor class CustomToken( ledgerArgs : Types.LedgerArgument, customArgs : { distribution : ?Tokenomic.InitialDistribution; - max_supply : Nat; + max_supply : ?Nat; metadata : [(Text, Types.MetadataValue)]; min_burn_amount : ?Nat; }, @@ -54,7 +54,7 @@ shared ({ caller = _owner }) actor class CustomToken( logo = null; max_accounts = null; max_memo = initArgs.max_memo_length; - max_supply = ?customArgs.max_supply; + max_supply = customArgs.max_supply; metadata = null; min_burn_amount = customArgs.min_burn_amount; name = ?initArgs.token_name; diff --git a/tour/minter-canister.mo b/tour/minter-canister.mo index f29cfad..5764aaf 100644 --- a/tour/minter-canister.mo +++ b/tour/minter-canister.mo @@ -28,7 +28,8 @@ shared ({ caller = Deployer}) actor class() = this { ///////////////////// Variables de estado /////////////////// let NULL_ADDRESS = "aaaaa-aa"; - let fees_collector_subaccount: ?Blob = ? "0\\1\\2\\3\\4\\5\\6\\7\\8\\9\\10\\11\\12\\13\\14\\15\\16\\17\\18\\19\\20\\21\\22\\23\\24\\25\\26\\27\\28\\29\\30\\31\\32"; + let fees_collector_subaccount: ?Blob = ? "FeeCollector00000000000000000000"; // El Blob del subaccount tiene que medir 32 Bytes + stable var LedgerActor = actor(NULL_ADDRESS): Ledger.CustomToken; stable var OldLedgerActor = actor(NULL_ADDRESS): Ledger.CustomToken; // Junto con restorePreviousLedger posiblemente innecesario @@ -61,14 +62,24 @@ shared ({ caller = Deployer}) actor class() = this { #Ok }; - ////////////////////////////////////// Getters ///////////////////////////////////////////// epvyw-ddnza-4wy4p-joxft-ciutt-s7pji-cfxm3-khwlb-x2tb7-uo7tc-xae + func ledgerReady(): Bool { + Principal.fromActor(LedgerActor) != Principal.fromText(NULL_ADDRESS) + }; + + + ////////////////////////////////////// Getters Fees dispersion section ///////////////////////////////////////////// public query func getFeeCollector(): async Account { fee_collector }; - func ledgerReady(): Bool { - Principal.fromActor(LedgerActor) != Principal.fromText(NULL_ADDRESS) + public shared func getFeeCollectorBalance(): async Nat { + await LedgerActor.icrc1_balance_of({owner = Principal.fromActor(this); subaccount = fees_collector_subaccount}) + }; + + public shared ({ caller }) func getFeesDispersionTable(): async FeesDispersionTable{ + assert(caller == Deployer); + feesDispersionTable }; diff --git a/tour/types.mo b/tour/types.mo index 4c75790..8fe8e4e 100644 --- a/tour/types.mo +++ b/tour/types.mo @@ -75,7 +75,7 @@ module { public type InitArgs = { decimals : Nat8; token_symbol : Text; - max_supply : Nat; + max_supply : ?Nat; transfer_fee : Nat; metadata : [(Text, MetadataValue)]; minting_account : Account; From 21445a9035115a0b0e2503b502727a9a62832ec8 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Mon, 17 Feb 2025 21:59:27 -0300 Subject: [PATCH 54/57] Rewards flow start --- backend/constants.mo | 1 + backend/main.mo | 63 +++++++++++++++++++++++++++++++++++++++-- tour/minter-canister.mo | 23 +++++++++++++-- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/backend/constants.mo b/backend/constants.mo index 35fb13d..4a0cbc8 100644 --- a/backend/constants.mo +++ b/backend/constants.mo @@ -9,6 +9,7 @@ module { public let UnauthorizedCaller = "Unauthorized user"; public let NotVerifiedUser = "The user is not verified"; public let NotUser = "Unregistered user"; + public let NotAdmin = "The caller is not admin"; public let Anonymous = "Anonymous caller is not allowed"; public let NotHostUser = "User is not Host User"; public let CallerIsNotrequester = "The caller does not match the reservation requester"; diff --git a/backend/main.mo b/backend/main.mo index dfd75d4..30d2d63 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -19,8 +19,11 @@ import { phash; nhash; n32hash; thash } "mo:map/Map"; import Indexer_icp "./indexer_icp_token"; import AccountIdentifier "mo:account-identifier"; +import Minter "../tour/minter-canister"; + import Types "types"; import msg "constants"; +import MinterCanister "../tour/minter-canister"; shared ({ caller = DEPLOYER }) actor class Triourism () = this { @@ -46,9 +49,17 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { type UpdateResult = Types.UpdateResult; type ResultHousingPaginate = {#Ok: {array: [HousingPreview]; hasNext: Bool}; #Err: Text}; - + type RewardRatio = { + #ICP_DIVIDES_TOUR: Nat; // Reward = Ammount * Relación. Ejemplo para ICPvTourRewardRatio = #ICP_DIVIDES_TOUR(2): txAmount = 100 ICP -> recompenza = 200 Tour + #TOUR_DIVIDES_ICP: Nat; // Reward = Ammount / Relación. Ejemplo para ICPvTourRewardRatio = #TOUR_DIVIDES_ICP(2): txAmount = 100 ICP -> recompenza = 50 Tour + }; + // stable let DEPLOYER = caller; + let NULL_ADDRESS = "aaaaa-aa"; + + stable var TourMinterCanister: Minter.Minter = actor(NULL_ADDRESS); + stable var TourLedgerCanisterID = NULL_ADDRESS; // /////////////// WARNING modificar estas variables en produccion a los valores reales //// let nanoSecPerDay = 30 * 1_000_000_000; // Test Transcurso acelerado de los dias @@ -63,8 +74,9 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { stable var TimeToPay = 15 * 1_000_000_000; // Tiempo en nanosegundos para confirmar la reserva mediante pago // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos stable var MinDaysBeforeCheckinForCancellation = 4; // Minimo de dias antes del checkin para cancelar una reserva pagando CancellationFeeCompensateBuyer - + stable var ICPvTourRewardRatio: RewardRatio = #TOUR_DIVIDES_ICP(2); + //////////////////////////////// Core Data Structures /////////////////////// stable let admins = Set.new(); @@ -267,7 +279,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { reviews = List.nil(); }; - /////////////////////////// Manage admins functions ///////////////////////////////// + /////////////////////////// Admins functions ///////////////////////////////// public shared ({ caller }) func addAdmin(p: Principal): async {#Ok; #Err} { if(not isAdmin(caller)){ @@ -296,6 +308,35 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; + public shared ({ caller }) func setMinter(m: Principal): async {#Ok; #Err: Text} { + if(not isAdmin(caller)) { return #Err(msg.NotAdmin) }; + TourMinterCanister := actor(Principal.toText(m)); + TourLedgerCanisterID := Principal.toText(await TourMinterCanister.getLedgerCanisterId()); + #Ok + }; + + public shared ({ caller }) func getMinterCanisterId(): async Principal { + if(not isAdmin(caller)) { return Principal.fromText(NULL_ADDRESS) }; + Principal.fromActor(TourMinterCanister); + + }; + + public shared ({ caller }) func getUserTourBalance(): async Nat { + if(TourLedgerCanisterID != NULL_ADDRESS){ + let ledger = actor(TourLedgerCanisterID): actor { + icrc1_balance_of : shared Principal -> async Nat + }; + return await ledger.icrc1_balance_of(caller); + }; + 0 + }; + + public shared ({ caller }) func setICPvTourRewardRatio(ratio: RewardRatio): async {#Ok; #Err}{ + if(not isAdmin(caller)) { return #Err }; + ICPvTourRewardRatio := ratio; + #Ok + }; + public shared ({ caller }) func updateSettings(config: Types.Settings): async Bool{ if(not isAdmin(caller)){ return false }; CancellationFeeCompensateBuyer := config.cancellationFeeCompensateBuyer; @@ -1210,6 +1251,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { if(reservation.requester != caller) { return #Err(msg.CallerIsNotRequester # Nat.toText(reservationId)) }; + let housing = Map.get(housings, nhash, reservation.housingId); switch housing { case null { #Err(msg.NotHousing)}; @@ -1234,6 +1276,21 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { reservation.housingId, { housing with calendary; reservationsPending } ); + /////// Rewards for confirm reservation //////////////////////////////////////////////////// + if (Principal.fromActor(TourMinterCanister) != Principal.fromText(NULL_ADDRESS)){ + ignore TourMinterCanister.rewardMint( + { + to = {owner = caller; subaccount = null}; + amount = switch ICPvTourRewardRatio{ + case (#ICP_DIVIDES_TOUR(r)) { Nat64.toNat(reservation.amount) * r }; + case (#TOUR_DIVIDES_ICP(r)) { Nat64.toNat(reservation.amount) / r }; + }; + created_at_time = ?Nat64.fromNat(Int.abs(now())); + memo = null; + } + ); + }; + //////////////////////////////////////////////////////////////////////////////////////////// #Ok(currentReservation) } } diff --git a/tour/minter-canister.mo b/tour/minter-canister.mo index 5764aaf..1f6c267 100644 --- a/tour/minter-canister.mo +++ b/tour/minter-canister.mo @@ -2,10 +2,12 @@ import Principal "mo:base/Principal"; import Blob "mo:base/Blob"; import Text "mo:base/Text"; import Ledger "icrc1-custom"; +import ICRC1 "mo:icrc1-mo/ICRC1"; -/// Este canister debe ser desplegado antes que el ledger +/// Este canister debe ser desplegado despues de Triourism y antes del Tour ledger + +shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal}) = this { -shared ({ caller = Deployer}) actor class() = this { //////////////// Si crece mover a un archivo de tipos /////// type Account = {owner: Principal; subaccount: ?Blob}; @@ -28,10 +30,12 @@ shared ({ caller = Deployer}) actor class() = this { ///////////////////// Variables de estado /////////////////// let NULL_ADDRESS = "aaaaa-aa"; + let fees_collector_subaccount: ?Blob = ? "FeeCollector00000000000000000000"; // El Blob del subaccount tiene que medir 32 Bytes stable var LedgerActor = actor(NULL_ADDRESS): Ledger.CustomToken; + stable let TriourismCanisterId = triourismCanisterId; stable var OldLedgerActor = actor(NULL_ADDRESS): Ledger.CustomToken; // Junto con restorePreviousLedger posiblemente innecesario stable var feesDispersionTable: FeesDispersionTable = {toBurnPermille = 0; receivers = []}; stable let fee_collector: Account = {owner = Principal.fromActor(this); subaccount = fees_collector_subaccount }; @@ -66,6 +70,10 @@ shared ({ caller = Deployer}) actor class() = this { Principal.fromActor(LedgerActor) != Principal.fromText(NULL_ADDRESS) }; + // func isAllowedToMint(p: Principal): Bool { + // p == TriourismCanisterId or false + // }; + ////////////////////////////////////// Getters Fees dispersion section ///////////////////////////////////////////// @@ -82,6 +90,17 @@ shared ({ caller = Deployer}) actor class() = this { feesDispersionTable }; + public query func getLedgerCanisterId(): async Principal { + Principal.fromActor(LedgerActor); + }; + + /////////////////////////////////////// Mint section //////////////////////////////////////////////////////////////// + + public shared ({ caller }) func rewardMint(args: ICRC1.Mint): async ICRC1.TransferResult{ + assert(caller == TriourismCanisterId and ledgerReady()); + await LedgerActor.mint(args); + }; + From bb37a458c4ff06faf312061b7b1beda87744e4ed Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Tue, 18 Feb 2025 17:54:41 -0300 Subject: [PATCH 55/57] Basic Mint Reward flow --- backend/main.mo | 47 ++++++++++++++++--------------- dfx.json | 4 +++ scripts-sh/deploy-integral.sh | 25 ++++++++++++++++ scripts-sh/deploy-token.sh | 30 +++++++------------- scripts-sh/deploy-with-content.sh | 12 ++++---- tour/minter-canister.mo | 10 +++++-- 6 files changed, 76 insertions(+), 52 deletions(-) create mode 100644 scripts-sh/deploy-integral.sh diff --git a/backend/main.mo b/backend/main.mo index 30d2d63..4a7fe4a 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -23,8 +23,6 @@ import Minter "../tour/minter-canister"; import Types "types"; import msg "constants"; -import MinterCanister "../tour/minter-canister"; - shared ({ caller = DEPLOYER }) actor class Triourism () = this { @@ -69,11 +67,11 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { ////////////////////////////////// Global Configuration Parameters ////////////////////////////////////// - stable var CancellationFeeCompensateBuyer: Nat64 = 5; //Percentage added to the buyer's refund - stable var ReservationFee: Nat64 = 10; //Percentage of the total reservation price - stable var TimeToPay = 15 * 1_000_000_000; // Tiempo en nanosegundos para confirmar la reserva mediante pago - // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos - stable var MinDaysBeforeCheckinForCancellation = 4; // Minimo de dias antes del checkin para cancelar una reserva pagando CancellationFeeCompensateBuyer + stable var CancellationFeeCompensateBuyer: Nat64 = 5; // Percentage added to the buyer's refund + stable var ReservationFee: Nat64 = 10; // Percentage of the total reservation price + stable var TimeToPay = 15 * 1_000_000_000; // Tiempo en nanosegundos para confirmar la reserva mediante pago + // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos + stable var MinDaysBeforeCheckinForCancellation = 4; // Minimo de dias antes del checkin para cancelar una reserva pagando CancellationFeeCompensateBuyer stable var ICPvTourRewardRatio: RewardRatio = #TOUR_DIVIDES_ICP(2); @@ -321,12 +319,12 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; - public shared ({ caller }) func getUserTourBalance(): async Nat { + public shared ({ caller }) func getUserTourBalance(subaccount: ?Blob): async Nat { if(TourLedgerCanisterID != NULL_ADDRESS){ let ledger = actor(TourLedgerCanisterID): actor { - icrc1_balance_of : shared Principal -> async Nat + icrc1_balance_of : shared {owner: Principal; subaccount: ?Blob} -> async Nat }; - return await ledger.icrc1_balance_of(caller); + return await ledger.icrc1_balance_of({owner = caller; subaccount}); }; 0 }; @@ -1009,7 +1007,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { var reservationsPendingForTheProvidedID: [Nat] = []; for ((id, reservation) in Map.toArray(reservationsPendingConfirmation).vals()) { if (now() > reservation.date + TimeToPay) { - print("Solicitud de reserva " # Nat.toText(id) # " Eliminada por timeout"); + // print("Solicitud de reserva " # Nat.toText(id) # " Eliminada por timeout"); ignore Map.remove(reservationsPendingConfirmation, nhash, id); let housing = Map.get(housings, nhash, reservation.housingId); switch housing { @@ -1019,8 +1017,8 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { housing.reservationsPending, func x = x != id ); - print("Id de solicitud " # Nat.toText(id) # " borrado\nreservas pendientes: "); - print(debug_show(reservationsPending)); // revisar el filter + // print("Id de solicitud " # Nat.toText(id) # " borrado\nreservas pendientes: "); + // print(debug_show(reservationsPending)); // revisar el filter if (id == housingId ) { reservationsPendingForTheProvidedID := reservationsPending }; ignore Map.put(housings, nhash, reservation.housingId, {housing with reservationsPending}); } @@ -1040,7 +1038,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { // Si la solicitud es antigua se elimina del map y se devuelte el id para limpiar if ( now() > reservation.date + TimeToPay) { ignore Map.remove(reservationsPendingConfirmation, nhash, id); - print("reserva " # Nat.toText(id) # " eliminada"); + // print("reserva " # Nat.toText(id) # " eliminada"); } else { bufferReservations.add(reservation); bufferReservUpdate.add(id); @@ -1139,7 +1137,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { func (a, b) = if (a.minimumDays < b.minimumDays) { #less } else { #greater } ); for(discount in discounts.vals()){ - print(debug_show(discount)); + // print(debug_show(discount)); if(days < discount.minimumDays) { return Nat64.fromNat((price.base * days) - (price.base * days * currentDiscount / 100)) }; currentDiscount := discount.discount; }; @@ -1267,7 +1265,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { housing.calendary.reservations.size() + 1, func x = if (x == 0) { currentReservation } else { housing.calendary.reservations[x - 1] }) }; - print(debug_show(calendary)); + // print(debug_show(calendary)); let reservationsPending = Array.filter(housing.reservationsPending, func x = x !=reservationId ); ignore Map.put(reservationsHistory, nhash, reservationId, currentReservation); ignore Map.put( @@ -1276,21 +1274,24 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { reservation.housingId, { housing with calendary; reservationsPending } ); - /////// Rewards for confirm reservation //////////////////////////////////////////////////// + ///// Rewards for confirm reservation //////////////////////////////////////////////////// if (Principal.fromActor(TourMinterCanister) != Principal.fromText(NULL_ADDRESS)){ - ignore TourMinterCanister.rewardMint( + let mintAmount = switch ICPvTourRewardRatio{ + case (#ICP_DIVIDES_TOUR(r)) { Nat64.toNat(reservation.amount) * r }; + case (#TOUR_DIVIDES_ICP(r)) { Nat64.toNat(reservation.amount) / r }; + }; + print("Mintenado recompenza: " # debug_show(mintAmount) # " Tour"); + + ignore await TourMinterCanister.rewardMint( // { to = {owner = caller; subaccount = null}; - amount = switch ICPvTourRewardRatio{ - case (#ICP_DIVIDES_TOUR(r)) { Nat64.toNat(reservation.amount) * r }; - case (#TOUR_DIVIDES_ICP(r)) { Nat64.toNat(reservation.amount) / r }; - }; + amount = mintAmount; created_at_time = ?Nat64.fromNat(Int.abs(now())); memo = null; } ); }; - //////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////// #Ok(currentReservation) } } diff --git a/dfx.json b/dfx.json index a783d88..b20cef8 100644 --- a/dfx.json +++ b/dfx.json @@ -27,6 +27,10 @@ "candid": "https://raw.githubusercontent.com/dfinity/ic/a62848817cec7ae50618a87a526c85d020283fd9/rs/ledger_suite/icrc1/index-ng/index-ng.did", "wasm": "https://download.dfinity.systems/ic/a62848817cec7ae50618a87a526c85d020283fd9/canisters/ic-icrc1-index-ng.wasm.gz" }, + "icrc1_minter_canister": { + "type": "motoko", + "main": "tour/minter-canister.mo" + }, "test": { "type": "motoko", diff --git a/scripts-sh/deploy-integral.sh b/scripts-sh/deploy-integral.sh new file mode 100644 index 0000000..a84c607 --- /dev/null +++ b/scripts-sh/deploy-integral.sh @@ -0,0 +1,25 @@ + +dfx identity new triourism +dfx identity use triourism + +# Deploy del canister principal +dfx deploy backend +export backend=$(dfx canister id backend) + +# Deploy minster canister +dfx deploy icrc1_minter_canister --argument '( + record {triourismCanisterId = principal "'$backend'"} +)' +export minterCanister=$(dfx canister id icrc1_minter_canister) + +# Referencia al minter en el backend +echo "Seteando referencia al canister minter en el canister backend..." +dfx canister call backend setMinter '(principal "'$minterCanister'")' + +echo -e "\n\nSiguientes pasos: + 1: Deploy ledger + 2: Referenciar el ledger en el canister minter + dfx canister call icrc1_minter_canister setLedger '(principal "'"$(dfx canister id icrc1_ledger_canister)"'")' +" + +# diff --git a/scripts-sh/deploy-token.sh b/scripts-sh/deploy-token.sh index bedc25f..2f354e5 100755 --- a/scripts-sh/deploy-token.sh +++ b/scripts-sh/deploy-token.sh @@ -105,34 +105,24 @@ dfx identity new 0000Founder03 dfx identity use 0000Founder03 export Founder03=$(dfx identity get-principal) -dfx identity new 0000Minter -dfx identity use 0000Minter -export Minter=$(dfx identity get-principal) - -dfx identity new 0000FeeCollector -dfx identity use 0000FeeCollector -export FeeCollector=$(dfx identity get-principal) dfx identity new 0000Controller dfx identity use 0000Controller export Controller=$(dfx identity get-principal) - - - -dfx identity new 0000Deployer -dfx identity use 0000Deployer +dfx identity use triourism export deployer=$(dfx identity get-principal) +export minterCanister=$(dfx canister id icrc1_minter_canister) timestamp=$(date +%s) -cliffFounders=$((timestamp + 83)) #Primos -cliffInvestors=$((timestamp + 120)) #No Primos para que no coincida ningun release de una categoria con uno de la otra +cliffFounders=$((timestamp + 83)) +cliffInvestors=$((timestamp + 120)) dfx deploy tour --argument '( variant { Init = record { decimals = 8 : nat8; - token_symbol = "TOUR4"; + token_symbol = "TOUR"; transfer_fee = 10_000 : nat; metadata = vec { record { @@ -142,18 +132,18 @@ dfx deploy tour --argument '( }; }; record { "icrc1:decimals"; variant { Nat = 8 : nat } }; - record { "icrc1:name"; variant { Text = "TOUR4" } }; - record { "icrc1:symbol"; variant { Text = "$TOUR4" } }; + record { "icrc1:name"; variant { Text = "TOUR" } }; + record { "icrc1:symbol"; variant { Text = "$TOUR" } }; record { "icrc1:fee"; variant { Nat = 10_000 : nat } }; record { "icrc1:max_memo_length"; variant { Nat = 32 : nat } }; }; minting_account = record { - owner = principal "'$Minter'"; + owner = principal "'$minterCanister'"; subaccount = null; }; initial_balances = vec {}; fee_collector_account = opt record { - owner = principal "'$Minter'"; + owner = principal "'$minterCanister'"; subaccount = opt blob "FeeCollector00000000000000000000"; }; archive_options = record { @@ -168,7 +158,7 @@ dfx deploy tour --argument '( }; max_supply = null; max_memo_length = opt (32 : nat); - token_name = "$TOUR4"; + token_name = "$TOUR"; feature_flags = opt record { icrc2 = true }; } }, diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index bce8015..9922c7f 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -1,8 +1,8 @@ #!/bin/bash # ------------ Usuario deployer ------------ -dfx identity new 0000TestUser0 -dfx identity use 0000TestUser0 -dfx deploy backend +# dfx identity new 0000TestUser0 +# dfx identity use 0000TestUser0 +# dfx deploy backend # ------------ Usuario Host 1 -------------- dfx identity new 0000TestUser1 @@ -228,8 +228,8 @@ dfx canister call backend confirmReservation '(record { dfx identity use 0000TestUser6 dfx canister call backend requestReservation '(record { housingId = 1; - checkIn = 6; - checkOut = 7; + checkIn = 21; + checkOut = 31; guest = "Carlos"; email = "carlos@gmil.com"; phone = 536657090 @@ -239,7 +239,7 @@ dfx canister call backend requestReservation '(record { # ------------ Usuario 6 confirma la reserva dfx canister call backend confirmReservation '(record { - reservationId = 3; + reservationId = 10; txData = record { to = "walletHousingInRequest"; amount = 4_000_000_000; diff --git a/tour/minter-canister.mo b/tour/minter-canister.mo index 1f6c267..9e6d948 100644 --- a/tour/minter-canister.mo +++ b/tour/minter-canister.mo @@ -3,8 +3,10 @@ import Blob "mo:base/Blob"; import Text "mo:base/Text"; import Ledger "icrc1-custom"; import ICRC1 "mo:icrc1-mo/ICRC1"; +import { print } "mo:base/Debug"; -/// Este canister debe ser desplegado despues de Triourism y antes del Tour ledger +/// Este canister debe ser desplegado despues de Triourism y antes del Tour ledger. +// Luego de desplegado el Ledger ejecutar setLedger con el ledeger canister ID shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal}) = this { @@ -38,7 +40,7 @@ shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal stable let TriourismCanisterId = triourismCanisterId; stable var OldLedgerActor = actor(NULL_ADDRESS): Ledger.CustomToken; // Junto con restorePreviousLedger posiblemente innecesario stable var feesDispersionTable: FeesDispersionTable = {toBurnPermille = 0; receivers = []}; - stable let fee_collector: Account = {owner = Principal.fromActor(this); subaccount = fees_collector_subaccount }; + /////////////////////// Settings ////////////////////////////////////////////////////////////////// @@ -78,7 +80,7 @@ shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal ////////////////////////////////////// Getters Fees dispersion section ///////////////////////////////////////////// public query func getFeeCollector(): async Account { - fee_collector + {owner = Principal.fromActor(this); subaccount = fees_collector_subaccount} }; public shared func getFeeCollectorBalance(): async Nat { @@ -97,7 +99,9 @@ shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal /////////////////////////////////////// Mint section //////////////////////////////////////////////////////////////// public shared ({ caller }) func rewardMint(args: ICRC1.Mint): async ICRC1.TransferResult{ + print("Minter recibiendo llamada"); assert(caller == TriourismCanisterId and ledgerReady()); + print("Llamada aceptada"); await LedgerActor.mint(args); }; From c8674fee558ccd19ca3fca66301454c8c43c0623 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 19 Feb 2025 17:09:47 -0300 Subject: [PATCH 56/57] mint rewards for guest and host user --- backend/main.mo | 17 +++++++++++++---- .../{deploy-integral.sh => deploy-general.sh} | 9 +++++---- scripts-sh/deploy-with-content.sh | 3 +-- tour/icrc1-custom.mo | 5 ++--- 4 files changed, 21 insertions(+), 13 deletions(-) rename scripts-sh/{deploy-integral.sh => deploy-general.sh} (81%) diff --git a/backend/main.mo b/backend/main.mo index 4a7fe4a..aa9485e 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -73,7 +73,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos stable var MinDaysBeforeCheckinForCancellation = 4; // Minimo de dias antes del checkin para cancelar una reserva pagando CancellationFeeCompensateBuyer - stable var ICPvTourRewardRatio: RewardRatio = #TOUR_DIVIDES_ICP(2); + stable var ICPvTourRewardRatio: RewardRatio = #TOUR_DIVIDES_ICP(2); //////////////////////////////// Core Data Structures /////////////////////// @@ -315,8 +315,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { public shared ({ caller }) func getMinterCanisterId(): async Principal { if(not isAdmin(caller)) { return Principal.fromText(NULL_ADDRESS) }; - Principal.fromActor(TourMinterCanister); - + Principal.fromActor(TourMinterCanister); }; public shared ({ caller }) func getUserTourBalance(subaccount: ?Blob): async Nat { @@ -1282,7 +1281,7 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { }; print("Mintenado recompenza: " # debug_show(mintAmount) # " Tour"); - ignore await TourMinterCanister.rewardMint( // + let mintToGuest = TourMinterCanister.rewardMint( // { to = {owner = caller; subaccount = null}; amount = mintAmount; @@ -1290,6 +1289,16 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { memo = null; } ); + let mintToHost = TourMinterCanister.rewardMint( // + { + to = {owner = housing.owner; subaccount = null}; + amount = mintAmount; + created_at_time = ?Nat64.fromNat(Int.abs(now())); + memo = null; + } + ); + ignore await mintToGuest; + ignore await mintToHost; }; ////////////////////////////////////////////////////////////////////////////////////////// #Ok(currentReservation) diff --git a/scripts-sh/deploy-integral.sh b/scripts-sh/deploy-general.sh similarity index 81% rename from scripts-sh/deploy-integral.sh rename to scripts-sh/deploy-general.sh index a84c607..83eee78 100644 --- a/scripts-sh/deploy-integral.sh +++ b/scripts-sh/deploy-general.sh @@ -6,20 +6,21 @@ dfx identity use triourism dfx deploy backend export backend=$(dfx canister id backend) -# Deploy minster canister +# Deploy del Minter Canister dfx deploy icrc1_minter_canister --argument '( record {triourismCanisterId = principal "'$backend'"} )' export minterCanister=$(dfx canister id icrc1_minter_canister) -# Referencia al minter en el backend +# Referencia al Minter en el backend echo "Seteando referencia al canister minter en el canister backend..." dfx canister call backend setMinter '(principal "'$minterCanister'")' echo -e "\n\nSiguientes pasos: - 1: Deploy ledger + 1: Deploy del canister Ledger + a: 2: Referenciar el ledger en el canister minter - dfx canister call icrc1_minter_canister setLedger '(principal "'"$(dfx canister id icrc1_ledger_canister)"'")' + dfx canister call icrc1_minter_canister setLedger '(principal "'$(dfx canister id tour)'")' " # diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index 9922c7f..8083649 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -236,10 +236,9 @@ dfx canister call backend requestReservation '(record { })' - # ------------ Usuario 6 confirma la reserva dfx canister call backend confirmReservation '(record { - reservationId = 10; + reservationId = 4; txData = record { to = "walletHousingInRequest"; amount = 4_000_000_000; diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index 38a606d..32453e9 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -185,6 +185,7 @@ shared ({ caller = _owner }) actor class CustomToken( //////////////// Initial Distribution //////////////// + func distribution(allocations : [Tokenomic.Allocation]): async {#Ok; #Err: Text} { if (distributionComplete) { return #Err("Distribution is already complete")}; for (distItem in allocations.vals()) { @@ -229,13 +230,10 @@ shared ({ caller = _owner }) actor class CustomToken( ////// Deploy de canister indexer y distribucion inicial /////// - - public shared ({ caller }) func initialize() : async { #Ok; #Err : Text } { assert (caller == _owner); let indexerCanisterId = await deploy_indexer(); print("Indexer canister deployed at " # debug_show (indexerCanisterId)); - switch ledgerArgs { case (#Init(_)) { switch (customArgs.distribution) { @@ -452,6 +450,7 @@ shared ({ caller = _owner }) actor class CustomToken( // Deposit cycles into this canister. public shared func deposit_cycles() : async () { + let amount = ExperimentalCycles.available(); let accepted = ExperimentalCycles.accept(amount); assert (accepted == amount); From 00613f6abb3042e1e3886e4e84ce9d0e69f7d7d0 Mon Sep 17 00:00:00 2001 From: ariel robotti Date: Wed, 26 Feb 2025 00:37:40 -0300 Subject: [PATCH 57/57] change RewardRatio; Change Mint Token for transfer from poolRewards --- backend/main.mo | 50 +++++++------- package.json | 1 + scripts-sh/deploy-general.sh | 3 +- scripts-sh/deploy-with-content.sh | 4 +- tour/icrc1-custom.mo | 109 +++++++++++++++--------------- tour/minter-canister.mo | 79 ++++++++++++++++++---- 6 files changed, 150 insertions(+), 96 deletions(-) mode change 100644 => 100755 scripts-sh/deploy-general.sh diff --git a/backend/main.mo b/backend/main.mo index aa9485e..0460aa6 100644 --- a/backend/main.mo +++ b/backend/main.mo @@ -18,6 +18,7 @@ import Set "mo:map/Set"; import { phash; nhash; n32hash; thash } "mo:map/Map"; import Indexer_icp "./indexer_icp_token"; import AccountIdentifier "mo:account-identifier"; +import IC "ic:aaaaa-aa"; import Minter "../tour/minter-canister"; @@ -73,7 +74,8 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { // stable var TimeToPay = 30 * 60 * 1_000_000_000; // Tiempo sugerido 30 minutos stable var MinDaysBeforeCheckinForCancellation = 4; // Minimo de dias antes del checkin para cancelar una reserva pagando CancellationFeeCompensateBuyer - stable var ICPvTourRewardRatio: RewardRatio = #TOUR_DIVIDES_ICP(2); + stable var ICPvTourRewardRatio: RewardRatio = #TOUR_DIVIDES_ICP(2); // Cambiar por un Nat + stable var RewardRatio: Nat64 = 1000; // eg. RewardRatio = 1000; -> 1000 USD = 1 Tour //////////////////////////////// Core Data Structures /////////////////////// @@ -334,6 +336,12 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { #Ok }; + public shared ({ caller }) func serRewardRatio(v: Nat64): async {#Ok; #Err}{ + if(not isAdmin(caller)) { return #Err }; + RewardRatio := v; + #Ok + }; + public shared ({ caller }) func updateSettings(config: Types.Settings): async Bool{ if(not isAdmin(caller)){ return false }; CancellationFeeCompensateBuyer := config.cancellationFeeCompensateBuyer; @@ -1239,6 +1247,12 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } }; + func calculateReward(amount: Nat64, coin: Text): async Nat64 { + let urlApi = "https://api3.binance.com/api/v3/ticker/price?symbol=" # Text.toUppercase(coin) # "USDT"; + //TODO convertir amount al equivalente en usdt + 1 * amount / RewardRatio; + }; + public shared ({ caller }) func confirmReservation({reservationId: Nat; txData: DataTransaction}): async {#Ok: Reservation; #Err: Text} { let reservation = Map.remove(reservationsPendingConfirmation, nhash, reservationId); switch reservation { @@ -1274,31 +1288,15 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { { housing with calendary; reservationsPending } ); ///// Rewards for confirm reservation //////////////////////////////////////////////////// + // TODO calcular recompenza en base al aquialente en dolares de reservation.amount if (Principal.fromActor(TourMinterCanister) != Principal.fromText(NULL_ADDRESS)){ - let mintAmount = switch ICPvTourRewardRatio{ - case (#ICP_DIVIDES_TOUR(r)) { Nat64.toNat(reservation.amount) * r }; - case (#TOUR_DIVIDES_ICP(r)) { Nat64.toNat(reservation.amount) / r }; - }; - print("Mintenado recompenza: " # debug_show(mintAmount) # " Tour"); - - let mintToGuest = TourMinterCanister.rewardMint( // - { - to = {owner = caller; subaccount = null}; - amount = mintAmount; - created_at_time = ?Nat64.fromNat(Int.abs(now())); - memo = null; - } - ); - let mintToHost = TourMinterCanister.rewardMint( // - { - to = {owner = housing.owner; subaccount = null}; - amount = mintAmount; - created_at_time = ?Nat64.fromNat(Int.abs(now())); - memo = null; - } - ); - ignore await mintToGuest; - ignore await mintToHost; + let rewardAmount = await calculateReward(reservation.amount, "ICP"); + print("Mintenado recompenza: " # debug_show(rewardAmount) # " Tour"); + let accounts = [ + {owner = caller; subaccount = null }, + {owner = housing.owner; subaccount = null} + ]; + ignore await TourMinterCanister.issueRewards({accounts; amount = rewardAmount }) }; ////////////////////////////////////////////////////////////////////////////////////////// #Ok(currentReservation) @@ -1396,7 +1394,5 @@ shared ({ caller = DEPLOYER }) actor class Triourism () = this { } } }; - - }; diff --git a/package.json b/package.json index 0754a81..b8aec4c 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ ], "scripts": { "deploy-with-content": "chmod +x scripts-sh/deploy-with-content.sh; ./scripts-sh/deploy-with-content.sh", + "secuencial-deploy": "chmod +x scripts-sh/deploy-general.sh; ./scripts-sh/deploy-general.sh", "deploy-token": "chmod +x scripts-sh/deploy-token.sh; ./scripts-sh/deploy-token.sh", "test-token": "chmod +x scripts-sh/test-token.sh; ./scripts-sh/test-token.sh", "build": "turbo run build", diff --git a/scripts-sh/deploy-general.sh b/scripts-sh/deploy-general.sh old mode 100644 new mode 100755 index 83eee78..0af8c1a --- a/scripts-sh/deploy-general.sh +++ b/scripts-sh/deploy-general.sh @@ -18,9 +18,10 @@ dfx canister call backend setMinter '(principal "'$minterCanister'")' echo -e "\n\nSiguientes pasos: 1: Deploy del canister Ledger - a: 2: Referenciar el ledger en el canister minter dfx canister call icrc1_minter_canister setLedger '(principal "'$(dfx canister id tour)'")' + 3: Referenciar el canister Minteren el canister principal + dfx canister call backend setMinter '(principal "'$(dfx canister id icrc1_minter_canister)'")' " # diff --git a/scripts-sh/deploy-with-content.sh b/scripts-sh/deploy-with-content.sh index 8083649..9aaeb9d 100755 --- a/scripts-sh/deploy-with-content.sh +++ b/scripts-sh/deploy-with-content.sh @@ -1,5 +1,5 @@ -#!/bin/bash -# ------------ Usuario deployer ------------ +# #!/bin/bash +# # ------------ Usuario deployer ------------ # dfx identity new 0000TestUser0 # dfx identity use 0000TestUser0 # dfx deploy backend diff --git a/tour/icrc1-custom.mo b/tour/icrc1-custom.mo index 32453e9..434130a 100644 --- a/tour/icrc1-custom.mo +++ b/tour/icrc1-custom.mo @@ -35,9 +35,9 @@ shared ({ caller = _owner }) actor class CustomToken( ///////////////////////////////// WARNING /////////////////////////////////////// // let NanosPerDay = 24 * 60 * 60 * 1_000_000_000; // Valor definitivo - let NanosPerDay = 2 * 1_000_000_000; // Valor para pruebas 1 dia = 4 segundo + let NanosPerDay = 2 * 1_000_000_000; // Valor para pruebas 1 dia = 4 segundo - stable var distributionTimestamp: Int = 0; + stable var distributionTimestamp : Int = 0; ////////////////////////////////////////////////////////////////////////////////// @@ -84,7 +84,7 @@ shared ({ caller = _owner }) actor class CustomToken( ///////////////////////////////////// Flags ////////////////////////////////////// stable var distributionComplete = false; - stable var vestingSchemes: [{categoryName: Text; scheme : Tokenomic.VestingScheme; ended : Bool }] = []; + stable var vestingSchemes : [{ categoryName : Text; scheme : Tokenomic.VestingScheme; ended : Bool }] = []; stable var isLedgerReady = false; ////// @@ -185,18 +185,16 @@ shared ({ caller = _owner }) actor class CustomToken( //////////////// Initial Distribution //////////////// - - func distribution(allocations : [Tokenomic.Allocation]): async {#Ok; #Err: Text} { - if (distributionComplete) { return #Err("Distribution is already complete")}; + func distribution(allocations : [Tokenomic.Allocation]) : async { #Ok; #Err : Text } { + if (distributionComplete) { return #Err("Distribution is already complete") }; for (distItem in allocations.vals()) { - vestingSchemes := Array.tabulate<{categoryName: Text; scheme: Tokenomic.VestingScheme; ended : Bool }>( - vestingSchemes.size() + 1, - func i = if (i == vestingSchemes.size()) { - {categoryName = distItem.categoryName; scheme = distItem.vestingScheme; ended = false} - } - else { - vestingSchemes[i] - } + vestingSchemes := Array.tabulate<{ categoryName : Text; scheme : Tokenomic.VestingScheme; ended : Bool }>( + vestingSchemes.size() + 1, + func i = if (i == vestingSchemes.size()) { + { categoryName = distItem.categoryName; scheme = distItem.vestingScheme; ended = false }; + } else { + vestingSchemes[i]; + }, ); for ({ allocatedAmount; hasVesting; owner } in distItem.holders.vals()) { let mintArgs = { @@ -218,14 +216,14 @@ shared ({ caller = _owner }) actor class CustomToken( holdersVesting, phash, owner, - { value = allocatedAmount; schemeIndex = vestingSchemes.size() - 1}, + { value = allocatedAmount; schemeIndex = vestingSchemes.size() - 1 }, ); }; }; }; distributionComplete := true; distributionTimestamp := now(); - #Ok + #Ok; }; ////// Deploy de canister indexer y distribucion inicial /////// @@ -238,7 +236,7 @@ shared ({ caller = _owner }) actor class CustomToken( case (#Init(_)) { switch (customArgs.distribution) { case (?dist) { - return await distribution(dist.allocations); + return await distribution(dist.allocations); }; case (_) { return #Ok }; }; @@ -252,36 +250,40 @@ shared ({ caller = _owner }) actor class CustomToken( stable let holdersVesting = Map.new(); - func calculateBlockedAmount(p: Principal): Nat { + func calculateBlockedAmount(p : Principal) : Nat { switch (Map.get(holdersVesting, phash, p)) { case null { return 0 }; case (?{ value; schemeIndex }) { - if(vestingSchemes[schemeIndex].ended) { + if (vestingSchemes[schemeIndex].ended) { return 0; } else { switch (vestingSchemes[schemeIndex].scheme) { case (#timeBasedVesting(scheme)) { let { period } = getCurrentPeriodVesting(scheme); - if(period == 0) { - return value - } - else { - return value - ((value * period) / Nat8.toNat(scheme.intervalQty)) - } + if (period == 0) { + return value; + } else { + return value - ((value * period) / Nat8.toNat(scheme.intervalQty)); + }; }; - case (_){ return 0 } // Otros esquemas a implementar + case (_) { return 0 } // Otros esquemas a implementar }; - value - } + value; + }; }; - }; + }; }; - func checkVestingRestrictions(caller : Principal, trx : ICRC1.TransferArgs) : { #Ok; #Err : Types.TransferError; } { + func checkVestingRestrictions(caller : Principal, trx : ICRC1.TransferArgs) : { + #Ok; + #Err : Types.TransferError; + } { + // TODO Se puede agregar un valor aleatorio y acotado a cada usuario con vesting para evitar + // que todos los usuarios de una misma categoria queden con sus tokens liberados al mismo tiempo let balance = icrc1().balance_of({ owner = caller; subaccount = null }); let blocked_amount = calculateBlockedAmount(caller); - if (blocked_amount == 0 ) { return #Ok }; + if (blocked_amount == 0) { return #Ok }; if (balance >= blocked_amount + trx.amount + icrc1().fee()) { #Ok; } else { @@ -308,51 +310,52 @@ shared ({ caller = _owner }) actor class CustomToken( // icrc1().balance_of({ owner = caller; subaccount = null }) - get; // }; - func getCurrentPeriodVesting (scheme: Tokenomic.TimeBasedVesting) : {period: Nat; cliff: Int} { + func getCurrentPeriodVesting(scheme : Tokenomic.TimeBasedVesting) : { period : Nat; cliff : Int } { let currentTime = Int.abs(now()); let cliff = switch (scheme.cliff) { case null { distributionTimestamp }; - case ( ?c ) { c * 1_000_000_000 } + case (?c) { c * 1_000_000_000 }; }; - var period = Int.abs( - if (currentTime < cliff) { 0 } - else { - let p = ((currentTime - cliff) / (scheme.intervalDuration * NanosPerDay) + 1 ); - if ( p <= Nat8.toNat(scheme.intervalQty) ) { p } else { Nat8.toNat(scheme.intervalQty) } + var period = Int.abs( + if (currentTime < cliff) { 0 } else { + let p = ((currentTime - cliff) / (scheme.intervalDuration * NanosPerDay) + 1); + if (p <= Nat8.toNat(scheme.intervalQty)) { p } else { Nat8.toNat(scheme.intervalQty) }; } ); - return {period; cliff} + return { period; cliff }; }; - public query func vestingsStatus(): async [Tokenomic.VestingState] { + public query func vestingsStatus() : async [Tokenomic.VestingState] { let states = Buffer.fromArray([]); - for (vst in vestingSchemes.vals()){ + for (vst in vestingSchemes.vals()) { switch (vst.scheme) { case (#timeBasedVesting(scheme)) { - let {period; cliff} = getCurrentPeriodVesting(scheme); - - let state: Tokenomic.VestingState = { + let { period; cliff } = getCurrentPeriodVesting(scheme); + + let vState : Tokenomic.VestingState = { categoryName = vst.categoryName; currentPeriodOverTotal = (period, Nat8.toNat(scheme.intervalQty)); isBeforeCliff = period == 0; isFullyVested = period == Nat8.toNat(scheme.intervalQty); - nextReleaseTime = if(period == Nat8.toNat(scheme.intervalQty)) { null } else { ? (cliff + period * scheme.intervalDuration * NanosPerDay) }; - remainingAmount = 0; - vestedAmount = 0; + nextReleaseTime = if (period == Nat8.toNat(scheme.intervalQty)) { null } else { + ?(cliff + period * scheme.intervalDuration * NanosPerDay); + }; + remainingAmount = 0; // TODO + vestedAmount = 0; // TODO }; - states.add(state); + states.add(vState); }; - case _{ }; - }; + case _ {}; + }; }; - Buffer.toArray(states) + Buffer.toArray(states); }; /// Functions for the ICRC1 token standard - public shared query func is_ledger_ready(): async Bool { - isLedgerReady + public shared query func is_ledger_ready() : async Bool { + isLedgerReady; }; public shared query func icrc1_name() : async Text { diff --git a/tour/minter-canister.mo b/tour/minter-canister.mo index 9e6d948..b02fbec 100644 --- a/tour/minter-canister.mo +++ b/tour/minter-canister.mo @@ -1,9 +1,12 @@ import Principal "mo:base/Principal"; import Blob "mo:base/Blob"; import Text "mo:base/Text"; +import { now } "mo:base/Time"; import Ledger "icrc1-custom"; import ICRC1 "mo:icrc1-mo/ICRC1"; -import { print } "mo:base/Debug"; +// import { print } "mo:base/Debug"; +import Int "mo:base/Int"; +import Nat64 "mo:base/Nat64"; /// Este canister debe ser desplegado despues de Triourism y antes del Tour ledger. // Luego de desplegado el Ledger ejecutar setLedger con el ledeger canister ID @@ -29,11 +32,33 @@ shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal result < 1000; }; + func fee(): async Nat { + switch _fee { + case null { await LedgerActor.icrc1_fee() }; + case ( ?f ) { + _fee := ?f; + f + } + } + }; + ///////////////////// Variables de estado /////////////////// let NULL_ADDRESS = "aaaaa-aa"; - let fees_collector_subaccount: ?Blob = ? "FeeCollector00000000000000000000"; // El Blob del subaccount tiene que medir 32 Bytes + // let fees_collector_subaccount: ?Blob = ? "FeeCollector00000000000000000000"; // El Blob del subaccount tiene que medir 32 Bytes + // let pool_rewards: ?Blob = ? "PoolRewards00000000000000000000"; + + // var pool_rewards_balance = 0; + stable var _fee: ?Nat = null; + + func pool_rewards_account(): Account { + {owner = Principal.fromActor(this); subaccount = ? "PoolRewards00000000000000000000"} + }; + + func fees_collector_account(): Account { + {owner = Principal.fromActor(this); subaccount = ? "FeeCollector00000000000000000000"} + }; stable var LedgerActor = actor(NULL_ADDRESS): Ledger.CustomToken; @@ -48,6 +73,7 @@ shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal assert(caller == Deployer); OldLedgerActor := LedgerActor; LedgerActor := actor(Principal.toText(lerdgerCanisterId)): Ledger.CustomToken; + _fee := ?(await LedgerActor.icrc1_fee()); #Ok }; @@ -56,6 +82,7 @@ shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal if (Principal.fromActor(OldLedgerActor) != Principal.fromText("aaaaa-aa")) { LedgerActor := OldLedgerActor; OldLedgerActor := actor(NULL_ADDRESS): Ledger.CustomToken; + _fee := ?(await OldLedgerActor.icrc1_fee()); return #Ok }; #Err("No hay registros de ledgers configurados previamente al actual") @@ -72,19 +99,14 @@ shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal Principal.fromActor(LedgerActor) != Principal.fromText(NULL_ADDRESS) }; - // func isAllowedToMint(p: Principal): Bool { - // p == TriourismCanisterId or false - // }; - - ////////////////////////////////////// Getters Fees dispersion section ///////////////////////////////////////////// public query func getFeeCollector(): async Account { - {owner = Principal.fromActor(this); subaccount = fees_collector_subaccount} + fees_collector_account() }; public shared func getFeeCollectorBalance(): async Nat { - await LedgerActor.icrc1_balance_of({owner = Principal.fromActor(this); subaccount = fees_collector_subaccount}) + await LedgerActor.icrc1_balance_of(fees_collector_account()) }; public shared ({ caller }) func getFeesDispersionTable(): async FeesDispersionTable{ @@ -98,11 +120,42 @@ shared ({ caller = Deployer}) actor class Minter({triourismCanisterId: Principal /////////////////////////////////////// Mint section //////////////////////////////////////////////////////////////// - public shared ({ caller }) func rewardMint(args: ICRC1.Mint): async ICRC1.TransferResult{ - print("Minter recibiendo llamada"); + // public shared ({ caller }) func mintRewards(args: ICRC1.Mint): async ICRC1.TransferResult{ + // print("Minter recibiendo llamada"); + // assert(caller == TriourismCanisterId and ledgerReady()); + // print("Llamada aceptada"); + // await LedgerActor.mint(args); + // }; + + public shared ({ caller }) func issueRewards({accounts: [Account]; amount : Nat64}): async {#Ok; #Err}{ assert(caller == TriourismCanisterId and ledgerReady()); - print("Llamada aceptada"); - await LedgerActor.mint(args); + let args = { + amount = Nat64.toNat(amount); + memo: ?Blob = null; + created_at_time = ? Nat64.fromNat(Int.abs(now())); + }; + let pool_rewards_balance = await LedgerActor.icrc1_balance_of(pool_rewards_account()); + if(pool_rewards_balance >= accounts.size() * (args.amount + (await fee()))){ + let from_subaccount: ?ICRC1.Subaccount = pool_rewards_account().subaccount; + for (to in accounts.vals()){ + switch (await LedgerActor.icrc1_transfer({ + args with + fee = null; + from_subaccount; + to; + })) { + case (#Err(_)) { ignore LedgerActor.mint({args with to})}; + case _ {} + }; + } + } else { + for (to in accounts.vals()){ + ignore await LedgerActor.mint({args with to}) + } + }; + + #Ok + };