diff --git a/frontend/mobile/lib/api/ad.dart b/frontend/mobile/lib/api/ad.dart index 4721e4aa..fb87f2f6 100644 --- a/frontend/mobile/lib/api/ad.dart +++ b/frontend/mobile/lib/api/ad.dart @@ -2,6 +2,7 @@ library ad; import 'dart:convert'; +import 'dart:typed_data'; import 'ftl_client.dart'; @@ -61,7 +62,7 @@ class AdResponse { Map toMap() { return { 'name': ((dynamic v) => v)(name), - 'ads': ((dynamic v) => v.map((v) => Ad.fromMap(v)).cast().toList())(ads), + 'ads': ((dynamic v) => v.map((v) => v.toMap()).cast().toList())(ads), }; } diff --git a/frontend/mobile/lib/api/builtin.dart b/frontend/mobile/lib/api/builtin.dart new file mode 100644 index 00000000..48c6b301 --- /dev/null +++ b/frontend/mobile/lib/api/builtin.dart @@ -0,0 +1,80 @@ +// ignore_for_file: unused_import +library builtin; + +import 'dart:convert'; +import 'dart:typed_data'; +import 'ftl_client.dart'; + + +class HttpRequest { + String method; + String path; + Map pathParameters; + Map> query; + Map> headers; + Uint8List body; + + HttpRequest({ required this.method, required this.path, required this.pathParameters, required this.query, required this.headers, required this.body, }); + + Map toMap() { + return { + 'method': ((dynamic v) => v)(method), + 'path': ((dynamic v) => v)(path), + 'pathParameters': ((dynamic v) => v.map((k, v) => MapEntry(k, v)).cast())(pathParameters), + 'query': ((dynamic v) => v.map((k, v) => MapEntry(k, v.map((v) => v).cast().toList())).cast>())(query), + 'headers': ((dynamic v) => v.map((k, v) => MapEntry(k, v.map((v) => v).cast().toList())).cast>())(headers), + 'body': ((dynamic v) => v)(body), + }; + } + + factory HttpRequest.fromMap(Map map) { + return HttpRequest( + method: ((dynamic v) => v)(map['method']), + path: ((dynamic v) => v)(map['path']), + pathParameters: ((dynamic v) => v.map((k, v) => MapEntry(k, v)).cast())(map['pathParameters']), + query: ((dynamic v) => v.map((k, v) => MapEntry(k, v.map((v) => v).cast().toList())).cast>())(map['query']), + headers: ((dynamic v) => v.map((k, v) => MapEntry(k, v.map((v) => v).cast().toList())).cast>())(map['headers']), + body: ((dynamic v) => v)(map['body']), + ); + } + + String toJson() => json.encode(toMap()); + + factory HttpRequest.fromJson(String source) => HttpRequest.fromMap(json.decode(source)); +} + +class HttpResponse { + int status; + Map> headers; + Uint8List body; + + HttpResponse({ required this.status, required this.headers, required this.body, }); + + Map toMap() { + return { + 'status': ((dynamic v) => v)(status), + 'headers': ((dynamic v) => v.map((k, v) => MapEntry(k, v.map((v) => v).cast().toList())).cast>())(headers), + 'body': ((dynamic v) => v)(body), + }; + } + + factory HttpResponse.fromMap(Map map) { + return HttpResponse( + status: ((dynamic v) => v)(map['status']), + headers: ((dynamic v) => v.map((k, v) => MapEntry(k, v.map((v) => v).cast().toList())).cast>())(map['headers']), + body: ((dynamic v) => v)(map['body']), + ); + } + + String toJson() => json.encode(toMap()); + + factory HttpResponse.fromJson(String source) => HttpResponse.fromMap(json.decode(source)); +} + + +class BuiltinClient { + final FTLHttpClient ftlClient; + + BuiltinClient({required this.ftlClient}); + +} diff --git a/frontend/mobile/lib/api/cart.dart b/frontend/mobile/lib/api/cart.dart index 5890e88c..02f088c1 100644 --- a/frontend/mobile/lib/api/cart.dart +++ b/frontend/mobile/lib/api/cart.dart @@ -2,6 +2,7 @@ library cart; import 'dart:convert'; +import 'dart:typed_data'; import 'ftl_client.dart'; @@ -105,7 +106,7 @@ class Cart { Map toMap() { return { 'userID': ((dynamic v) => v)(userID), - 'items': ((dynamic v) => v.map((v) => Item.fromMap(v)).cast().toList())(items), + 'items': ((dynamic v) => v.map((v) => v.toMap()).cast().toList())(items), }; } diff --git a/frontend/mobile/lib/api/checkout.dart b/frontend/mobile/lib/api/checkout.dart new file mode 100644 index 00000000..740894cc --- /dev/null +++ b/frontend/mobile/lib/api/checkout.dart @@ -0,0 +1,123 @@ +// ignore_for_file: unused_import +library checkout; + +import 'dart:convert'; +import 'dart:typed_data'; +import 'ftl_client.dart'; +import 'cart.dart' as cart; +import 'currency.dart' as currency; +import 'payment.dart' as payment; +import 'productcatalog.dart' as productcatalog; +import 'shipping.dart' as shipping; + + +class PlaceOrderRequest { + String userID; + String userCurrency; + shipping.Address address; + String email; + payment.CreditCardInfo creditCard; + + PlaceOrderRequest({ required this.userID, required this.userCurrency, required this.address, required this.email, required this.creditCard, }); + + Map toMap() { + return { + 'userID': ((dynamic v) => v)(userID), + 'userCurrency': ((dynamic v) => v)(userCurrency), + 'address': ((dynamic v) => v.toMap())(address), + 'email': ((dynamic v) => v)(email), + 'creditCard': ((dynamic v) => v.toMap())(creditCard), + }; + } + + factory PlaceOrderRequest.fromMap(Map map) { + return PlaceOrderRequest( + userID: ((dynamic v) => v)(map['userID']), + userCurrency: ((dynamic v) => v)(map['userCurrency']), + address: ((dynamic v) => shipping.Address.fromMap(v))(map['address']), + email: ((dynamic v) => v)(map['email']), + creditCard: ((dynamic v) => payment.CreditCardInfo.fromMap(v))(map['creditCard']), + ); + } + + String toJson() => json.encode(toMap()); + + factory PlaceOrderRequest.fromJson(String source) => PlaceOrderRequest.fromMap(json.decode(source)); +} + +class OrderItem { + cart.Item item; + currency.Money cost; + + OrderItem({ required this.item, required this.cost, }); + + Map toMap() { + return { + 'item': ((dynamic v) => v.toMap())(item), + 'cost': ((dynamic v) => v.toMap())(cost), + }; + } + + factory OrderItem.fromMap(Map map) { + return OrderItem( + item: ((dynamic v) => cart.Item.fromMap(v))(map['item']), + cost: ((dynamic v) => currency.Money.fromMap(v))(map['cost']), + ); + } + + String toJson() => json.encode(toMap()); + + factory OrderItem.fromJson(String source) => OrderItem.fromMap(json.decode(source)); +} + +class Order { + String id; + String shippingTrackingID; + currency.Money shippingCost; + shipping.Address shippingAddress; + List items; + + Order({ required this.id, required this.shippingTrackingID, required this.shippingCost, required this.shippingAddress, required this.items, }); + + Map toMap() { + return { + 'id': ((dynamic v) => v)(id), + 'shippingTrackingID': ((dynamic v) => v)(shippingTrackingID), + 'shippingCost': ((dynamic v) => v.toMap())(shippingCost), + 'shippingAddress': ((dynamic v) => v.toMap())(shippingAddress), + 'items': ((dynamic v) => v.map((v) => v.toMap()).cast().toList())(items), + }; + } + + factory Order.fromMap(Map map) { + return Order( + id: ((dynamic v) => v)(map['id']), + shippingTrackingID: ((dynamic v) => v)(map['shippingTrackingID']), + shippingCost: ((dynamic v) => currency.Money.fromMap(v))(map['shippingCost']), + shippingAddress: ((dynamic v) => shipping.Address.fromMap(v))(map['shippingAddress']), + items: ((dynamic v) => v.map((v) => OrderItem.fromMap(v)).cast().toList())(map['items']), + ); + } + + String toJson() => json.encode(toMap()); + + factory Order.fromJson(String source) => Order.fromMap(json.decode(source)); +} + + +class CheckoutClient { + final FTLHttpClient ftlClient; + + CheckoutClient({required this.ftlClient}); + + + Future placeOrder(PlaceOrderRequest request) async { + final response = await ftlClient.post('/checkout/userID', request: request.toMap()); + if (response.statusCode == 200) { + return Order.fromJson(response.body); + } else { + throw Exception('Failed to get placeOrder response'); + } + } + +} diff --git a/frontend/mobile/lib/api/currency.dart b/frontend/mobile/lib/api/currency.dart index 6175e7f6..ac7e0e5a 100644 --- a/frontend/mobile/lib/api/currency.dart +++ b/frontend/mobile/lib/api/currency.dart @@ -2,6 +2,7 @@ library currency; import 'dart:convert'; +import 'dart:typed_data'; import 'ftl_client.dart'; diff --git a/frontend/mobile/lib/api/payment.dart b/frontend/mobile/lib/api/payment.dart index d4d6388f..f6d0b795 100644 --- a/frontend/mobile/lib/api/payment.dart +++ b/frontend/mobile/lib/api/payment.dart @@ -2,6 +2,7 @@ library payment; import 'dart:convert'; +import 'dart:typed_data'; import 'ftl_client.dart'; import 'currency.dart' as currency; diff --git a/frontend/mobile/lib/api/productcatalog.dart b/frontend/mobile/lib/api/productcatalog.dart index b799ab66..f25aaae4 100644 --- a/frontend/mobile/lib/api/productcatalog.dart +++ b/frontend/mobile/lib/api/productcatalog.dart @@ -2,6 +2,7 @@ library productcatalog; import 'dart:convert'; +import 'dart:typed_data'; import 'ftl_client.dart'; import 'currency.dart' as currency; @@ -69,7 +70,7 @@ class ListResponse { Map toMap() { return { - 'products': ((dynamic v) => v.map((v) => Product.fromMap(v)).cast().toList())(products), + 'products': ((dynamic v) => v.map((v) => v.toMap()).cast().toList())(products), }; } @@ -135,7 +136,7 @@ class SearchResponse { Map toMap() { return { - 'results': ((dynamic v) => v.map((v) => Product.fromMap(v)).cast().toList())(results), + 'results': ((dynamic v) => v.map((v) => v.toMap()).cast().toList())(results), }; } @@ -167,7 +168,7 @@ class ProductcatalogClient { } Future get(GetRequest request) async { - final response = await ftlClient.get('/productcatalog/${request.id}', requestJson: request.toJson()); + final response = await ftlClient.get('/productcatalog/id', requestJson: request.toJson()); if (response.statusCode == 200) { return Product.fromJson(response.body); } else { diff --git a/frontend/mobile/lib/api/recommendation.dart b/frontend/mobile/lib/api/recommendation.dart index 7cec7332..84739cdc 100644 --- a/frontend/mobile/lib/api/recommendation.dart +++ b/frontend/mobile/lib/api/recommendation.dart @@ -2,6 +2,7 @@ library recommendation; import 'dart:convert'; +import 'dart:typed_data'; import 'ftl_client.dart'; import 'productcatalog.dart' as productcatalog; diff --git a/frontend/mobile/lib/api/shipping.dart b/frontend/mobile/lib/api/shipping.dart index 04a951e3..b2b1bf6b 100644 --- a/frontend/mobile/lib/api/shipping.dart +++ b/frontend/mobile/lib/api/shipping.dart @@ -2,9 +2,10 @@ library shipping; import 'dart:convert'; +import 'dart:typed_data'; import 'ftl_client.dart'; -import 'currency.dart' as currency; import 'cart.dart' as cart; +import 'currency.dart' as currency; class Address { @@ -50,7 +51,7 @@ class ShippingRequest { Map toMap() { return { 'address': ((dynamic v) => v.toMap())(address), - 'items': ((dynamic v) => v.map((v) => cart.Item.fromMap(v)).cast().toList())(items), + 'items': ((dynamic v) => v.map((v) => v.toMap()).cast().toList())(items), }; } diff --git a/frontend/mobile/templates/template.js b/frontend/mobile/templates/template.js index 45d15959..5279d517 100644 --- a/frontend/mobile/templates/template.js +++ b/frontend/mobile/templates/template.js @@ -22,6 +22,9 @@ function dartType(t) { case "Array": return `List<${dartType(t.element)}>`; + + case "Bytes": + return `Uint8List`; case "VerbRef": case "DataRef": @@ -57,10 +60,10 @@ function deserialize(t) { function serialize(t) { switch (typename(t)) { case "Array": - return `v.map((v) => ${deserialize(t.element)}).cast<${dartType(t.element)}>().toList()`; + return `v.map((v) => ${serialize(t.element)}).cast<${dartType(t.element)}>().toList()`; case "Map": - return `v.map((k, v) => MapEntry(k, ${deserialize(t.value)})).cast<${dartType(t.key)}, ${dartType(t.value)}>()`; + return `v.map((k, v) => MapEntry(k, ${serialize(t.value)})).cast<${dartType(t.key)}, ${dartType(t.value)}>()`; case "DataRef": return "v.toMap()"; @@ -71,8 +74,7 @@ function serialize(t) { } function url(verb) { - let path = verb.metadata[0].path; - const method = verb.metadata[0].method; + let path = '/' + verb.metadata[0].path.join("/"); return path.replace(/{(.*?)}/g, (match, fieldName) => { return "$" + `{request.${fieldName}}`; diff --git a/frontend/mobile/templates/{{ .Name | lower }}.dart b/frontend/mobile/templates/{{ .Name | lower }}.dart index 958fb331..fdd259ff 100644 --- a/frontend/mobile/templates/{{ .Name | lower }}.dart +++ b/frontend/mobile/templates/{{ .Name | lower }}.dart @@ -2,6 +2,7 @@ library {{ .Name | lower }}; import 'dart:convert'; +import 'dart:typed_data'; import 'ftl_client.dart'; {{- range .Imports }} import '{{. | lower }}.dart' as {{. | lower}}; diff --git a/frontend/web/src/api/builtin.ts b/frontend/web/src/api/builtin.ts new file mode 100644 index 00000000..a3c8f2cd --- /dev/null +++ b/frontend/web/src/api/builtin.ts @@ -0,0 +1,34 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +// eslint-disable @typescript-eslint/no-unused-vars +// +// Automatically generated by +// ____________ +// / __/_ __/ / +// / _/ / / / /__ +// /_/ /_/ /____/ +// +// +export interface HttpRequest { + method: string; + path: string; + pathParameters: Map; + query: Map; + headers: Map; + body: Uint8Array; +} + +export interface HttpResponse { + status: number; + headers: Map; + body: Uint8Array; +} + + +export class BuiltinClient { + private baseUrl: string; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } +} diff --git a/frontend/web/src/api/checkout.ts b/frontend/web/src/api/checkout.ts new file mode 100644 index 00000000..48a73a4e --- /dev/null +++ b/frontend/web/src/api/checkout.ts @@ -0,0 +1,61 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +// eslint-disable @typescript-eslint/no-unused-vars +// +// Automatically generated by +// ____________ +// / __/_ __/ / +// / _/ / / / /__ +// /_/ /_/ /____/ +// +// +import * as cart from "./cart" +import * as currency from "./currency" +import * as payment from "./payment" +import * as productcatalog from "./productcatalog" +import * as shipping from "./shipping" +export interface PlaceOrderRequest { + userID: string; + userCurrency: string; + address: shipping.Address; + email: string; + creditCard: payment.CreditCardInfo; +} + +export interface OrderItem { + item: cart.Item; + cost: currency.Money; +} + +export interface Order { + id: string; + shippingTrackingID: string; + shippingCost: currency.Money; + shippingAddress: shipping.Address; + items: OrderItem[]; +} + + +export class CheckoutClient { + private baseUrl: string; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } + + public async placeOrder(request: PlaceOrderRequest): Promise { + const path = `/checkout/userID`; + const response = await fetch(`${this.baseUrl}${path}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request), + });if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + return response.json(); + } + +} diff --git a/frontend/web/src/api/productcatalog.ts b/frontend/web/src/api/productcatalog.ts index fa22b5d5..ce32cd99 100644 --- a/frontend/web/src/api/productcatalog.ts +++ b/frontend/web/src/api/productcatalog.ts @@ -62,7 +62,7 @@ export class ProductcatalogClient { } public async get(request: GetRequest): Promise { - const path = `/productcatalog/${request.id}?@json=${encodeURIComponent(JSON.stringify(request))}`; + const path = `/productcatalog/id?@json=${encodeURIComponent(JSON.stringify(request))}`; const response = await fetch(`${this.baseUrl}${path}`, { method: 'GET', headers: { diff --git a/frontend/web/templates/template.js b/frontend/web/templates/template.js index afa7089d..87bde6e1 100644 --- a/frontend/web/templates/template.js +++ b/frontend/web/templates/template.js @@ -23,6 +23,9 @@ function tsType(t) { case 'Array': return `${tsType(t.element)}[]` + case "Bytes": + return "Uint8Array"; + case 'VerbRef': case 'DataRef': if (context.name === t.module) { @@ -57,10 +60,10 @@ function deserialize(t) { function serialize(t) { switch (typename(t)) { case 'Array': - return `v.map((v) => ${deserialize(t.element)}).cast<${tsType(t.element)}>().toList()` + return `v.map((v) => ${serialize(t.element)}).cast<${tsType(t.element)}>().toList()` case 'Map': - return `v.map((k, v) => MapEntry(k, ${deserialize(t.value)})).cast<${tsType(t.key)}, ${tsType(t.value)}>()` + return `v.map((k, v) => MapEntry(k, ${serialize(t.value)})).cast<${tsType(t.key)}, ${tsType(t.value)}>()` case 'DataRef': return 'v.toMap()' @@ -71,7 +74,7 @@ function serialize(t) { } function url(verb) { - let path = verb.metadata[0].path + let path = "/" + verb.metadata[0].path.join("/"); const method = verb.metadata[0].method path = path.replace(/{(.*?)}/g, (match, fieldName) => {