Initial project structure with sails.js

This commit is contained in:
2023-11-21 21:57:32 -05:00
commit 523978e520
197 changed files with 76740 additions and 0 deletions

View File

@ -0,0 +1,55 @@
module.exports = {
friendlyName: 'Logout',
description: 'Log out of this app.',
extendedDescription:
`This action deletes the \`req.session.userId\` key from the session of the requesting user agent.
Actual garbage collection of session data depends on this app's session store, and
potentially also on the [TTL configuration](https://sailsjs.com/docs/reference/configuration/sails-config-session)
you provided for it.
Note that this action does not check to see whether or not the requesting user was
actually logged in. (If they weren't, then this action is just a no-op.)`,
exits: {
success: {
description: 'The requesting user agent has been successfully logged out.'
},
redirect: {
description: 'The requesting user agent looks to be a web browser.',
extendedDescription: 'After logging out from a web browser, the user is redirected away.',
responseType: 'redirect'
}
},
fn: async function () {
// Clear the `userId` property from this session.
delete this.req.session.userId;
// Broadcast a message that we can display in other open tabs.
if (sails.hooks.sockets) {
await sails.helpers.broadcastSessionChange(this.req);
}
// Then finish up, sending an appropriate response.
// > Under the covers, this persists the now-logged-out session back
// > to the underlying session store.
if (!this.req.wantsJSON) {
throw {redirect: '/login'};
}
}
};

View File

@ -0,0 +1,79 @@
module.exports = {
friendlyName: 'Update billing card',
description: 'Update the credit card for the logged-in user.',
inputs: {
stripeToken: {
type: 'string',
example: 'tok_199k3qEXw14QdSnRwmsK99MH',
description: 'The single-use Stripe Checkout token identifier representing the user\'s payment source (i.e. credit card.)',
extendedDescription: 'Omit this (or use "") to remove this user\'s payment source.',
whereToGet: {
description: 'This Stripe.js token is provided to the front-end (client-side) code after completing a Stripe Checkout or Stripe Elements flow.'
}
},
billingCardLast4: {
type: 'string',
example: '4242',
description: 'Omit if removing card info.',
whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
},
billingCardBrand: {
type: 'string',
example: 'visa',
description: 'Omit if removing card info.',
whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
},
billingCardExpMonth: {
type: 'string',
example: '08',
description: 'Omit if removing card info.',
whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
},
billingCardExpYear: {
type: 'string',
example: '2023',
description: 'Omit if removing card info.',
whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
},
},
fn: async function ({stripeToken, billingCardLast4, billingCardBrand, billingCardExpMonth, billingCardExpYear}) {
// Add, update, or remove the default payment source for the logged-in user's
// customer entry in Stripe.
var stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
stripeCustomerId: this.req.me.stripeCustomerId,
token: stripeToken || '',
}).timeout(5000).retry();
// Update (or clear) the card info we have stored for this user in our database.
// > Remember, never store complete card numbers-- only the last 4 digits + expiration!
// > Storing (or even receiving) complete, unencrypted card numbers would require PCI
// > compliance in the U.S.
await User.updateOne({ id: this.req.me.id })
.set({
stripeCustomerId,
hasBillingCard: stripeToken ? true : false,
billingCardBrand: stripeToken ? billingCardBrand : '',
billingCardLast4: stripeToken ? billingCardLast4 : '',
billingCardExpMonth: stripeToken ? billingCardExpMonth : '',
billingCardExpYear: stripeToken ? billingCardExpYear : ''
});
}
};

View File

@ -0,0 +1,35 @@
module.exports = {
friendlyName: 'Update password',
description: 'Update the password for the logged-in user.',
inputs: {
password: {
description: 'The new, unencrypted password.',
example: 'abc123v2',
required: true
}
},
fn: async function ({password}) {
// Hash the new password.
var hashed = await sails.helpers.passwords.hashPassword(password);
// Update the record for the logged-in user.
await User.updateOne({ id: this.req.me.id })
.set({
password: hashed
});
}
};

View File

