Skip to content

Commit

Permalink
#23 | change follow create subcsription when register
Browse files Browse the repository at this point in the history
  • Loading branch information
leophan07 committed Nov 27, 2020
1 parent ffabcf7 commit e125ed1
Show file tree
Hide file tree
Showing 43 changed files with 712 additions and 344 deletions.
26 changes: 8 additions & 18 deletions api/constants/billing.constant.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
export const BILLING_PRICE = {
freelancer: {
price: 12,
permissions: ['can_invite_user', 'can_remove_user'],
},
startup: {
price: 24,
permissions: ['can_invite_user', 'can_remove_user', 'can_set_admin_user'],
},
enterprise: {
price: 48,
permissions: [
'can_invite_user',
'can_remove_user',
'can_set_admin_user',
'can_create_group',
],
},
export const PERMISSION_PLAN = {
starter: ['can_invite_user', 'can_remove_user', 'can_set_admin_user'],
professional: [
'can_invite_user',
'can_remove_user',
'can_set_admin_user',
'can_create_group',
],
};
4 changes: 3 additions & 1 deletion api/constants/table-name.constant.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const TABLES = {
users: 'users',
userToken: 'user_token',
userTokens: 'user_tokens',
userPlans: 'user_plans',
userPermissions: 'user_permissions',
products: 'products',
prices: 'prices',
};

export { TABLES };
5 changes: 5 additions & 0 deletions api/graphql/resolvers/authorization.resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AuthenticationError } from 'apollo-server-express';
import { skip } from 'graphql-resolvers';

export const isAuthenticated = (parent, args, { user }) =>
user && user.email ? skip : new AuthenticationError('Authentication fail');
14 changes: 14 additions & 0 deletions api/graphql/resolvers/user-plan.resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { combineResolvers } from 'graphql-resolvers';
import { getUserPlan } from '~/services/user/plans-user.service';
import { isAuthenticated } from './authorization.resolver';

const resolvers = {
Query: {
getUserPlan: combineResolvers(
isAuthenticated,
(_, args, { user }) => getUserPlan(user.id),
),
},
};

export default resolvers;
12 changes: 9 additions & 3 deletions api/graphql/resolvers/user.resolver.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { combineResolvers } from 'graphql-resolvers';

