Skip to content
This repository has been archived by the owner on May 10, 2023. It is now read-only.

Commit

Permalink
chore: adjust to use userId everywhere and migration now creates dumm…
Browse files Browse the repository at this point in the history
…y users
  • Loading branch information
MichaelKohler committed Aug 29, 2020
1 parent f411382 commit dd9369b
Show file tree
Hide file tree
Showing 20 changed files with 134 additions and 71 deletions.
2 changes: 1 addition & 1 deletion Dockerfile-mig
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ WORKDIR '/app/server'

RUN npm ci

CMD ["node", "migration/index.js"]
CMD ["node", "migration.js"]
21 changes: 19 additions & 2 deletions server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,27 @@ app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser((user, done) => {
const extendedUser = Object.assign({}, user, { email: user && user.emails && user.emails[0] && user.emails[0].value });
const email = user && user.emails && user.emails[0] && user.emails[0].value;

if (!email) {
return done(new Error('NO_EMAIL_PROVIDED'), null);
}

const extendedUser = Object.assign({}, user, { email });
return done(null, extendedUser);
});
passport.deserializeUser((sessionUser, done) => done(null, sessionUser));

passport.deserializeUser((sessionUser, done) => {
users.get(sessionUser.email)
.then((user) => {
const extendedUser = Object.assign({}, sessionUser, { id: user.id });
return done(null, extendedUser);
})
.catch((error) => {
console.error('FAILED_DESERIALIZE_USER', error);
return done(error, null);
});
});

