Skip to content

Commit c9cb0b7

Browse files
wip
1 parent ac7438f commit c9cb0b7

File tree

4 files changed

+37
-264
lines changed

4 files changed

+37
-264
lines changed

src/cli.mjs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,15 @@ function buildYargs(argvInput) {
202202
"Components to emit logs for. Overrides the --verbosity flag. Pass values as a space-separated list. Ex: --verbose-component fetch error.",
203203
type: "array",
204204
default: [],
205-
choices: ["argv", "completion", "config", "creds", "error", "fetch"],
205+
choices: [
206+
"argv",
207+
"command",
208+
"completion",
209+
"config",
210+
"creds",
211+
"error",
212+
"fetch",
213+
],
206214
group: "Debug:",
207215
},
208216
verbosity: {

src/commands/init.mjs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,24 @@ import { listDatabasesWithAccountAPI } from "./database/list.mjs";
1818
// TODO: handle error/exit case cleanly
1919

2020
async function doInit(argv) {
21+
const logger = container.resolve("logger");
2122
const getters = [getDatabaseRunnable, getProjectRunnable, getKeyRunnable];
2223
const runnables = [];
2324

2425
let allChoices = {};
2526
// in order, gather user input (choices)
26-
for (const [index, getter] of Object.entries(getters)) {
27-
// eslint-disable-next-line no-await-in-loop
28-
runnables[index] = await getter(argv, allChoices);
29-
allChoices = { ...allChoices, ...runnables[index].choices };
27+
try {
28+
for (const [index, getter] of Object.entries(getters)) {
29+
// eslint-disable-next-line no-await-in-loop
30+
runnables[index] = await getter(argv, allChoices);
31+
allChoices = { ...allChoices, ...runnables[index].choices };
32+
}
33+
} catch (e) {
34+
logger.stderr(
35+
`Failed while gathering user input. No changes have been made to the filesystem or any Fauna databases.`,
36+
"command",
37+
);
38+
throw e;
3039
}
3140

3241
// in order, do tasks based on user input (choices)
@@ -70,7 +79,10 @@ async function getDatabaseRunnable(argv /*, priorChoices*/) {
7079
],
7180
});
7281

73-
if (runnable.choices.createNewDb !== "new") return runnable;
82+
if (runnable.choices.createNewDb !== "new") {
83+
runnable.choices.dbName = runnable.choices.createNewDb.split("/")[1];
84+
return runnable;
85+
}
7486

7587
logger.stdout("Ok! We'll create a new database.");
7688

@@ -94,7 +106,7 @@ async function getDatabaseRunnable(argv /*, priorChoices*/) {
94106
const otherSettings = await inquirer.checkbox({
95107
message: "Configure any other settings",
96108
choices: [
97-
// TODO: this could fail with role issues?
109+
// TODO: this could fail with plan / role issues? skipping.
98110
// {
99111
// name: "Backups",
100112
// value: "backup",
@@ -195,9 +207,6 @@ async function getKeyRunnable(argv, priorChoices) {
195207
],
196208
});
197209

198-
// this is a little white lie - we don't actually run this FQL since we call frontdoor instead
199-
// but we run the equivalent of it
200-
// actually, do we just... run the FQL? let's just run the FQL
201210
runnable.fql = `Key.create({
202211
role: "${runnable.choices.role}",
203212
data: {
@@ -206,14 +215,16 @@ async function getKeyRunnable(argv, priorChoices) {
206215
})`;
207216

208217
runnable.runner = async ({ choices, fql }) => {
209-
logger.stdout(`Creating key ${choices.dbName} by running FQL query:`);
218+
logger.stdout(`Creating key "${choices.keyName}" by running FQL query:`);
210219
logger.stdout(fql);
211-
await runQueryFromString({
220+
const response = await runQueryFromString({
212221
expression: fql,
213222
secret: await getSecret(),
214223
url: "https://db.fauna.com",
215224
});
216-
logger.stdout(`Created key ${choices.dbName}.`);
225+
logger.stdout(
226+
`Created key "${choices.keyName}" with value "${response.data.secret}".`,
227+
);
217228
};
218229

219230
return runnable;
@@ -238,7 +249,7 @@ async function getProjectRunnable(argv, priorChoices) {
238249
if (!shouldCreateProjectDirectory) return runnable;
239250

240251
runnable.choices.dirName = await inquirer.input({
241-
message: `FSL files are stored in a project directory and are specific to the database "${priorChoices.dbName}". What would you like to name this project directory?`,
252+
message: `FSL files are stored in a project directory and are specific to the database "${priorChoices.dbName}". What would you like to name this project directory? In the next step, you will choose where to put the directory.`,
242253
default: priorChoices.dbName,
243254
});
244255

@@ -327,11 +338,11 @@ async function getProjectRunnable(argv, priorChoices) {
327338
// new db with no demo data? create two blank schema files
328339
await Promise.all([
329340
fsp.writeFile(
330-
path.join(__dirname, "../lib/schema/demo-collection-schema.fsl"),
341+
path.join(__dirname, "../src/lib/schema/demo-collection-schema.fsl"),
331342
"",
332343
),
333344
fsp.writeFile(
334-
path.join(__dirname, "../lib/schema/demo-function-schema.fsl"),
345+
path.join(__dirname, "../src/lib/schema/demo-function-schema.fsl"),
335346
"",
336347
),
337348
]);
@@ -346,7 +357,8 @@ async function getProjectRunnable(argv, priorChoices) {
346357

347358
export default {
348359
command: "init",
349-
describe: "Init!!",
360+
describe:
361+
"Configure an existing database or create a new one. Optionally creates demo data, an FSL directory, and a database key.",
350362
builder: buildInitCommand,
351363
handler: doInit,
352364
};
Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +0,0 @@
1-
collection Customer {
2-
name: String
3-
email: String
4-
address: {
5-
street: String,
6-
city: String,
7-
state: String,
8-
postalCode: String,
9-
country: String
10-
}
11-
12-
compute cart: Order? = (customer => Order.byCustomerAndStatus(customer, 'cart').first())
13-
14-
// Use a computed field to get the set of Orders for a customer.
15-
compute orders: Set<Order> = ( customer => Order.byCustomer(customer))
16-
17-
// Use a unique constraint to ensure no two customers have the same email.
18-
unique [.email]
19-
20-
index byEmail {
21-
terms [.email]
22-
}
23-
}
24-
25-
collection Product {
26-
name: String
27-
description: String
28-
// Use an Integer to represent cents.
29-
// This avoids floating-point precision issues.
30-
price: Int
31-
category: Ref<Category>
32-
stock: Int
33-
34-
// Use a unique constraint to ensure no two products have the same name.
35-
unique [.name]
36-
check stockIsValid (product => product.stock >= 0)
37-
check priceIsValid (product => product.price > 0)
38-
39-
index byCategory {
40-
terms [.category]
41-
}
42-
43-
index sortedByCategory {
44-
values [.category]
45-
}
46-
47-
index byName {
48-
terms [.name]
49-
}
50-
51-
index sortedByPriceLowToHigh {
52-
values [.price, .name, .description, .stock]
53-
}
54-
}
55-
56-
collection Category {
57-
name: String
58-
description: String
59-
compute products: Set<Product> = (category => Product.byCategory(category))
60-
61-
unique [.name]
62-
63-
index byName {
64-
terms [.name]
65-
}
66-
}
67-
68-
collection Order {
69-
customer: Ref<Customer>
70-
status: "cart" | "processing" | "shipped" | "delivered"
71-
createdAt: Time
72-
73-
compute items: Set<OrderItem> = (order => OrderItem.byOrder(order))
74-
compute total: Number = (order => order.items.fold(0, (sum, orderItem) => {
75-
let orderItem: Any = orderItem
76-
if (orderItem.product != null) {
77-
sum + orderItem.product.price * orderItem.quantity
78-
} else {
79-
sum
80-
}
81-
}))
82-
payment: { *: Any }
83-
84-
check oneOrderInCart (order => {
85-
Order.byCustomerAndStatus(order.customer, "cart").count() <= 1
86-
})
87-
88-
// Define an index to get all orders for a customer. Orders will be sorted by
89-
// createdAt in descending order.
90-
index byCustomer {
91-
terms [.customer]
92-
values [desc(.createdAt), .status]
93-
}
94-
95-
index byCustomerAndStatus {
96-
terms [.customer, .status]
97-
}
98-
}
99-
100-
collection OrderItem {
101-
order: Ref<Order>
102-
product: Ref<Product>
103-
quantity: Int
104-
105-
unique [.order, .product]
106-
check positiveQuantity (orderItem => orderItem.quantity > 0)
107-
108-
index byOrder {
109-
terms [.order]
110-
values [.product, .quantity]
111-
}
112-
113-
index byOrderAndProduct {
114-
terms [.order, .product]
115-
}
116-
}
Lines changed: 0 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +0,0 @@
1-
function createOrUpdateCartItem(customerId, productName, quantity) {
2-
// Find the customer by id, using the ! operator to assert that the customer exists.
3-
// If the customer does not exist, fauna will throw a document_not_found error.
4-
let customer = Customer.byId(customerId)!
5-
// There is a unique constraint on [.name] so this will return at most one result.
6-
let product = Product.byName(productName).first()
7-
8-
// Check if the product exists.
9-
if (product == null) {
10-
abort("Product does not exist.")
11-
}
12-
13-
// Check that the quantity is valid.
14-
if (quantity < 0) {
15-
abort("Quantity must be a non-negative integer.")
16-
}
17-
18-
// Create a new cart for the customer if they do not have one.
19-
if (customer!.cart == null) {
20-
Order.create({
21-
status: "cart",
22-
customer: customer,
23-
createdAt: Time.now(),
24-
payment: {}
25-
})
26-
}
27-
28-
// Check that the product has the requested quantity in stock.
29-
if (product!.stock < quantity) {
30-
abort("Product does not have the requested quantity in stock.")
31-
}
32-
33-
// Attempt to find an existing order item for the order, product pair.
34-
// There is a unique constraint on [.order, .product] so this will return at most one result.
35-
let orderItem = OrderItem.byOrderAndProduct(customer!.cart, product).first()
36-
37-
if (orderItem == null) {
38-
// If the order item does not exist, create a new one.
39-
OrderItem.create({
40-
order: Order(customer!.cart!.id),
41-
product: product,
42-
quantity: quantity,
43-
})
44-
} else {
45-
// If the order item exists, update the quantity.
46-
orderItem!.update({ quantity: quantity })
47-
}
48-
}
49-
50-
function getOrCreateCart(id) {
51-
// Find the customer by id, using the ! operator to assert that the customer exists.
52-
// If the customer does not exist, fauna will throw a document_not_found error.
53-
let customer = Customer.byId(id)!
54-
55-
if (customer!.cart == null) {
56-
// Create a cart if the customer does not have one.
57-
Order.create({
58-
status: 'cart',
59-
customer: Customer.byId(id),
60-
createdAt: Time.now(),
61-
payment: {}
62-
})
63-
} else {
64-
// Return the cart if it already exists.
65-
customer!.cart
66-
}
67-
}
68-
69-
function checkout(orderId, status, payment) {
70-
// Find the order by id, using the ! operator to assert that the order exists.
71-
let order = Order.byId(orderId)!
72-
73-
// Check that we are setting the order to the processing status. If not, we should
74-
// not be calling this function.
75-
if (status != "processing") {
76-
abort("Can not call checkout with status other than processing.")
77-
}
78-
79-
// Check that the order can be transitioned to the processing status.
80-
validateOrderStatusTransition(order!.status, "processing")
81-
82-
// Check that the order has at least one order item.
83-
if (order!.items.isEmpty()) {
84-
abort("Order must have at least one item.")
85-
}
86-
87-
// Check that customer has a valid address.
88-
if (order!.customer!.address == null) {
89-
abort("Customer must have a valid address.")
90-
}
91-
92-
// Check that the order has a payment method if not provided as an argument.
93-
if (order!.payment == null && payment == null) {
94-
abort("Order must have a valid payment method.")
95-
}
96-
97-
// Check that the order items are still in stock.
98-
order!.items.forEach((item) => {
99-
let product: Any = item.product
100-
if (product.stock < item.quantity) {
101-
abort("One of the selected products does not have the requested quantity in stock.")
102-
}
103-
})
104-
105-
// Decrement the stock of each product in the order.
106-
order!.items.forEach((item) => {
107-
let product: Any = item.product
108-
product.update({ stock: product.stock - item.quantity })
109-
})
110-
111-
// Transition the order to the processing status, update the payment if provided.
112-
if (payment != null) {
113-
order!.update({ status: "processing", payment: payment })
114-
} else {
115-
order!.update({ status: "processing" })
116-
}
117-
}
118-
119-
function validateOrderStatusTransition(oldStatus, newStatus) {
120-
if (oldStatus == "cart" && newStatus != "processing") {
121-
// The order can only transition from cart to processing.
122-
abort("Invalid status transition.")
123-
} else if (oldStatus == "processing" && newStatus != "shipped") {
124-
// The order can only transition from processing to shipped.
125-
abort("Invalid status transition.")
126-
} else if (oldStatus == "shipped" && newStatus != "delivered") {
127-
// The order can only transition from shipped to delivered.
128-
abort("Invalid status transition.")
129-
}
130-
}
131-

0 commit comments

Comments
 (0)