import { isAuthenticated } from './authorization.resolver';
import { registerUser } from '~/services/authentication/register.service';
import { loginUser } from '~/services/authentication/login.service';
import { verifyEmail, resendEmailAction } from '~/services/authentication/verify-email.service';
Expand All @@ -13,14 +16,17 @@ const resolvers = {
loginBySocial: (_, { provider, code }) => loginSocial(provider, code),
},
Mutation: {
register(_, { email, password, name, planName, billingType }) {
return registerUser(email, password, name, planName, billingType);
register(_, { email, password, name, paymentMethodToken, planName, billingType }) {
return registerUser(email, password, name, paymentMethodToken, planName, billingType);
},
login(_, { email, password }) {
return loginUser(email, password);
},
forgotPassword: async (_, { email }) => forgotPasswordUser(email),
changePassword: async (_, { currentPassword, newPassword }, { user }) => changePasswordUser(user.id, currentPassword, newPassword),
changePassword: combineResolvers(
isAuthenticated,
(_, { currentPassword, newPassword }, { user }) => changePasswordUser(user.id, currentPassword, newPassword),
),
resetPassword: async (_, { token, password, confirmPassword }) => resetPasswordUser(token, password, confirmPassword),
verify: (_, { token }) => verifyEmail(token),
resendEmail: (_, { type }, { user }) => resendEmailAction(user, type.toLowerCase()),
Expand Down
3 changes: 2 additions & 1 deletion api/graphql/root.resolver.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import userResolves from './resolvers/user.resolver';
import userPlanResolves from './resolvers/user-plan.resolver';
import stripeResolves from './resolvers/stripe.resolver';

export default [userResolves, stripeResolves];
export default [userResolves, userPlanResolves, stripeResolves];
3 changes: 2 additions & 1 deletion api/graphql/root.schema.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Apollo from 'apollo-server-express';
import { UserSchema } from './schemas/user.schema';
import { UserPlanSchema } from './schemas/user-plan.schema';
import { StripeSchema } from './schemas/stripe.schema';

const { gql } = Apollo;
Expand All @@ -20,4 +21,4 @@ const rootSchema = gql`
}
`;

export default [rootSchema, UserSchema, StripeSchema];
export default [rootSchema, UserSchema, UserPlanSchema, StripeSchema];
20 changes: 20 additions & 0 deletions api/graphql/schemas/user-plan.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import pkg from 'apollo-server-express';

const { gql } = pkg;

export const UserPlanSchema = gql`
type ResponseUserPlan {
userId: Int!
productId: Int!
priceId: Int!
name: String!
amount: Float!
productType: String!
priceType: String!
}
extend type Query {
getUserPlan: ResponseUserPlan!
}
`;
2 changes: 1 addition & 1 deletion api/graphql/schemas/user.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const UserSchema = gql`
}
extend type Mutation {
register(email: String!, password: String!, name: String!, planName: String, billingType: BillingType): ResponseUserLogin!
register(email: String!, password: String!, name: String!, paymentMethodToken: String, planName: String, billingType: BillingType): ResponseUserLogin!
login(email: String!, password: String!): ResponseUserLogin!
Expand Down
12 changes: 7 additions & 5 deletions api/libs/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ const mailgun = mailgunFactory({
});

export default async function sendMail(to, subject, html) {
return mailgun.messages().send({
from: process.env.MAIL_FROM,
to,
subject,
html,
return new Promise((resolve) => {
mailgun.messages().send({
from: process.env.MAIL_FROM,
to,
subject,
html,
}).then(() => resolve()).catch(() => resolve())
});
}
3 changes: 3 additions & 0 deletions api/migrations/20201104155841_create-users-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export function up(knex) {
t.string('email').notNullable();
t.string('password').notNullable();
t.boolean('is_active').defaultTo(false);
t.string('avatar_url');
t.string('provider');
t.integer('provider_id', 20);
t.dateTime('created_at')
.notNullable()
.defaultTo(knex.raw('CURRENT_TIMESTAMP'));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function up(knex) {
return knex.schema.createTable('user_token', (t) => {
return knex.schema.createTable('user_tokens', (t) => {
t.increments('id');
t.integer('user_id').unsigned().notNullable();
t.string('token');
Expand All @@ -16,5 +16,5 @@ export function up(knex) {
}

export function down(knex) {
return knex.schema.dropTable('user_token');
return knex.schema.dropTable('user_tokens');
}
15 changes: 0 additions & 15 deletions api/migrations/20201119143905_addAvatarAndProviderToUsers.js

This file was deleted.

19 changes: 19 additions & 0 deletions api/migrations/20201126155848_create-products.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function up(knex) {
return knex.schema.createTable('products', (t) => {
t.increments('id');
t.string('name').notNullable();
t.string('type').notNullable();
t.string('stripe_id').notNullable();
t.dateTime('created_at')
.notNullable()
.defaultTo(knex.raw('CURRENT_TIMESTAMP'));
t.dateTime('updated_at')
.notNullable()
.defaultTo(knex.raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
t.unique('type');
});
}

export function down(knex) {
return knex.schema.dropTable('products');
}
20 changes: 20 additions & 0 deletions api/migrations/20201126165854_create-prices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function up(knex) {
return knex.schema.createTable('prices', (t) => {
t.increments('id');
t.float('amount').notNullable();
t.string('type').notNullable();
t.string('stripe_id').notNullable();
t.integer('product_id').unsigned().notNullable();
t.dateTime('created_at')
.notNullable()
.defaultTo(knex.raw('CURRENT_TIMESTAMP'));
t.dateTime('updated_at')
.notNullable()
.defaultTo(knex.raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
t.foreign('product_id').references('id').inTable('products');
});
}

export function down(knex) {
return knex.schema.dropTable('prices');
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ export function up(knex) {
return knex.schema.createTable('user_plans', (t) => {
t.increments('id');
t.integer('user_id').unsigned().notNullable();
t.string('plan_name');
t.float('price');
t.string('billing_type');
t.integer('product_id').unsigned().notNullable();
t.integer('price_id').unsigned().notNullable();
t.dateTime('created_at')
.notNullable()
.defaultTo(knex.raw('CURRENT_TIMESTAMP'));
t.dateTime('updated_at')
.notNullable()
.defaultTo(knex.raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
t.foreign('user_id').references('id').inTable('users');
t.foreign('product_id').references('id').inTable('products');
t.foreign('price_id').references('id').inTable('prices');
});
}

Expand Down
2 changes: 2 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"scripts": {
"start": "nodemon --experimental-modules --es-module-specifier-resolution=node --inspect server",
"dev": "nodemon --experimental-modules --es-module-specifier-resolution=node --inspect server",
"db:create-products": "node --experimental-modules --es-module-specifier-resolution=node scripts/create-products.js",
"db:migrate": "knex --esm migrate:latest --knexfile config/knexfile.cjs",
"db:rollback": "knex --esm migrate:rollback --knexfile config/knexfile.cjs",
"db:down": "knex --esm migrate:down --knexfile config/knexfile.cjs",
Expand All @@ -26,6 +27,7 @@
"dotenv": "^8.2.0",
"express": "^4.17.1",
"fastest-validator": "^1.8.0",
"graphql-resolvers": "^0.4.2",
"handlebars": "^4.7.6",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.12",
Expand Down
18 changes: 18 additions & 0 deletions api/repository/prices.repository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import database from '~/config/database.config';
import { TABLES } from '~/constants/table-name.constant';

const TABLE = TABLES.prices;

export const priceColumns = {
id: 'prices.id',
amount: 'prices.amount',
type: 'prices.type',
stripeId: 'prices.stripe_id',
productId: 'prices.product_id',
createAt: 'prices.created_at',
updatedAt: 'prices.updated_at',
};

export function insertPrice(priceData = [], transaction) {
return database(TABLE).insert(priceData).transacting(transaction);
}
47 changes: 47 additions & 0 deletions api/repository/products.repository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import database from '~/config/database.config';
import { TABLES } from '~/constants/table-name.constant';
import { insertPrice, priceColumns } from './prices.repository';

const TABLE = TABLES.products;

export const productColumns = {
id: 'products.id',
name: 'products.name',
type: 'products.type',
stripeId: 'products.stripe_id',
createAt: 'products.created_at',
updatedAt: 'products.updated_at',
};

export async function insertProduct(productData, priceDatas = []) {
let t;
try {
t = await database.transaction();
const [productId] = await database(TABLE).transacting(t).insert(productData);
await insertPrice(priceDatas.map((priceItem) => ({
...priceItem,
product_id: productId,
})), t);
await t.commit();
return true;
} catch (error) {
if (t) t.rollback();
return false;
}
}

export function findProductByType(type) {
return database(TABLE).where({ type }).first();
}

export function findProductInType(types) {
return database(TABLE).whereIn('type', types);
}

export function findProductAndPriceByType(productType, priceType) {
return database(TABLE)
.join(TABLES.prices, productColumns.id, priceColumns.product_id)
.select(productColumns, `${priceColumns.id} as price_id`, priceColumns.amount, `${priceColumns.type} as price_type`, `${priceColumns.stripeId} as price_stripe_id`)
.where({ [productColumns.type]: productType, [priceColumns.type]: priceType })
.first();
}
36 changes: 22 additions & 14 deletions api/repository/user.repository.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import union from 'lodash/union';
import database from '~/config/database.config';
import { userTokenColumns } from './user_token.repository';
import { userTokenColumns } from './user_tokens.repository';
import { TABLES } from '~/constants/table-name.constant';
import { insertUserPlan } from './user_plans.repository';

const TABLE = TABLES.users;

Expand All @@ -17,22 +18,29 @@ export const usersColumns = {
providerId: 'users.provider_id',
};

export async function findUser({ id, email, provider_id, provider }) {
const condition = {};
if (id) condition.id = id;
if (email) condition.email = email;
if (provider_id) condition.provider_id = provider_id;
if (provider) condition.provider = provider;
export async function findUser(condition) {
return database(TABLE).where(condition).first();
}

export async function createUser({ name, email, password = 'null', provider, provider_id, is_active, avatar_url }) {
const data = { name, email, password };
if (is_active) data.is_active = is_active;
if (provider) data.provider = provider;
if (provider_id) data.provider_id = provider_id;
if (avatar_url) data.avatar_url = avatar_url;
return database(TABLE).insert(data);
export async function createUser(userData, userPlanData = null) {
let t;
try {
t = await database.transaction();
const [userId] = await database(TABLE).transacting(t).insert(userData);

if (userPlanData) {
await insertUserPlan({
...userPlanData,
user_id: userId,
}, t);
}

await t.commit();
return userId;
} catch (error) {
if (t) t.rollback();
return new Error(error);
}
}

export async function updateUser(id, data) {
Expand Down
Loading

0 comments on commit e125ed1

Please sign in to comment.