@ -0,0 +1,160 @@
module.exports = {
friendlyName: 'Update profile',
description: 'Update the profile for the logged-in user.',
inputs: {
fullName: {
type: 'string'
},
emailAddress: {
type: 'string'
},
},
exits: {
emailAlreadyInUse: {
statusCode: 409,
description: 'The provided email address is already in use.',
},
},
fn: async function ({fullName, emailAddress}) {
var newEmailAddress = emailAddress;
if (newEmailAddress !== undefined) {
newEmailAddress = newEmailAddress.toLowerCase();
}
// Determine if this request wants to change the current user's email address,
// revert her pending email address change, modify her pending email address
// change, or if the email address won't be affected at all.
var desiredEmailEffect;// ('change-immediately', 'begin-change', 'cancel-pending-change', 'modify-pending-change', or '')
if (
newEmailAddress === undefined ||
(this.req.me.emailStatus !== 'change-requested' && newEmailAddress === this.req.me.emailAddress) ||
(this.req.me.emailStatus === 'change-requested' && newEmailAddress === this.req.me.emailChangeCandidate)
) {
desiredEmailEffect = '';
} else if (this.req.me.emailStatus === 'change-requested' && newEmailAddress === this.req.me.emailAddress) {
desiredEmailEffect = 'cancel-pending-change';
} else if (this.req.me.emailStatus === 'change-requested' && newEmailAddress !== this.req.me.emailAddress) {
desiredEmailEffect = 'modify-pending-change';
} else if (!sails.config.custom.verifyEmailAddresses || this.req.me.emailStatus === 'unconfirmed') {
desiredEmailEffect = 'change-immediately';
} else {
desiredEmailEffect = 'begin-change';
}
// If the email address is changing, make sure it is not already being used.
if (_.contains(['begin-change', 'change-immediately', 'modify-pending-change'], desiredEmailEffect)) {
let conflictingUser = await User.findOne({
or: [
{ emailAddress: newEmailAddress },
{ emailChangeCandidate: newEmailAddress }
]
});
if (conflictingUser) {
throw 'emailAlreadyInUse';
}
}
// Start building the values to set in the db.
// (We always set the fullName if provided.)
var valuesToSet = {
fullName,
};
switch (desiredEmailEffect) {
// Change now
case 'change-immediately':
_.extend(valuesToSet, {
emailAddress: newEmailAddress,
emailChangeCandidate: '',
emailProofToken: '',
emailProofTokenExpiresAt: 0,
emailStatus: this.req.me.emailStatus === 'unconfirmed' ? 'unconfirmed' : 'confirmed'
});
break;
// Begin new email change, or modify a pending email change
case 'begin-change':
case 'modify-pending-change':
_.extend(valuesToSet, {
emailChangeCandidate: newEmailAddress,
emailProofToken: await sails.helpers.strings.random('url-friendly'),
emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
emailStatus: 'change-requested'
});
break;
// Cancel pending email change
case 'cancel-pending-change':
_.extend(valuesToSet, {
emailChangeCandidate: '',
emailProofToken: '',
emailProofTokenExpiresAt: 0,
emailStatus: 'confirmed'
});
break;
// Otherwise, do nothing re: email
}
// Save to the db
await User.updateOne({id: this.req.me.id })
.set(valuesToSet);
// If this is an immediate change, and billing features are enabled,
// then also update the billing email for this user's linked customer entry
// in the Stripe API to make sure they receive email receipts.
// > Note: If there was not already a Stripe customer entry for this user,
// > then one will be set up implicitly, so we'll need to persist it to our
// > database. (This could happen if Stripe credentials were not configured
// > at the time this user was originally created.)
if(desiredEmailEffect === 'change-immediately' && sails.config.custom.enableBillingFeatures) {
let didNotAlreadyHaveCustomerId = (! this.req.me.stripeCustomerId);
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
stripeCustomerId: this.req.me.stripeCustomerId,
emailAddress: newEmailAddress
}).timeout(5000).retry();
if (didNotAlreadyHaveCustomerId){
await User.updateOne({ id: this.req.me.id })
.set({
stripeCustomerId
});
}
}
// If an email address change was requested, and re-confirmation is required,
// send the "confirm account" email.
if (desiredEmailEffect === 'begin-change' || desiredEmailEffect === 'modify-pending-change') {
await sails.helpers.sendTemplateEmail.with({
to: newEmailAddress,
subject: 'Your account has been updated',
template: 'email-verify-new-email',
templateData: {
fullName: fullName||this.req.me.fullName,
token: valuesToSet.emailProofToken
}
});
}
}
};

View File

@ -0,0 +1,30 @@
module.exports = {
friendlyName: 'View account overview',
description: 'Display "Account Overview" page.',
exits: {
success: {
viewTemplatePath: 'pages/account/account-overview',
}
},
fn: async function () {
// If billing features are enabled, include our configured Stripe.js
// public key in the view locals. Otherwise, leave it as undefined.
return {
stripePublishableKey: sails.config.custom.enableBillingFeatures? sails.config.custom.stripePublishableKey : undefined,
};
}
};

View File

@ -0,0 +1,26 @@
module.exports = {
friendlyName: 'View edit password',
description: 'Display "Edit password" page.',
exits: {
success: {
viewTemplatePath: 'pages/account/edit-password'
}
},
fn: async function () {
return {};
}
};

View File

@ -0,0 +1,26 @@
module.exports = {
friendlyName: 'View edit profile',
description: 'Display "Edit profile" page.',
exits: {
success: {
viewTemplatePath: 'pages/account/edit-profile',
}
},
fn: async function () {
return {};
}
};