if (AUTH0_DOMAIN) {
Auth0Strategy.prototype.authorizationParams = function(options = {}) {
Expand Down
3 changes: 2 additions & 1 deletion server/lib/models/sentence.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
module.exports = (sequelize, DataTypes) => {
const Sentence = sequelize.define('Sentence', {
sentence: DataTypes.STRING,
user: DataTypes.STRING,
source: DataTypes.STRING,
batch: DataTypes.STRING,
userId: DataTypes.STRING,
localeId: DataTypes.STRING,
}, {});

Sentence.associate = (models) => {
Sentence.hasMany(models.Vote, { as: 'Vote', foreignKey: 'sentenceId' });
Sentence.hasOne(models.User, { as: 'User', foreignKey: 'id' });
};

return Sentence;
Expand Down
3 changes: 2 additions & 1 deletion server/lib/models/vote.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

module.exports = (sequelize, DataTypes) => {
const Vote = sequelize.define('Vote', {
user: DataTypes.STRING,
userId: DataTypes.STRING,
approval: DataTypes.BOOLEAN,
}, {});

Vote.associate = (models) => {
Vote.belongsTo(models.Sentence);
Vote.hasOne(models.User, { as: 'User', foreignKey: 'id' });
};

return Vote;
Expand Down
32 changes: 16 additions & 16 deletions server/lib/sentences.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@ function getApprovedSentencesForLocale(locale) {
return sequelize.query(validatedSentencesQuery, { type: QueryTypes.SELECT });
}

async function getSentencesForReview({ locale, user }) {
async function getSentencesForReview({ locale, userId }) {
debug('GETTING_SENTENCES_FOR_LOCALE', locale);

const query = getReviewQuery({ locale, user });
const query = getReviewQuery({ locale, userId });
const sentences = await sequelize.query(query, { type: QueryTypes.SELECT });
return sentences;
}

async function getRejectedSentences({ user }) {
async function getRejectedSentences({ userId }) {
debug('GETTING_REJECTED_SENTENCES');

const query = getRejectedSentencesQuery({ user });
const query = getRejectedSentencesQuery({ userId });
const sentences = await sequelize.query(query, { type: QueryTypes.SELECT });
const sentencesPerLocale = sentences.reduce((perLocale, sentenceInfo) => {
perLocale[sentenceInfo.localeId] = perLocale[sentenceInfo.localeId] || [];
Expand Down Expand Up @@ -103,11 +103,11 @@ async function getValidatedSentencesCountForLocale(locale) {
return validatedCount;
}

async function getUnreviewedByYouCountForLocales(locales, user) {
async function getUnreviewedByYouCountForLocales(locales, userId) {
const stats = {};

for await (const locale of locales) {
const reviewQuery = getReviewQuery({ locale, user });
const reviewQuery = getReviewQuery({ locale, userId });
const query = `
SELECT COUNT(*) FROM
(${reviewQuery}) as approved_sentences;
Expand Down Expand Up @@ -150,12 +150,12 @@ function calculateStats(stats, sentenceInfo) {
return stats;
}

async function getUserAddedSentencesPerLocale(user) {
async function getUserAddedSentencesPerLocale(userId) {
debug('GETTING_USER_ADDED_STATS');

const options = {
where: {
user,
userId,
},
include: [{
model: Vote,
Expand All @@ -173,7 +173,7 @@ async function getUserAddedSentencesPerLocale(user) {
async function addSentences(data) {
const {
sentences,
user,
userId,
source,
locale = FALLBACK_LOCALE,
} = data;
Expand All @@ -188,7 +188,7 @@ async function addSentences(data) {
const addSentenceToDatabase = (sentence, transaction, isValidated) => {
const params = {
sentence,
user,
userId,
source,
batch,
localeId: locale,
Expand All @@ -202,7 +202,7 @@ async function addSentences(data) {

const voteParams = {
sentenceId: sentence.id,
user,
userId,
approval: true,
};
return votes.addVoteForSentence(voteParams, transaction);
Expand All @@ -229,7 +229,7 @@ async function addSentences(data) {
return { errors: filtered, duplicates: duplicateCounter };
}

function getReviewQuery({ locale, user }) {
function getReviewQuery({ locale, userId }) {
return `
SELECT
Sentences.id,
Expand All @@ -242,7 +242,7 @@ function getReviewQuery({ locale, user }) {
WHERE Sentences.localeId = "${locale}"
AND NOT EXISTS (SELECT *
FROM Votes
WHERE Sentences.id = Votes.sentenceId AND Votes.user = "${user}")
WHERE Sentences.id = Votes.sentenceId AND Votes.userId = "${userId}")
GROUP BY Sentences.id
HAVING
number_of_votes < 2 OR # not enough votes yet
Expand All @@ -264,11 +264,11 @@ function getValidatedSentencesQuery({ locale }) {
number_of_approving_votes >= 2`;
}

function getRejectedSentencesQuery({ user, locale }) {
function getRejectedSentencesQuery({ userId, locale }) {
let whereClause = '';

if (user) {
whereClause = `WHERE Sentences.user = '${user}'`;
if (typeof userId !== 'undefined') {
whereClause = `WHERE Sentences.userId = '${userId}'`;
}

if (locale) {
Expand Down
1 change: 1 addition & 0 deletions server/lib/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ async function get(email) {
const [user] = await User.findAll({ where: { email }});
const userLanguages = user.languages || '';
return {
id: user.id,
email: user.email,
languages: userLanguages.split(',').filter(Boolean),
settings: {
Expand Down
2 changes: 1 addition & 1 deletion server/lib/votes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async function addVoteForSentence(voteParams, transaction) {
const [, created] = await Vote.findOrCreate({
where: {
sentenceId: voteParams.sentenceId,
user: voteParams.user,
userId: voteParams.userId,
},
defaults: voteParams,
transaction,
Expand Down
35 changes: 26 additions & 9 deletions server/migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ if (!HOSTNAME || !USERNAME || !PASSWORD || !DATABASE || !BACKUP_PATH) {

let connection;
let locales;
let userIdCache = {};

const connectionLimit = parseInt(CONNECTIONS, 10) || 50;
console.log(`Using max ${connectionLimit} connections..`);
Expand Down Expand Up @@ -53,23 +54,39 @@ console.log(`Using max ${connectionLimit} connections..`);
const locale = locales.find((locale) => locale.id === scLanguage);
const fileContent = await fs.promises.readFile(sentenceCollectorFilePath, 'utf-8');
const content = JSON.parse(fileContent);
const sortedByApprovingVotes = content.sort((a, b) => b.valid.length - a.valid.length);
console.log(`${sortedByApprovingVotes.length} sentences to migrate`);
await Promise.all(sortedByApprovingVotes.map((sentenceInfo) => processSentence(sentenceInfo, locale.id)));
console.log(`${content.length} sentences to migrate`);

const allUsers = [...new Set(content.map((sentences) => sentences.username))];
console.log(`${allUsers.length} users to create `);

for (const user of allUsers) {
if (!userIdCache[user]) {
userIdCache[user] = await createUser(user);
}
}

await Promise.all(content.map((sentence) => processSentence(sentence, locale.id)));
}

console.log('We are done!');
process.exit(0);
})();

async function createUser(username) {
const dummyEmail = `${username}@sentencecollector.local`;
const [insertedRecord] = await connection.query('INSERT INTO Users SET ?', { email: dummyEmail, createdAt: new Date(), updatedAt: new Date() });
return insertedRecord.insertId;
}

async function processSentence(sentenceInfo, localeId) {
const userId = userIdCache[sentenceInfo.username];
const createdAt = typeof sentenceInfo.createdAt !== 'undefined' ? new Date(sentenceInfo.createdAt) : new Date();
const updatedAt = typeof sentenceInfo.last_modified !== 'undefined' ? new Date(sentenceInfo.last_modified) : new Date();

const sentenceParams = {
sentence: sentenceInfo.sentence,
source: sentenceInfo.source || '',
user: sentenceInfo.username,
userId,
localeId,
createdAt,
updatedAt,
Expand All @@ -78,8 +95,8 @@ async function processSentence(sentenceInfo, localeId) {
try {
const insertedId = await insertSentence(sentenceParams);
await Promise.all([
...sentenceInfo.valid.map((user) => insertVote(sentenceInfo, user, insertedId, true)),
...sentenceInfo.invalid.map((user) => insertVote(sentenceInfo, user, insertedId, false)),
...sentenceInfo.valid.map((user) => insertVote(sentenceInfo, user, userIdCache[user], insertedId, true)),
...sentenceInfo.invalid.map((user) => insertVote(sentenceInfo, user, userIdCache[user], insertedId, false)),
]);
} catch (error) {
console.log(error.message);
Expand All @@ -91,17 +108,17 @@ async function insertSentence(sentenceParams) {
return insertedSentence[0].insertId;
}

async function insertVote(sentenceInfo, user, insertedId, approval) {
async function insertVote(sentenceInfo, username, userId, insertedId, approval) {
let createdAt = typeof sentenceInfo.last_modified !== 'undefined' ? new Date(sentenceInfo.last_modified) : new Date();

const voteTimestamp = sentenceInfo[`Sentences_Meta_UserVoteDate_${user}`];
const voteTimestamp = sentenceInfo[`Sentences_Meta_UserVoteDate_${username}`];
if (Number.isInteger(voteTimestamp)) {
createdAt = new Date(voteTimestamp);
}

const voteParams = {
approval,
user,
userId,
sentenceId: insertedId,
createdAt,
updatedAt: createdAt,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = {
up: (queryInterface) => queryInterface.removeColumn('Sentences', 'user'),
down: (queryInterface, Sequelize) => queryInterface.addColumn('Sentences', 'user', Sequelize.STRING),
};
6 changes: 6 additions & 0 deletions server/migrations/20200829170845-sentences-real-user-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = {
up: (queryInterface, Sequelize) => queryInterface.addColumn('Sentences', 'userId', Sequelize.STRING),
down: (queryInterface) => queryInterface.removeColumn('Sentences', 'userId'),
};
6 changes: 6 additions & 0 deletions server/migrations/20200829180012-votes-remove-user-column.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = {
up: (queryInterface) => queryInterface.removeColumn('Votes', 'user'),
down: (queryInterface, Sequelize) => queryInterface.addColumn('Votes', 'user', Sequelize.STRING),
};
6 changes: 6 additions & 0 deletions server/migrations/20200829180036-votes-real-user-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = {
up: (queryInterface, Sequelize) => queryInterface.addColumn('Votes', 'userId', Sequelize.STRING),
down: (queryInterface) => queryInterface.removeColumn('Votes', 'userId'),
};
18 changes: 9 additions & 9 deletions server/routes/sentences.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ const router = express.Router(); // eslint-disable-line new-cap

router.get('/review', async (req, res) => {
const locale = req.query.locale;
const user = req.user && req.user.email;
debug('GET_SENTENCES_FOR_REVIEW', user);
const userId = req.user && req.user.id;
debug('GET_SENTENCES_FOR_REVIEW', userId);

try {
const foundSentences = await sentences.getSentencesForReview({ locale, user });
const foundSentences = await sentences.getSentencesForReview({ locale, userId });
res.json(foundSentences);
} catch (error) {
debug('GET_SENTENCES_FOR_REVIEW_ERROR', error);
Expand All @@ -26,9 +26,9 @@ router.get('/review', async (req, res) => {
});

router.get('/rejected', async (req, res) => {
const user = req.user && req.user.email;
debug('GET_REJECTED_SENTENCES', user);
sentences.getRejectedSentences({ user })
const userId = req.user && req.user.id;
debug('GET_REJECTED_SENTENCES', userId);
sentences.getRejectedSentences({ userId })
.then((foundSentences) => res.json(foundSentences))
.catch((error) => {
debug('GET_REJECTED_SENTENCES_ERROR', error);
Expand All @@ -38,11 +38,11 @@ router.get('/rejected', async (req, res) => {
});

router.put('/', async (req, res) => {
const user = req.user && req.user.email;
debug('CREATE_SENTENCES', req.body, user);
const userId = req.user && req.user.id;
debug('CREATE_SENTENCES', req.body, userId);

try {
const result = await sentences.addSentences({ ...req.body, user });
const result = await sentences.addSentences({ ...req.body, userId });
res.status(STATUS_CREATED);
res.json(result);
} catch (error) {
Expand Down
8 changes: 4 additions & 4 deletions server/routes/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ const STATUS_ERROR = 500;
const router = express.Router(); // eslint-disable-line new-cap

router.get('/', async (req, res) => {
const sessionUser = req.user && req.user.email;
const sessionUserId = req.user && req.user.id;
const queryLocales = req.query.locales || '';
const locales = queryLocales.split(',');

debug('GET_STATS', sessionUser);
debug('GET_STATS', sessionUserId);

try {
const [all, user, userUnreviewed] = await Promise.all([
sentences.getStats(locales),
sentences.getUserAddedSentencesPerLocale(sessionUser),
sentences.getUnreviewedByYouCountForLocales(locales, sessionUser),
sentences.getUserAddedSentencesPerLocale(sessionUserId),
sentences.getUnreviewedByYouCountForLocales(locales, sessionUserId),
]);

res.json({
Expand Down
2 changes: 1 addition & 1 deletion server/routes/votes.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ router.put('/', async (req, res) => {
try {
const voteParams = {
sentenceId: sentenceId,
user: req.user && req.user.email,
userId: req.user && req.user.id,
approval: isValidated,
};
await votes.addVoteForSentence(voteParams);
Expand Down
Loading

0 comments on commit dd9369b

Please sign in to comment.