From ce828945be9a7c61f1192c5a61d6b9a42b066e36 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Tue, 20 Feb 2024 14:28:50 +0100
Subject: [PATCH 01/21] feat: Add game to post
---
Helpers.js | 10 +-
migration-updates/add-game-to-posts.js | 24 +
migrations/create-post.js | 3 +
models/Post.js | 1 +
routes/Auth.js | 2 +-
routes/Post.js | 1062 ++++++++++++------------
6 files changed, 573 insertions(+), 529 deletions(-)
create mode 100644 migration-updates/add-game-to-posts.js
diff --git a/Helpers.js b/Helpers.js
index a01f688..58cac79 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -965,6 +965,7 @@ const fullPostAttributes = [
'totalReposts',
'totalRatings',
'totalLinks',
+ 'game'
]
// todo: replace all use cases with const fullPostAttributes above
@@ -984,6 +985,7 @@ function findFullPostAttributes(model, accountId) {
'totalReposts',
'totalRatings',
'totalLinks',
+ 'game',
// accountLike('post', model, accountId),
// accountComment('post', model, accountId),
// accountLink('post', model, accountId),
@@ -1630,12 +1632,10 @@ function sendGBGInvite(player, postId, creator, settings) {
Allowed bead types: ${allowedBeadTypes.join(',')}
- Time window for moves: ${
- moveTimeWindow ? `${moveTimeWindow} minutes` : 'Off'
+ Time window for moves: ${moveTimeWindow ? `${moveTimeWindow} minutes` : 'Off'
}
- Character limit: ${
- characterLimit ? `${characterLimit} characters` : 'Off'
+ Character limit: ${characterLimit ? `${characterLimit} characters` : 'Off'
}
Audio time limit: ${moveDuration ? `${moveDuration} seconds` : 'Off'}
@@ -1700,6 +1700,7 @@ function createPost(data, files, accountId) {
event,
poll,
glassBeadGame,
+ game,
card,
color,
watermark,
@@ -1721,6 +1722,7 @@ function createPost(data, files, accountId) {
color: color || null,
watermark: !!watermark,
lastActivity: new Date(),
+ game,
})
// todo: add the correct notification type
diff --git a/migration-updates/add-game-to-posts.js b/migration-updates/add-game-to-posts.js
new file mode 100644
index 0000000..03ac257
--- /dev/null
+++ b/migration-updates/add-game-to-posts.js
@@ -0,0 +1,24 @@
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) => {
+ return Promise.all([
+ queryInterface.addColumn(
+ 'Posts',
+ 'game',
+ {
+ type: Sequelize.DataTypes.JSON,
+ },
+ { transaction: t }
+ ),
+ ])
+ })
+ },
+
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) => {
+ return Promise.all([
+ queryInterface.removeColumn('Posts', 'game', { transaction: t }),
+ ])
+ })
+ },
+}
diff --git a/migrations/create-post.js b/migrations/create-post.js
index 42e91fa..9172eee 100644
--- a/migrations/create-post.js
+++ b/migrations/create-post.js
@@ -59,6 +59,9 @@ module.exports = {
totalGlassBeadGames: {
type: Sequelize.INTEGER,
},
+ game: {
+ type: Sequelize.JSON,
+ },
lastActivity: {
type: Sequelize.DATE,
},
diff --git a/models/Post.js b/models/Post.js
index c27f000..cce0993 100644
--- a/models/Post.js
+++ b/models/Post.js
@@ -25,6 +25,7 @@ module.exports = (sequelize, DataTypes) => {
totalReposts: DataTypes.INTEGER,
totalRatings: DataTypes.INTEGER,
totalGlassBeadGames: DataTypes.INTEGER,
+ game: DataTypes.JSON,
lastActivity: DataTypes.DATE,
},
{}
diff --git a/routes/Auth.js b/routes/Auth.js
index 0abd291..dece6b5 100644
--- a/routes/Auth.js
+++ b/routes/Auth.js
@@ -12,7 +12,7 @@ const jwt = require('jsonwebtoken')
const sgMail = require('@sendgrid/mail')
const webpush = require('web-push')
// webpush.setGCMAPIKey('')
-webpush.setVapidDetails('https://weco.io', vapidPublicKey, vapidPrivateKey)
+// webpush.setVapidDetails('https://weco.io', vapidPublicKey, vapidPrivateKey)
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
const authenticateToken = require('../middleware/authenticateToken')
diff --git a/routes/Post.js b/routes/Post.js
index be14a45..514628f 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -1267,15 +1267,15 @@ router.get('/card-faces', async (req, res) => {
})
const linkToImage = linkToImageBlock
? await Link.findOne({
- where: {
- itemAType: 'image-block',
- itemAId: linkToImageBlock.itemBId,
- itemBType: 'image',
- state: 'active',
- },
- attributes: [],
- include: { model: Image, attributes: ['url'] },
- })
+ where: {
+ itemAType: 'image-block',
+ itemAId: linkToImageBlock.itemBId,
+ itemBType: 'image',
+ state: 'active',
+ },
+ attributes: [],
+ include: { model: Image, attributes: ['url'] },
+ })
: null
blocks.push({
...link.Post.dataValues,
@@ -1304,71 +1304,71 @@ router.post('/create-post', authenticateToken, async (req, res) => {
// add spaces and increment space stats
const addSpaces = spaceIds
? await new Promise(async (resolve) => {
- const addDirectSpaces = await Promise.all(
- spaceIds.map((spaceId) =>
- createSpacePost(accountId, spaceId, post.id, 'post', 'direct')
- )
- )
- // gather direct spaces ancestor ids
- const spaces = await Space.findAll({
- where: { id: spaceIds, state: 'active' },
- attributes: ['id'],
- include: {
- model: Space,
- as: 'SpaceAncestors',
- attributes: ['id'],
- through: { where: { state: 'open' }, attributes: [] },
- },
- })
- let ancestorIds = []
- spaces.forEach((space) =>
- ancestorIds.push(...space.SpaceAncestors.map((space) => space.id))
- )
- // remove duplicates and direct spaces
- ancestorIds = [...new Set(ancestorIds)].filter((id) => !spaceIds.includes(id))
- // store ancestor ids for response
- allSpaceIds.push(...ancestorIds)
- const addIndirectSpaces = await Promise.all(
- ancestorIds.map((spaceId) =>
- createSpacePost(accountId, spaceId, post.id, 'post', 'indirect')
- )
- )
- // increment space stats
- const incrementSpaceStats = await Space.increment('totalPosts', {
- where: { id: allSpaceIds },
- silent: true,
- })
- Promise.all([addDirectSpaces, addIndirectSpaces, incrementSpaceStats])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const addDirectSpaces = await Promise.all(
+ spaceIds.map((spaceId) =>
+ createSpacePost(accountId, spaceId, post.id, 'post', 'direct')
+ )
+ )
+ // gather direct spaces ancestor ids
+ const spaces = await Space.findAll({
+ where: { id: spaceIds, state: 'active' },
+ attributes: ['id'],
+ include: {
+ model: Space,
+ as: 'SpaceAncestors',
+ attributes: ['id'],
+ through: { where: { state: 'open' }, attributes: [] },
+ },
+ })
+ let ancestorIds = []
+ spaces.forEach((space) =>
+ ancestorIds.push(...space.SpaceAncestors.map((space) => space.id))
+ )
+ // remove duplicates and direct spaces
+ ancestorIds = [...new Set(ancestorIds)].filter((id) => !spaceIds.includes(id))
+ // store ancestor ids for response
+ allSpaceIds.push(...ancestorIds)
+ const addIndirectSpaces = await Promise.all(
+ ancestorIds.map((spaceId) =>
+ createSpacePost(accountId, spaceId, post.id, 'post', 'indirect')
+ )
+ )
+ // increment space stats
+ const incrementSpaceStats = await Space.increment('totalPosts', {
+ where: { id: allSpaceIds },
+ silent: true,
+ })
+ Promise.all([addDirectSpaces, addIndirectSpaces, incrementSpaceStats])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
// todo: notify source creator
const addLink = source
? await new Promise(async (resolve) => {
- const createNewLink = await Link.create({
- state: 'active',
- creatorId: accountId,
- relationship: 'link',
- itemAType: source.type,
- itemBType: 'post',
- itemAId: source.id,
- itemBId: post.id,
- description: source.linkDescription,
- totalLikes: 0,
- totalComments: 0,
- totalRatings: 0,
- })
- const updateSourceLinks = await Post.increment('totalLinks', {
- where: { id: source.id },
- silent: true,
- })
- const updateTargetLinks = await post.update({ totalLinks: 1 }, { silent: true })
- Promise.all([createNewLink, updateSourceLinks, updateTargetLinks])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const createNewLink = await Link.create({
+ state: 'active',
+ creatorId: accountId,
+ relationship: 'link',
+ itemAType: source.type,
+ itemBType: 'post',
+ itemAId: source.id,
+ itemBId: post.id,
+ description: source.linkDescription,
+ totalLikes: 0,
+ totalComments: 0,
+ totalRatings: 0,
+ })
+ const updateSourceLinks = await Post.increment('totalLinks', {
+ where: { id: source.id },
+ silent: true,
+ })
+ const updateTargetLinks = await post.update({ totalLinks: 1 }, { silent: true })
+ Promise.all([createNewLink, updateSourceLinks, updateTargetLinks])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
Promise.all([addSpaces, addLink])
@@ -1404,14 +1404,14 @@ router.post('/create-comment', authenticateToken, async (req, res) => {
const createNotification = isOwnPost
? null
: await Notification.create({
- ownerId: parentPost.Creator.id,
- type: parentPost.type === 'comment' ? 'comment-reply' : 'post-comment',
- seen: false,
- spaceAId: postData.originSpaceId,
- userId: accountId,
- postId: parent.id,
- commentId: post.id,
- })
+ ownerId: parentPost.Creator.id,
+ type: parentPost.type === 'comment' ? 'comment-reply' : 'post-comment',
+ seen: false,
+ spaceAId: postData.originSpaceId,
+ userId: accountId,
+ postId: parent.id,
+ commentId: post.id,
+ })
const muted = await accountMuted(accountId, parentPost.Creator)
const skipEmail = isOwnPost || muted || parentPost.Creator.emailsDisabled
const messageText =
@@ -1419,14 +1419,14 @@ router.post('/create-comment', authenticateToken, async (req, res) => {
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: parentPost.Creator.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: parentPost.Creator.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${parentPost.Creator.name}, ${account.name} just ${messageText} ${parentPost.type} on weco:
http://${appURL}/p/${post.id}
`,
- html: `
+ html: `
Hi ${parentPost.Creator.name},
@@ -1436,7 +1436,7 @@ router.post('/create-comment', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
Promise.all([createNotification, sendEmail])
.then(() => resolve())
.catch((error) => resolve(error))
@@ -1468,44 +1468,44 @@ router.post('/create-chat-message', authenticateToken, async (req, res) => {
// add spaces and increment space stats
const addSpaces = spaceIds
? await new Promise(async (resolve) => {
- const addDirectSpaces = await Promise.all(
- spaceIds.map((spaceId) =>
- createSpacePost(accountId, spaceId, post.id, 'post', 'direct')
- )
- )
- // gather direct spaces ancestor ids
- const spaces = await Space.findAll({
- where: { id: spaceIds, state: 'active' },
- attributes: ['id'],
- include: {
- model: Space,
- as: 'SpaceAncestors',
- attributes: ['id'],
- through: { where: { state: 'open' }, attributes: [] },
- },
- })
- let ancestorIds = []
- spaces.forEach((space) =>
- ancestorIds.push(...space.SpaceAncestors.map((space) => space.id))
- )
- // remove duplicates and direct spaces
- ancestorIds = [...new Set(ancestorIds)].filter((id) => !spaceIds.includes(id))
- // store ancestor ids for response
- allSpaceIds.push(...ancestorIds)
- const addIndirectSpaces = await Promise.all(
- ancestorIds.map((spaceId) =>
- createSpacePost(accountId, spaceId, post.id, 'post', 'indirect')
- )
- )
- // increment space stats
- const incrementSpaceStats = await Space.increment('totalPosts', {
- where: { id: allSpaceIds },
- silent: true,
- })
- Promise.all([addDirectSpaces, addIndirectSpaces, incrementSpaceStats])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const addDirectSpaces = await Promise.all(
+ spaceIds.map((spaceId) =>
+ createSpacePost(accountId, spaceId, post.id, 'post', 'direct')
+ )
+ )
+ // gather direct spaces ancestor ids
+ const spaces = await Space.findAll({
+ where: { id: spaceIds, state: 'active' },
+ attributes: ['id'],
+ include: {
+ model: Space,
+ as: 'SpaceAncestors',
+ attributes: ['id'],
+ through: { where: { state: 'open' }, attributes: [] },
+ },
+ })
+ let ancestorIds = []
+ spaces.forEach((space) =>
+ ancestorIds.push(...space.SpaceAncestors.map((space) => space.id))
+ )
+ // remove duplicates and direct spaces
+ ancestorIds = [...new Set(ancestorIds)].filter((id) => !spaceIds.includes(id))
+ // store ancestor ids for response
+ allSpaceIds.push(...ancestorIds)
+ const addIndirectSpaces = await Promise.all(
+ ancestorIds.map((spaceId) =>
+ createSpacePost(accountId, spaceId, post.id, 'post', 'indirect')
+ )
+ )
+ // increment space stats
+ const incrementSpaceStats = await Space.increment('totalPosts', {
+ where: { id: allSpaceIds },
+ silent: true,
+ })
+ Promise.all([addDirectSpaces, addIndirectSpaces, incrementSpaceStats])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
// attach parent comment
const linkComment = parent ? await attachComment(post, parent, accountId) : null
@@ -1631,48 +1631,48 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
const notifyPlayers =
!synchronous && multiplayer
? await new Promise(async (resolve) => {
- // find other players to notify
- const otherPlayers = []
- if (gamePost.Players.length) {
- // if restricted game, use linked Players
- otherPlayers.push(...gamePost.Players.filter((p) => p.id !== accountId))
- } else {
- // if open game, use linked Bead Creators
- gamePost.Beads.forEach((bead) => {
- // filter out game creator and existing records
- if (
- bead.Creator.id !== accountId &&
- !otherPlayers.find((p) => p.id === bead.Creator.id)
- )
- otherPlayers.push(bead.Creator)
- })
- }
- // notify players
- const sendNotifications = await Promise.all(
- otherPlayers.map(
- (p) =>
- new Promise(async (resolve2) => {
- const notifyPlayer = await Notification.create({
- type: 'gbg-move-from-other-player',
- ownerId: p.id,
- postId: parent.id,
- userId: accountId,
- seen: false,
- })
- const emailPlayer = p.emailsDisabled
- ? null
- : await sgMail.send({
- to: p.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ // find other players to notify
+ const otherPlayers = []
+ if (gamePost.Players.length) {
+ // if restricted game, use linked Players
+ otherPlayers.push(...gamePost.Players.filter((p) => p.id !== accountId))
+ } else {
+ // if open game, use linked Bead Creators
+ gamePost.Beads.forEach((bead) => {
+ // filter out game creator and existing records
+ if (
+ bead.Creator.id !== accountId &&
+ !otherPlayers.find((p) => p.id === bead.Creator.id)
+ )
+ otherPlayers.push(bead.Creator)
+ })
+ }
+ // notify players
+ const sendNotifications = await Promise.all(
+ otherPlayers.map(
+ (p) =>
+ new Promise(async (resolve2) => {
+ const notifyPlayer = await Notification.create({
+ type: 'gbg-move-from-other-player',
+ ownerId: p.id,
+ postId: parent.id,
+ userId: accountId,
+ seen: false,
+ })
+ const emailPlayer = p.emailsDisabled
+ ? null
+ : await sgMail.send({
+ to: p.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${p.name}, ${creator.name} just added a new bead.
https://${appURL}/p/${parent.id}
`,
- html: `
+ html: `
Hi ${p.name},
@@ -1681,26 +1681,26 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
bead.
`,
- })
- Promise.all([notifyPlayer, emailPlayer])
- .then(() => resolve2())
- .catch((error) => resolve2(error))
- })
- )
- )
- // schedule next deadline
- const scheduleNewDeadline = moveTimeWindow
- ? await scheduleNextBeadDeadline(
- parent.id,
- gamePost.GlassBeadGame,
- gamePost.Players
- )
- : null
+ })
+ Promise.all([notifyPlayer, emailPlayer])
+ .then(() => resolve2())
+ .catch((error) => resolve2(error))
+ })
+ )
+ )
+ // schedule next deadline
+ const scheduleNewDeadline = moveTimeWindow
+ ? await scheduleNextBeadDeadline(
+ parent.id,
+ gamePost.GlassBeadGame,
+ gamePost.Players
+ )
+ : null
- Promise.all([sendNotifications, scheduleNewDeadline])
- .then((data) => resolve(data[1]))
- .catch((error) => resolve(error))
- })
+ Promise.all([sendNotifications, scheduleNewDeadline])
+ .then((data) => resolve(data[1]))
+ .catch((error) => resolve(error))
+ })
: null
const incrementTotalBeads = await GlassBeadGame.increment('totalBeads', {
@@ -1721,7 +1721,7 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
// test
router.post('/update-post', authenticateToken, async (req, res) => {
const accountId = req.user ? req.user.id : null
- const { id, mediaTypes, title, text, searchableText, mentions, urls: newUrls } = req.body
+ const id = req.body.id;
const post = await Post.findOne({
where: { id, creatorId: accountId },
attributes: ['id', 'type', 'mediaTypes'],
@@ -1733,71 +1733,83 @@ router.post('/update-post', authenticateToken, async (req, res) => {
})
if (!post) res.status(401).json({ message: 'Unauthorized' })
else {
+ const toUpdate = {};
+ for (const key of ['mediaTypes', 'title', 'text', 'searchableText', 'game']) {
+ if (key in req.body) {
+ toUpdate[key] = req.body[key]
+ }
+ }
+ const promises = []
const updatePost = await Post.update(
- { mediaTypes, title, text, searchableText },
+ toUpdate,
{ where: { id, creatorId: accountId } }
)
- // update urls
- const oldUrlBlockLinks = await Link.findAll({
- where: {
- itemAId: post.id,
- itemAType: post.type,
- itemBType: 'url-block',
- state: 'active',
- },
- attributes: ['id', 'itemBId'],
- })
- const oldUrlLinks = await Promise.all(
- oldUrlBlockLinks.map(
- (oldUrlBlockLink) =>
- new Promise(async (resolve) => {
- const oldUrlLink = await Link.findOne({
- where: {
- itemAId: oldUrlBlockLink.itemBId,
- itemAType: 'url-block',
- itemBType: 'url',
- state: 'active',
- },
- attributes: [],
- include: { model: Url, attributes: ['url'] },
+ promises.push(updatePost)
+ if ('urls' in req.body) {
+ const newUrls = req.body.urls;
+ // update urls
+ const oldUrlBlockLinks = await Link.findAll({
+ where: {
+ itemAId: post.id,
+ itemAType: post.type,
+ itemBType: 'url-block',
+ state: 'active',
+ },
+ attributes: ['id', 'itemBId'],
+ })
+ const oldUrlLinks = await Promise.all(
+ oldUrlBlockLinks.map(
+ (oldUrlBlockLink) =>
+ new Promise(async (resolve) => {
+ const oldUrlLink = await Link.findOne({
+ where: {
+ itemAId: oldUrlBlockLink.itemBId,
+ itemAType: 'url-block',
+ itemBType: 'url',
+ state: 'active',
+ },
+ attributes: [],
+ include: { model: Url, attributes: ['url'] },
+ })
+ resolve({ id: oldUrlBlockLink.id, url: oldUrlLink.Url.url })
})
- resolve({ id: oldUrlBlockLink.id, url: oldUrlLink.Url.url })
- })
+ )
)
- )
- const removeOldUrls = await Promise.all(
- oldUrlLinks.map(
- (oldUrlLink) =>
- new Promise(async (resolve) => {
- const match = newUrls.find((newUrl) => newUrl.url === oldUrlLink.url)
- if (match) resolve()
- else {
- Link.update({ state: 'deleted' }, { where: { id: oldUrlLink.id } })
- .then(() => resolve())
- .catch((error) => resolve(error))
- }
- })
+ const removeOldUrls = await Promise.all(
+ oldUrlLinks.map(
+ (oldUrlLink) =>
+ new Promise(async (resolve) => {
+ const match = newUrls.find((newUrl) => newUrl.url === oldUrlLink.url)
+ if (match) resolve()
+ else {
+ Link.update({ state: 'deleted' }, { where: { id: oldUrlLink.id } })
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ }
+ })
+ )
)
- )
- const addNewUrls = await Promise.all(
- newUrls.map(
- (newUrl, index) =>
- new Promise((resolve) => {
- const match = oldUrlLinks.find(
- (oldUrlLink) => oldUrlLink.url === newUrl.url
- )
- if (match) {
- Link.update({ index }, { where: { id: match.id } })
- .then(() => resolve())
- .catch((error) => resolve(error))
- } else {
- createUrl(accountId, id, post.type, newUrl, index)
- .then(() => resolve())
- .catch((error) => resolve(error))
- }
- })
+ const addNewUrls = await Promise.all(
+ newUrls.map(
+ (newUrl, index) =>
+ new Promise((resolve) => {
+ const match = oldUrlLinks.find(
+ (oldUrlLink) => oldUrlLink.url === newUrl.url
+ )
+ if (match) {
+ Link.update({ index }, { where: { id: match.id } })
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ } else {
+ createUrl(accountId, id, post.type, newUrl, index)
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ }
+ })
+ )
)
- )
+ promises.push(removeOldUrls, addNewUrls)
+ }
// const oldUrls = await post.getBlocks({
// attributes: ['id'],
@@ -1845,46 +1857,47 @@ router.post('/update-post', authenticateToken, async (req, res) => {
// )
// notify mentions
- const mentionedUsers = await User.findAll({
- where: { handle: mentions, state: 'active' },
- attributes: ['id', 'name', 'email', 'emailsDisabled'],
- })
+ if ('mentions' in req.body) {
+ const mentionedUsers = await User.findAll({
+ where: { handle: req.body.mentions, state: 'active' },
+ attributes: ['id', 'name', 'email', 'emailsDisabled'],
+ })
- const notifyMentions = await Promise.all(
- mentionedUsers.map(
- (user) =>
- new Promise(async (resolve) => {
- const alreadySent = await Notification.findOne({
- where: {
- ownerId: user.id,
- type: `${post.type}-mention`, // post, comment, or bead (todo: poll-answer)
- userId: accountId,
- postId: id,
- },
- })
- if (alreadySent) resolve()
- else {
- const sendNotification = await Notification.create({
- ownerId: user.id,
- type: `${post.type}-mention`,
- seen: false,
- userId: accountId,
- postId: id,
+ const notifyMentions = await Promise.all(
+ mentionedUsers.map(
+ (user) =>
+ new Promise(async (resolve) => {
+ const alreadySent = await Notification.findOne({
+ where: {
+ ownerId: user.id,
+ type: `${post.type}-mention`, // post, comment, or bead (todo: poll-answer)
+ userId: accountId,
+ postId: id,
+ },
})
- const sendEmail = user.emailsDisabled
- ? null
- : await sgMail.send({
- to: user.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ if (alreadySent) resolve()
+ else {
+ const sendNotification = await Notification.create({
+ ownerId: user.id,
+ type: `${post.type}-mention`,
+ seen: false,
+ userId: accountId,
+ postId: id,
+ })
+ const sendEmail = user.emailsDisabled
+ ? null
+ : await sgMail.send({
+ to: user.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${user.name}, ${post.Creator.name} just mentioned you in a ${post.type} on weco:
http://${appURL}/p/${id}
`,
- html: `
+ html: `
Hi ${user.name},
@@ -1894,18 +1907,23 @@ router.post('/update-post', authenticateToken, async (req, res) => {
on weco
`,
- })
- Promise.all([sendNotification, sendEmail])
- .then(() => resolve())
- .catch((error) => resolve(error))
- }
- })
+ })
+ Promise.all([sendNotification, sendEmail])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ }
+ })
+ )
)
- )
+ promises.push(notifyMentions)
+ }
- Promise.all([updatePost, removeOldUrls, addNewUrls, notifyMentions])
+ Promise.all(promises)
.then(() => res.status(200).json(updatePost))
- .catch((error) => res.status(500).json({ message: 'Error', error }))
+ .catch((error) => {
+ console.error(error)
+ res.status(500).json({ message: 'Error', error })
+ })
}
})
@@ -1940,28 +1958,28 @@ router.post('/repost-post', authenticateToken, async (req, res) => {
const sendNotification = skipNotification
? null
: await Notification.create({
- ownerId: post.Creator.id,
- type: 'post-repost',
- seen: false,
- spaceAId: spaceId,
- userId: accountId,
- postId,
- })
+ ownerId: post.Creator.id,
+ type: 'post-repost',
+ seen: false,
+ spaceAId: spaceId,
+ userId: accountId,
+ postId,
+ })
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: post.Creator.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ to: post.Creator.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${post.Creator.name}, ${accountName} just reposted your post on weco:
http://${appURL}/p/${postId}
`,
- html: `
+ html: `
Hi ${post.Creator.name},
@@ -1971,7 +1989,7 @@ router.post('/repost-post', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
const createReactions = await Promise.all(
spaceIds.map((id) =>
@@ -2017,14 +2035,14 @@ router.post('/repost-post', authenticateToken, async (req, res) => {
})
const updateSpaceUserStat = spaceUserStat
? await spaceUserStat.increment('totalPostLikes', {
- by: post.totalLikes,
- })
+ by: post.totalLikes,
+ })
: await SpaceUserStat.create({
- spaceId: id,
- userId: post.Creator.id,
- totalPostLikes: post.totalLikes,
- totalUnseenMessages: 0,
- })
+ spaceId: id,
+ userId: post.Creator.id,
+ totalPostLikes: post.totalLikes,
+ totalUnseenMessages: 0,
+ })
Promise.all([
createSpacePost,
incrementTotalPostLikes,
@@ -2094,14 +2112,14 @@ router.post('/repost-post', authenticateToken, async (req, res) => {
})
const updateSpaceUserStat = spaceUserStat
? await spaceUserStat.increment('totalPostLikes', {
- by: post.totalLikes,
- })
+ by: post.totalLikes,
+ })
: await SpaceUserStat.create({
- spaceId: id,
- userId: post.Creator.id,
- totalPostLikes: post.totalLikes,
- totalUnseenMessages: 0,
- })
+ spaceId: id,
+ userId: post.Creator.id,
+ totalPostLikes: post.totalLikes,
+ totalUnseenMessages: 0,
+ })
Promise.all([
createSpacePost,
updateSpaceStats,
@@ -2165,30 +2183,30 @@ router.post('/add-like', authenticateToken, async (req, res) => {
const updateSpaceStats =
type !== 'link'
? Promise.all(
- item.AllPostSpaces.map(
- (space) =>
- new Promise(async (resolve) => {
- const updateSpaceStat = await space.increment('totalPostLikes', {
- silent: true,
- })
- const spaceUserStat = await SpaceUserStat.findOne({
- where: { spaceId: space.id, userId: item.Creator.id },
- attributes: ['id'],
- })
- const updateSpaceUserStat = spaceUserStat
- ? await spaceUserStat.increment('totalPostLikes')
- : await SpaceUserStat.create({
- spaceId: space.id,
- userId: item.Creator.id,
- totalPostLikes: 1,
- totalUnseenMessages: 0,
- })
- Promise.all([updateSpaceStat, updateSpaceUserStat])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
- )
- )
+ item.AllPostSpaces.map(
+ (space) =>
+ new Promise(async (resolve) => {
+ const updateSpaceStat = await space.increment('totalPostLikes', {
+ silent: true,
+ })
+ const spaceUserStat = await SpaceUserStat.findOne({
+ where: { spaceId: space.id, userId: item.Creator.id },
+ attributes: ['id'],
+ })
+ const updateSpaceUserStat = spaceUserStat
+ ? await spaceUserStat.increment('totalPostLikes')
+ : await SpaceUserStat.create({
+ spaceId: space.id,
+ userId: item.Creator.id,
+ totalPostLikes: 1,
+ totalUnseenMessages: 0,
+ })
+ Promise.all([updateSpaceStat, updateSpaceUserStat])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
+ )
+ )
: null
const createReaction = await Reaction.create({
@@ -2220,14 +2238,14 @@ router.post('/add-like', authenticateToken, async (req, res) => {
const createNotification = skipNotification
? null
: await Notification.create({
- ownerId: item.Creator.id,
- type: `${type}-like`,
- seen: false,
- userId: accountId,
- spaceAId,
- postId,
- commentId,
- })
+ ownerId: item.Creator.id,
+ type: `${type}-like`,
+ seen: false,
+ userId: accountId,
+ spaceAId,
+ postId,
+ commentId,
+ })
const { handle, name, flagImagePath } = await User.findOne({
where: { id: accountId },
@@ -2249,14 +2267,14 @@ router.post('/add-like', authenticateToken, async (req, res) => {
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: item.Creator.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: item.Creator.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${item.Creator.name}, ${name} just liked your ${type} on weco:
http://${itemUrl}
`,
- html: `
+ html: `
Hi ${item.Creator.name},
@@ -2266,7 +2284,7 @@ router.post('/add-like', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
Promise.all([
updateTotalLikes,
@@ -2305,22 +2323,22 @@ router.post('/remove-like', authenticateToken, async (req, res) => {
const updateSpaceStats =
type === 'post'
? Promise.all(
- item.AllPostSpaces.map(
- (space) =>
- new Promise(async (resolve) => {
- const updateSpaceStat = await space.decrement('totalPostLikes', {
- silent: true,
- })
- const updateSpaceUserStat = await SpaceUserStat.decrement(
- 'totalPostLikes',
- { where: { spaceId: space.id, userId: item.Creator.id } }
- )
- Promise.all([updateSpaceStat, updateSpaceUserStat])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
- )
- )
+ item.AllPostSpaces.map(
+ (space) =>
+ new Promise(async (resolve) => {
+ const updateSpaceStat = await space.decrement('totalPostLikes', {
+ silent: true,
+ })
+ const updateSpaceUserStat = await SpaceUserStat.decrement(
+ 'totalPostLikes',
+ { where: { spaceId: space.id, userId: item.Creator.id } }
+ )
+ Promise.all([updateSpaceStat, updateSpaceUserStat])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
+ )
+ )
: null
const removeReaction = await Reaction.update(
@@ -2384,27 +2402,27 @@ router.post('/add-rating', authenticateToken, async (req, res) => {
const sendNotification = skipNotification
? null
: await Notification.create({
- ownerId: item.Creator.id,
- type: `${type}-rating`,
- seen: false,
- spaceAId: spaceId,
- userId: accountId,
- postId,
- commentId,
- })
+ ownerId: item.Creator.id,
+ type: `${type}-rating`,
+ seen: false,
+ spaceAId: spaceId,
+ userId: accountId,
+ postId,
+ commentId,
+ })
const itemUrl = `${appURL}/p/${id}`
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: item.Creator.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: item.Creator.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${item.Creator.name}, ${accountName} just rated your ${type} on weco:
http://${itemUrl}
`,
- html: `
+ html: `
Hi ${item.Creator.name},
@@ -2414,7 +2432,7 @@ router.post('/add-rating', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
Promise.all([updateTotalRatings, createReaction, sendNotification, sendEmail])
.then(() => res.status(200).json({ message: 'Success' }))
@@ -2572,33 +2590,29 @@ router.post('/add-link', authenticateToken, async (req, res) => {
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
- Hi ${name}, ${accountName} just linked ${
- type === 'user' ? 'you' : `your ${type}`
- } to another ${
- location === 'source' ? sourceType : targetType
- } on weco:
+ to: email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
+ Hi ${name}, ${accountName} just linked ${type === 'user' ? 'you' : `your ${type}`
+ } to another ${location === 'source' ? sourceType : targetType
+ } on weco:
http://${url}
`,
- html: `
+ html: `
Hi ${name},
${accountName}
- just linked ${
- type === 'user'
- ? `you`
- : `your ${type}`
+ just linked ${type === 'user'
+ ? `you`
+ : `your ${type}`
}
- to another ${
- location === 'source' ? sourceType : targetType
+ to another ${location === 'source' ? sourceType : targetType
} on weco
`,
- })
+ })
Promise.all([createNotification, sendEmail])
.then(() => resolve())
.catch((error) => resolve(error))
@@ -2672,34 +2686,34 @@ router.post('/respond-to-event', authenticateToken, async (req, res) => {
const updateStatus = previousResponse
? UserEvent.update({ state: 'removed' }, { where: { id: previousResponse.id } })
: new Promise(async (resolve) => {
- const removeOtherResponseTypes = await UserEvent.update(
- { state: 'removed' },
- { where: { userId: accountId, eventId, state: 'active' } }
- )
-
- const newResponse = await UserEvent.create({
- userId: accountId,
- eventId,
- relationship: response,
- state: 'active',
- })
-
- const scheduleReminder = await scheduleEventNotification({
- type: response,
- postId,
- eventId,
- userEventId: newResponse.id,
- startTime,
- userId: accountId,
- userName: user.name,
- userEmail: user.email,
- emailsDisabled: user.emailsDisabled,
- })
-
- Promise.all([removeOtherResponseTypes, scheduleReminder])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const removeOtherResponseTypes = await UserEvent.update(
+ { state: 'removed' },
+ { where: { userId: accountId, eventId, state: 'active' } }
+ )
+
+ const newResponse = await UserEvent.create({
+ userId: accountId,
+ eventId,
+ relationship: response,
+ state: 'active',
+ })
+
+ const scheduleReminder = await scheduleEventNotification({
+ type: response,
+ postId,
+ eventId,
+ userEventId: newResponse.id,
+ startTime,
+ userId: accountId,
+ userName: user.name,
+ userEmail: user.email,
+ emailsDisabled: user.emailsDisabled,
+ })
+
+ Promise.all([removeOtherResponseTypes, scheduleReminder])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
updateStatus
.then(() => res.status(200).json({ message: 'Success' }))
@@ -2752,89 +2766,89 @@ router.post('/vote-on-poll', authenticateToken, async (req, res) => {
const { type, action, threshold } = post.Poll
const executeAction = action
? Promise.all(
- voteData.map(
- (answer) =>
- new Promise(async (resolve1) => {
- // find poll answer
- const pollAnswer = await Post.findOne({
- where: { id: answer.id },
- attributes: ['id', 'text'],
- include: {
- model: Reaction,
- where: { type: 'vote', state: 'active' },
- required: false,
- attributes: ['value'],
- },
- })
- const answerLink = await Link.findOne({
- where: {
- itemAId: postId,
- itemAType: 'post',
- itemBId: answer.id,
- itemBType: 'poll-answer',
- },
- attributes: ['id', 'state'],
- })
- const { text, Reactions } = pollAnswer
- let totalVotes
- if (type === 'weighted-choice')
- totalVotes =
- Reactions.map((r) => +r.value).reduce((a, b) => a + b, 0) /
- 100
- else totalVotes = Reactions.length
- const createSpace =
- action === 'Create spaces' &&
- answerLink.state !== 'done' &&
- totalVotes >= threshold
- ? new Promise(async (resolve2) => {
- const markAnswerDone = await answerLink.update({
- state: 'done',
- })
- const newSpace = await Space.create({
- creatorId: post.Creator.id,
- handle: uuidv4().substring(0, 15),
- name: text,
- description: null,
- state: 'active',
- privacy: 'public',
- totalPostLikes: 0,
- totalPosts: 0,
- totalComments: 0,
- totalFollowers: 1,
- })
- const createModRelationship = SpaceUser.create({
- relationship: 'moderator',
- state: 'active',
- spaceId: newSpace.id,
- userId: post.Creator.id,
- })
- const createFollowerRelationship = SpaceUser.create({
- relationship: 'follower',
- state: 'active',
- spaceId: newSpace.id,
- userId: post.Creator.id,
- })
- const attachToParent = await attachParentSpace(
- newSpace.id,
- post.Poll.spaceId
- )
- Promise.all([
- markAnswerDone,
- createModRelationship,
- createFollowerRelationship,
- attachToParent,
- ])
- .then(() => resolve2())
- .catch((error) => resolve2(error))
+ voteData.map(
+ (answer) =>
+ new Promise(async (resolve1) => {
+ // find poll answer
+ const pollAnswer = await Post.findOne({
+ where: { id: answer.id },
+ attributes: ['id', 'text'],
+ include: {
+ model: Reaction,
+ where: { type: 'vote', state: 'active' },
+ required: false,
+ attributes: ['value'],
+ },
+ })
+ const answerLink = await Link.findOne({
+ where: {
+ itemAId: postId,
+ itemAType: 'post',
+ itemBId: answer.id,
+ itemBType: 'poll-answer',
+ },
+ attributes: ['id', 'state'],
+ })
+ const { text, Reactions } = pollAnswer
+ let totalVotes
+ if (type === 'weighted-choice')
+ totalVotes =
+ Reactions.map((r) => +r.value).reduce((a, b) => a + b, 0) /
+ 100
+ else totalVotes = Reactions.length
+ const createSpace =
+ action === 'Create spaces' &&
+ answerLink.state !== 'done' &&
+ totalVotes >= threshold
+ ? new Promise(async (resolve2) => {
+ const markAnswerDone = await answerLink.update({
+ state: 'done',
})
- : null
-
- Promise.all([createSpace])
- .then(() => resolve1())
- .catch((error) => resolve1(error))
- })
- )
- )
+ const newSpace = await Space.create({
+ creatorId: post.Creator.id,
+ handle: uuidv4().substring(0, 15),
+ name: text,
+ description: null,
+ state: 'active',
+ privacy: 'public',
+ totalPostLikes: 0,
+ totalPosts: 0,
+ totalComments: 0,
+ totalFollowers: 1,
+ })
+ const createModRelationship = SpaceUser.create({
+ relationship: 'moderator',
+ state: 'active',
+ spaceId: newSpace.id,
+ userId: post.Creator.id,
+ })
+ const createFollowerRelationship = SpaceUser.create({
+ relationship: 'follower',
+ state: 'active',
+ spaceId: newSpace.id,
+ userId: post.Creator.id,
+ })
+ const attachToParent = await attachParentSpace(
+ newSpace.id,
+ post.Poll.spaceId
+ )
+ Promise.all([
+ markAnswerDone,
+ createModRelationship,
+ createFollowerRelationship,
+ attachToParent,
+ ])
+ .then(() => resolve2())
+ .catch((error) => resolve2(error))
+ })
+ : null
+
+ Promise.all([createSpace])
+ .then(() => resolve1())
+ .catch((error) => resolve1(error))
+ })
+ )
+ )
: null
const skipNotification = post.Creator.id === accountId
@@ -2843,27 +2857,27 @@ router.post('/vote-on-poll', authenticateToken, async (req, res) => {
const createNotification = skipNotification
? null
: await Notification.create({
- ownerId: post.Creator.id,
- type: 'poll-vote',
- seen: false,
- userId: accountId,
- postId,
- })
+ ownerId: post.Creator.id,
+ type: 'poll-vote',
+ seen: false,
+ userId: accountId,
+ postId,
+ })
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: post.Creator.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ to: post.Creator.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${post.Creator.name}, ${userName} just voted on your Poll:
http://${appURL}/p/${postId}
`,
- html: `
+ html: `
Hi ${post.Creator.name},
@@ -2872,7 +2886,7 @@ router.post('/vote-on-poll', authenticateToken, async (req, res) => {
Poll
`,
- })
+ })
const updateLastPostActivity = await Post.update(
{ lastActivity: new Date() },
@@ -3062,8 +3076,8 @@ router.post('/delete-post', authenticateToken, async (req, res) => {
})
const updateSpaceUserStat = spaceUserStat
? await spaceUserStat.update({
- totalPostLikes: spaceUserStat.totalPostLikes - post.totalLikes,
- })
+ totalPostLikes: spaceUserStat.totalPostLikes - post.totalLikes,
+ })
: null
Promise.all([updateSpace, updateSpaceUserStat])
.then(() => resolve())
From 1042920bde597b746ab9fb79a061f6c7df721000 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Tue, 20 Feb 2024 14:29:21 +0100
Subject: [PATCH 02/21] fix
---
routes/Auth.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/routes/Auth.js b/routes/Auth.js
index dece6b5..0abd291 100644
--- a/routes/Auth.js
+++ b/routes/Auth.js
@@ -12,7 +12,7 @@ const jwt = require('jsonwebtoken')
const sgMail = require('@sendgrid/mail')
const webpush = require('web-push')
// webpush.setGCMAPIKey('')
-// webpush.setVapidDetails('https://weco.io', vapidPublicKey, vapidPrivateKey)
+webpush.setVapidDetails('https://weco.io', vapidPublicKey, vapidPrivateKey)
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
const authenticateToken = require('../middleware/authenticateToken')
From 410a057f4986f7e0964a47d4fd268cdbb847d9b2 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Thu, 29 Feb 2024 16:49:16 +0100
Subject: [PATCH 03/21] add play too
---
Helpers.js | 340 +++++++++++++------------
migration-updates/add-play-to-posts.js | 24 ++
models/Post.js | 1 +
routes/Post.js | 10 +-
4 files changed, 202 insertions(+), 173 deletions(-)
create mode 100644 migration-updates/add-play-to-posts.js
diff --git a/Helpers.js b/Helpers.js
index 58cac79..3c79644 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -301,14 +301,14 @@ function notifyMention(creator, user, postId, type) {
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: user.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: user.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${user.name}, ${creator.name} just mentioned you in a ${type} on weco:
http://${appURL}/p/${postId}
`,
- html: `
+ html: `
Hi ${user.name},
@@ -318,7 +318,7 @@ function notifyMention(creator, user, postId, type) {
on weco
`,
- })
+ })
Promise.all([sendNotification, sendEmail])
.then(() => resolve())
@@ -862,7 +862,7 @@ function totalSpaceResults(filters) {
const endDate = createSQLDate(new Date())
return depth === 'Deep'
? [
- literal(`(
+ literal(`(
SELECT COUNT(*)
FROM Spaces s
WHERE s.id != Space.id
@@ -880,10 +880,10 @@ function totalSpaceResults(filters) {
OR s.description LIKE '%${search}%'
) AND s.createdAt BETWEEN '${startDate}' AND '${endDate}'
)`),
- 'totalResults',
- ]
+ 'totalResults',
+ ]
: [
- literal(`(
+ literal(`(
SELECT COUNT(*)
FROM Spaces s
WHERE s.state = 'active'
@@ -899,8 +899,8 @@ function totalSpaceResults(filters) {
OR s.description LIKE '%${search}%'
) AND s.createdAt BETWEEN '${startDate}' AND '${endDate}'
)`),
- 'totalResults',
- ]
+ 'totalResults',
+ ]
}
}
@@ -965,7 +965,8 @@ const fullPostAttributes = [
'totalReposts',
'totalRatings',
'totalLinks',
- 'game'
+ 'game',
+ 'play'
]
// todo: replace all use cases with const fullPostAttributes above
@@ -986,6 +987,7 @@ function findFullPostAttributes(model, accountId) {
'totalRatings',
'totalLinks',
'game',
+ 'play',
// accountLike('post', model, accountId),
// accountComment('post', model, accountId),
// accountLink('post', model, accountId),
@@ -1607,14 +1609,14 @@ function sendGBGInvite(player, postId, creator, settings) {
const sendEmail = player.emailsDisabled
? null
: await sgMail.send({
- to: player.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: player.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${player.name}, ${creator.name} just invited you to join a game on weco: https://${appURL}/p/${postId}
Log in and go to your notifications to accept or reject the invitation.
`,
- html: `
+ html: `
Hi ${player.name},
@@ -1633,16 +1635,16 @@ function sendGBGInvite(player, postId, creator, settings) {
Allowed bead types: ${allowedBeadTypes.join(',')}
Time window for moves: ${moveTimeWindow ? `${moveTimeWindow} minutes` : 'Off'
- }
+ }
Character limit: ${characterLimit ? `${characterLimit} characters` : 'Off'
- }
+ }
Audio time limit: ${moveDuration ? `${moveDuration} seconds` : 'Off'}
`,
- })
+ })
Promise.all([createNotification, sendEmail])
.then(() => resolve())
@@ -1701,6 +1703,7 @@ function createPost(data, files, accountId) {
poll,
glassBeadGame,
game,
+ play,
card,
color,
watermark,
@@ -1723,19 +1726,20 @@ function createPost(data, files, accountId) {
watermark: !!watermark,
lastActivity: new Date(),
game,
+ play,
})
// todo: add the correct notification type
- const notifyMentions = mentions.length
+ const notifyMentions = mentions?.length
? await new Promise(async (resolve) => {
- const users = await User.findAll({
- where: { id: mentions, state: 'active' },
- attributes: ['id', 'name', 'email', 'emailsDisabled'],
- })
- Promise.all(users.map((user) => notifyMention(creator, user, post.id, type)))
- .then(() => resolve())
- .catch((error) => resolve(data, error))
- })
+ const users = await User.findAll({
+ where: { id: mentions, state: 'active' },
+ attributes: ['id', 'name', 'email', 'emailsDisabled'],
+ })
+ Promise.all(users.map((user) => notifyMention(creator, user, post.id, type)))
+ .then(() => resolve())
+ .catch((error) => resolve(data, error))
+ })
: null
const createUrls = urls
@@ -1744,158 +1748,158 @@ function createPost(data, files, accountId) {
const createImages = images
? await Promise.all(
- images.map((image, i) => createImage(accountId, post.id, type, image, i, files))
- )
+ images.map((image, i) => createImage(accountId, post.id, type, image, i, files))
+ )
: null
const createAudios = audios
? await Promise.all(
- audios.map((audio, i) => createAudio(accountId, post.id, type, audio, i, files))
- )
+ audios.map((audio, i) => createAudio(accountId, post.id, type, audio, i, files))
+ )
: null
const createEvent = event
? await Event.create({
- postId: post.id,
- state: 'active',
- startTime: event.startTime,
- endTime: event.endTime,
- })
+ postId: post.id,
+ state: 'active',
+ startTime: event.startTime,
+ endTime: event.endTime,
+ })
: null
const createPoll = poll
? await new Promise(async (resolve) => {
- const { type, answers, locked, governance, action, threshold } = poll
- const createPoll = await Poll.create({
- postId: post.id,
- type,
- answersLocked: locked,
- spaceId: governance ? spaceIds[0] : null,
- action: action || null,
- threshold: threshold || null,
- })
- const creatAnswers = await Promise.all(
- answers.map((a) => createPollAnswer(a, accountId, post.id, files))
- )
- Promise.all([createPoll, creatAnswers])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const { type, answers, locked, governance, action, threshold } = poll
+ const createPoll = await Poll.create({
+ postId: post.id,
+ type,
+ answersLocked: locked,
+ spaceId: governance ? spaceIds[0] : null,
+ action: action || null,
+ threshold: threshold || null,
+ })
+ const creatAnswers = await Promise.all(
+ answers.map((a) => createPollAnswer(a, accountId, post.id, files))
+ )
+ Promise.all([createPoll, creatAnswers])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
const createGBG = glassBeadGame
? await new Promise(async (resolve) => {
- const { settings, topicImage, topicGroup, beads, sourcePostId } = glassBeadGame
- const imageFile = files.find((file) => file.originalname === topicImage.id)
- const { players } = settings
- const createGame = await GlassBeadGame.create({
- postId: post.id,
- state: 'active',
- locked: false,
- topicGroup,
- topicImage: imageFile ? imageFile.url : topicImage.Image.url || null,
- synchronous: settings.synchronous,
- multiplayer: settings.multiplayer,
- allowedBeadTypes: settings.allowedBeadTypes.join(',').toLowerCase(),
- playerOrder: players.length ? players.map((p) => p.id).join(',') : null,
- totalMoves: settings.totalMoves || null,
- movesPerPlayer: settings.movesPerPlayer || null,
- moveDuration: settings.moveDuration || null,
- moveTimeWindow: settings.moveTimeWindow || null,
- characterLimit: settings.characterLimit || null,
- introDuration: settings.introDuration || null,
- outroDuration: settings.outroDuration || null,
- intervalDuration: settings.intervalDuration || null,
- nextMoveDeadline: settings.nextMoveDeadline || null,
- totalBeads: beads.length + (sourcePostId ? 1 : 0),
- })
-
- // const linkSourceBead = sourcePostId
- // ? await Link.create({
- // state: 'active',
- // // type: 'gbg-post',
- // index: 0,
- // relationship: 'source',
- // creatorId: accountId,
- // itemAId: post.id,
- // itemBId: sourcePostId,
- // totalLikes: 0,
- // totalComments: 0,
- // totalRatings: 0,
- // })
- // : null
-
- // const notifySourceCreator =
- // sourcePostId && sourceCreatorId !== accountId
- // ? await new Promise(async (Resolve) => {
- // const sourceCreator = await User.findOne({
- // where: { id: sourceCreatorId },
- // attributes: ['name', 'email', 'emailsDisabled'],
- // })
- // const notifyCreator = await Notification.create({
- // type: 'new-gbg-from-your-post',
- // ownerId: sourceCreatorId,
- // userId: accountId,
- // postId: post.id,
- // seen: false,
- // })
- // const skipEmail =
- // sourceCreator.emailsDisabled ||
- // (await accountMuted(accountId, sourceCreator))
- // const emailCreator = skipEmail
- // ? null
- // : await sgMail.send({
- // to: sourceCreator.email,
- // from: {
- // email: 'admin@weco.io',
- // name: 'we { collective }',
- // },
- // subject: 'New notification',
- // text: `
- // Hi ${sourceCreator.name}, ${creatorName} just created a new glass bead game from your post on weco: https://${appURL}/p/${post.id}
- // `,
- // html: `
- //
- // Hi ${sourceCreator.name},
- //
- // ${creatorName}
- // just created a new glass bead game from your post on weco.
- //
- // `,
- // })
- // Promise.all([notifyCreator, emailCreator])
- // .then(() => Resolve())
- // .catch((error) => Resolve(error))
- // })
- // : null
-
- const createBeads = await Promise.all(
- beads.map((bead, index) => createBead(bead, index, accountId, post.id, files))
- )
-
- const addPlayers =
- settings.multiplayer && !!players.length
- ? await addGBGPlayers(post.id, creator, settings)
- : null
-
- Promise.all([
- createGame,
- // linkSourceBead,
- // notifySourceCreator,
- createBeads,
- addPlayers,
- ])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const { settings, topicImage, topicGroup, beads, sourcePostId } = glassBeadGame
+ const imageFile = files.find((file) => file.originalname === topicImage.id)
+ const { players } = settings
+ const createGame = await GlassBeadGame.create({
+ postId: post.id,
+ state: 'active',
+ locked: false,
+ topicGroup,
+ topicImage: imageFile ? imageFile.url : topicImage.Image.url || null,
+ synchronous: settings.synchronous,
+ multiplayer: settings.multiplayer,
+ allowedBeadTypes: settings.allowedBeadTypes.join(',').toLowerCase(),
+ playerOrder: players.length ? players.map((p) => p.id).join(',') : null,
+ totalMoves: settings.totalMoves || null,
+ movesPerPlayer: settings.movesPerPlayer || null,
+ moveDuration: settings.moveDuration || null,
+ moveTimeWindow: settings.moveTimeWindow || null,
+ characterLimit: settings.characterLimit || null,
+ introDuration: settings.introDuration || null,
+ outroDuration: settings.outroDuration || null,
+ intervalDuration: settings.intervalDuration || null,
+ nextMoveDeadline: settings.nextMoveDeadline || null,
+ totalBeads: beads.length + (sourcePostId ? 1 : 0),
+ })
+
+ // const linkSourceBead = sourcePostId
+ // ? await Link.create({
+ // state: 'active',
+ // // type: 'gbg-post',
+ // index: 0,
+ // relationship: 'source',
+ // creatorId: accountId,
+ // itemAId: post.id,
+ // itemBId: sourcePostId,
+ // totalLikes: 0,
+ // totalComments: 0,
+ // totalRatings: 0,
+ // })
+ // : null
+
+ // const notifySourceCreator =
+ // sourcePostId && sourceCreatorId !== accountId
+ // ? await new Promise(async (Resolve) => {
+ // const sourceCreator = await User.findOne({
+ // where: { id: sourceCreatorId },
+ // attributes: ['name', 'email', 'emailsDisabled'],
+ // })
+ // const notifyCreator = await Notification.create({
+ // type: 'new-gbg-from-your-post',
+ // ownerId: sourceCreatorId,
+ // userId: accountId,
+ // postId: post.id,
+ // seen: false,
+ // })
+ // const skipEmail =
+ // sourceCreator.emailsDisabled ||
+ // (await accountMuted(accountId, sourceCreator))
+ // const emailCreator = skipEmail
+ // ? null
+ // : await sgMail.send({
+ // to: sourceCreator.email,
+ // from: {
+ // email: 'admin@weco.io',
+ // name: 'we { collective }',
+ // },
+ // subject: 'New notification',
+ // text: `
+ // Hi ${sourceCreator.name}, ${creatorName} just created a new glass bead game from your post on weco: https://${appURL}/p/${post.id}
+ // `,
+ // html: `
+ //
+ // Hi ${sourceCreator.name},
+ //
+ // ${creatorName}
+ // just created a new glass bead game from your post on weco.
+ //
+ // `,
+ // })
+ // Promise.all([notifyCreator, emailCreator])
+ // .then(() => Resolve())
+ // .catch((error) => Resolve(error))
+ // })
+ // : null
+
+ const createBeads = await Promise.all(
+ beads.map((bead, index) => createBead(bead, index, accountId, post.id, files))
+ )
+
+ const addPlayers =
+ settings.multiplayer && !!players.length
+ ? await addGBGPlayers(post.id, creator, settings)
+ : null
+
+ Promise.all([
+ createGame,
+ // linkSourceBead,
+ // notifySourceCreator,
+ createBeads,
+ addPlayers,
+ ])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
const createCard = card
? await Promise.all(
- [card.front, card.back].map((cardFace, index) =>
- createCardFace(cardFace, index, accountId, post.id, files)
- )
- )
+ [card.front, card.back].map((cardFace, index) =>
+ createCardFace(cardFace, index, accountId, post.id, files)
+ )
+ )
: null
Promise.all([
@@ -2051,14 +2055,14 @@ function scheduleNextBeadDeadline(postId, settings, players) {
const sendMoveEmail = nextPlayer.emailsDisabled
? null
: await sgMail.send({
- to: nextPlayer.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: nextPlayer.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${nextPlayer.name}, it's your move!
Add a new bead to the glass bead game: https://${appURL}/p/${postId}
`,
- html: `
+ html: `
Hi ${nextPlayer.name},
@@ -2067,7 +2071,7 @@ function scheduleNextBeadDeadline(postId, settings, players) {
Add a new bead to the glass bead game.
`,
- })
+ })
const scheduleReminders = await scheduleGBGMoveJobs(
postId,
nextPlayer,
diff --git a/migration-updates/add-play-to-posts.js b/migration-updates/add-play-to-posts.js
new file mode 100644
index 0000000..e07a28a
--- /dev/null
+++ b/migration-updates/add-play-to-posts.js
@@ -0,0 +1,24 @@
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) => {
+ return Promise.all([
+ queryInterface.addColumn(
+ 'Posts',
+ 'play',
+ {
+ type: Sequelize.DataTypes.JSON,
+ },
+ { transaction: t }
+ ),
+ ])
+ })
+ },
+
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) => {
+ return Promise.all([
+ queryInterface.removeColumn('Posts', 'play', { transaction: t }),
+ ])
+ })
+ },
+}
diff --git a/models/Post.js b/models/Post.js
index cce0993..425c608 100644
--- a/models/Post.js
+++ b/models/Post.js
@@ -26,6 +26,7 @@ module.exports = (sequelize, DataTypes) => {
totalRatings: DataTypes.INTEGER,
totalGlassBeadGames: DataTypes.INTEGER,
game: DataTypes.JSON,
+ play: DataTypes.JSON,
lastActivity: DataTypes.DATE,
},
{}
diff --git a/routes/Post.js b/routes/Post.js
index 514628f..c30b155 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -685,7 +685,7 @@ router.get('/post-comments', async (req, res) => {
// failed approaches:
// + full nested include with no recursive promises (doesn't allow limit beyond first generation)
// + get links first instead of using getBlocks function ~1.5s
- const { postId, offset, filter } = req.query
+ const { postId, offset, filter, limit } = req.query
const limits = [5, 4, 3, 2, 1] // number of comments to inlcude per generation (length of array determines max depth)
const post = await Post.findOne({
where: { id: postId },
@@ -708,7 +708,7 @@ router.get('/post-comments', async (req, res) => {
['id', 'ASC'],
]
- async function getChildComments(parent, depth) {
+ async function getChildComments(parent, depth, limit) {
return new Promise(async (resolve) => {
const comments = await parent.getBlocks({
attributes: [...fullPostAttributes, 'totalChildComments'],
@@ -727,7 +727,7 @@ router.get('/post-comments', async (req, res) => {
attributes: ['id', 'handle', 'name', 'flagImagePath'],
},
],
- limit: limits[depth],
+ limit: limit || limits[depth],
offset: depth ? 0 : +offset,
order,
})
@@ -748,7 +748,7 @@ router.get('/post-comments', async (req, res) => {
})
}
- getChildComments(post, 0)
+ getChildComments(post, 0, +limit)
.then(() =>
res.status(200).json({
totalChildren: post.totalChildComments,
@@ -1734,7 +1734,7 @@ router.post('/update-post', authenticateToken, async (req, res) => {
if (!post) res.status(401).json({ message: 'Unauthorized' })
else {
const toUpdate = {};
- for (const key of ['mediaTypes', 'title', 'text', 'searchableText', 'game']) {
+ for (const key of ['mediaTypes', 'title', 'text', 'searchableText', 'game', 'play']) {
if (key in req.body) {
toUpdate[key] = req.body[key]
}
From 0387de490673cdc7ac4a2a8e95e2802c7c363d65 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Tue, 5 Mar 2024 12:42:33 +0100
Subject: [PATCH 04/21] links
---
Helpers.js | 11 +++++++++++
models/Post.js | 1 +
routes/Post.js | 21 ++++++++++++++++++++-
3 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/Helpers.js b/Helpers.js
index 3c79644..e285e54 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -1146,6 +1146,17 @@ function findPostInclude(accountId) {
},
},
},
+ {
+ model: Link,
+ as: 'Plays',
+ separate: true,
+ where: { relationship: 'play', state: 'active' },
+ order: [['index', 'ASC']],
+ include: {
+ model: Post,
+ attributes: ['id', 'title', 'play', 'state']
+ }
+ },
{
model: Reaction,
where: { creatorId: accountId, state: 'active' },
diff --git a/models/Post.js b/models/Post.js
index 425c608..8c37f02 100644
--- a/models/Post.js
+++ b/models/Post.js
@@ -50,6 +50,7 @@ module.exports = (sequelize, DataTypes) => {
Post.hasMany(models.Link, { as: 'UrlBlocks', foreignKey: 'itemAId' })
Post.hasMany(models.Link, { as: 'ImageBlocks', foreignKey: 'itemAId' })
Post.hasMany(models.Link, { as: 'AudioBlocks', foreignKey: 'itemAId' })
+ Post.hasMany(models.Link, { as: 'Plays', foreignKey: 'itemAId' })
Post.hasOne(models.Link, { as: 'MediaLink', foreignKey: 'itemAId' })
// used for post map (todo: rethink...)
Post.hasMany(models.Link, { as: 'OutgoingPostLinks', foreignKey: 'itemAId' })
diff --git a/routes/Post.js b/routes/Post.js
index c30b155..f74d981 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -1350,7 +1350,7 @@ router.post('/create-post', authenticateToken, async (req, res) => {
const createNewLink = await Link.create({
state: 'active',
creatorId: accountId,
- relationship: 'link',
+ relationship: source.relationship ?? 'link',
itemAType: source.type,
itemBType: 'post',
itemAId: source.id,
@@ -3058,6 +3058,25 @@ router.post('/delete-post', authenticateToken, async (req, res) => {
{ where: { id: postId, creatorId: accountId } }
)
+ await Link.update(
+ { state: 'deleted', },
+ {
+ where: {
+ state: 'active',
+ [Op.or]: [
+ {
+ itemAType: 'post',
+ itemAId: postId
+ },
+ {
+ itemBType: 'post',
+ itemBId: postId
+ }
+ ]
+ }
+ }
+ );
+
const updateSpaceStats = await Promise.all(
post.AllPostSpaces.map(
(space) =>
From 0df50b74af1dcad4f6dc5b0815e083d6f4397f2c Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Tue, 5 Mar 2024 16:32:07 +0100
Subject: [PATCH 05/21] play service
---
Play.js | 358 ++++++++++++++++++++++++++++++++++++++++++++++
ScheduledTasks.js | 37 +++--
Socket.js | 3 +
3 files changed, 379 insertions(+), 19 deletions(-)
create mode 100644 Play.js
diff --git a/Play.js b/Play.js
new file mode 100644
index 0000000..6361c4b
--- /dev/null
+++ b/Play.js
@@ -0,0 +1,358 @@
+const sequelize = require('sequelize')
+const { Op } = sequelize
+const { User, Event, UserEvent, Notification, Post, Weave, GlassBeadGame } = require('./models')
+const schedule = require('node-schedule')
+
+function getFirstLeafStep(
+ step,
+ variables,
+ playerIds
+) {
+ if (!step) {
+ return undefined
+ }
+ switch (step.type) {
+ case 'game':
+ throw new Error('TODO')
+ case 'post':
+ return { step, variables }
+ case 'rounds': {
+ const firstStep = getFirstLeafStep(step.steps[0], variables, playerIds)
+ if (!firstStep) {
+ return undefined
+ }
+ return {
+ step: firstStep.step,
+ variables: {
+ ...firstStep.variables,
+ [`${step.id}_round`]: firstStep.variables[`${step.id}_round`] ?? 1,
+ },
+ }
+ }
+ case 'turns': {
+ const firstStep = getFirstLeafStep(step.steps[0], variables, playerIds)
+ if (!firstStep) {
+ return undefined
+ }
+ return {
+ step: firstStep.step,
+ variables: {
+ ...firstStep.variables,
+ [`${step.id}_player`]: firstStep.variables[`${step.id}_player`] ?? playerIds[0],
+ },
+ }
+ }
+ default: {
+ const exhaustivenessCheck = step
+ throw exhaustivenessCheck
+ }
+ }
+}
+
+const getNextLeafStep = (
+ steps,
+ stepId,
+ variables,
+ playerIds
+) => {
+ let currentFound = false
+ let currentVariables = variables
+ for (let i = 0; i < steps.length; i++) {
+ const step = steps[i]
+
+ if (currentFound) {
+ const nextStep = getFirstLeafStep(step, currentVariables, playerIds)
+ if (nextStep) {
+ return nextStep
+ }
+ } else {
+ switch (step.type) {
+ case 'game':
+ throw new Error('TODO')
+ case 'post':
+ if (step.id === stepId) {
+ currentFound = true
+ }
+ break
+ case 'rounds': {
+ const result = getNextLeafStep(step.steps, stepId, currentVariables, playerIds)
+ if (!result) {
+ break
+ }
+
+ if (result.step) {
+ return result
+ }
+
+ const roundKey = `${step.id}_round`
+ const currentRound = currentVariables[roundKey]
+ if (currentRound < +step.amount) {
+ const firstStep = getFirstLeafStep(
+ step,
+ {
+ ...result.variables,
+ [roundKey]: currentRound + 1,
+ },
+ playerIds
+ )
+ if (firstStep) {
+ return firstStep
+ }
+ }
+
+ currentFound = true
+ currentVariables = omit(currentVariables, roundKey)
+ break
+ }
+ case 'turns': {
+ const result = getNextLeafStep(step.steps, stepId, currentVariables, playerIds)
+ if (!result) {
+ break
+ }
+
+ if (result.step) {
+ return result
+ }
+
+ const playerKey = `${step.id}_player`
+ const currentPlayerId = result.variables[playerKey]
+ const currentPlayerIndex = playerIds.indexOf(currentPlayerId)
+ if (currentPlayerIndex < playerIds.length - 1) {
+ const firstStep = getFirstLeafStep(
+ step,
+ {
+ ...result.variables,
+ [playerKey]: playerIds[currentPlayerIndex + 1],
+ },
+ playerIds
+ )
+ if (firstStep) {
+ return firstStep
+ }
+ }
+
+ currentFound = true
+ currentVariables = omit(currentVariables, playerKey)
+ break
+ }
+ default: {
+ const exhaustivenessCheck = step
+ throw exhaustivenessCheck
+ }
+ }
+ }
+ }
+
+ if (currentFound) {
+ return { variables: currentVariables }
+ }
+
+ return undefined
+}
+
+async function endStep(post) {
+ // TODO
+}
+
+function scheduleEndStep(post) {
+ schedule.scheduleJob(post.play.stepTimeout, async () => {
+ const currentPost = await Post.findOne({
+ where: {
+ state: 'active',
+ id: post.id
+ }
+ })
+ if (!deepEquals(currentPost?.play, post.play)) {
+ // The state of the play has changed, we assume a new scheduled job has been triggered.
+ return
+ }
+ endStep(currentPost)
+ })
+}
+
+async function initializePlayTasks() {
+ const plays = await Post.findAll({
+ where: {
+ state: 'active',
+ 'play': { [Op.not]: null }
+ }
+ })
+
+ for (const post of plays) {
+ const play = post.play
+ if (play.status !== 'started') {
+ continue
+ }
+
+ const stepTimeout = new Date(play.stepTimeout);
+ if (stepTimeout < new Date()) {
+ endStep(post)
+ } else {
+ scheduleEndStep(post)
+ }
+ }
+}
+
+const EVENTS = {
+ outgoing: {
+ updateGame: 'play:outgoing-update-game',
+ start: 'play:outgoing-start',
+ next: 'play:outgoing-next',
+ pause: 'play:outgoing-pause',
+ stop: 'play:outgoing-stop'
+ },
+ incoming: {
+ updated: 'play:incoming-updated'
+ }
+}
+
+function registerPlaySocketEvents(socket, io) {
+ socket.on(EVENTS.outgoing.updateGame, async ({ id, game }) => {
+ const post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+
+ const newPlay = {
+ ...post.play,
+ game
+ }
+
+ await Post.update({
+ play: {
+ ...post.play,
+ game
+ }
+ }, {
+ where: {
+ id
+ }
+ })
+
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+
+ socket.on(EVENTS.outgoing.start, async ({ id }) => {
+ const post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+
+ let newPlay;
+ if (post.play.status === 'paused') {
+ newPlay = {
+ ...post.play,
+ status: 'started',
+ stepTimeout: +new Date() + post.play.step.post.timeout
+ }
+ } else {
+ const firstStep = getFirstLeafStep(post.play.game.steps[0]);
+
+ newPlay = {
+ ...post.play,
+ status: 'started',
+ step: firstStep.step,
+ variables: firstStep.variables,
+ stepTimeout:
+ +new Date() + firstStep.step.post.timeout,
+ }
+
+ }
+
+
+ await Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ })
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+
+ socket.on(EVENTS.outgoing.next, async ({ id }) => {
+ const post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+ const play = post.play;
+ const nextStep = getNextLeafStep(
+ play.game.steps,
+ play.step.id,
+ play.variables,
+ play.playerIds
+ )
+ const newPlay = nextStep?.step ? {
+ ...play,
+ step: nextStep.step,
+ variables: nextStep.variables
+ } : {
+ game: play.game,
+ gameId: play.gameId,
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: {}
+ }
+
+ await Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ })
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+
+ socket.on(EVENTS.outgoing.pause, async ({ id }) => {
+ const post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+
+ const newPlay = {
+ ...post.play,
+ status: 'paused'
+ }
+
+ await Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ })
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+
+ socket.on(EVENTS.outgoing.stop, async ({ id }) => {
+ const post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+
+ const newPlay = {
+ ...post.play,
+ status: 'stopped'
+ }
+
+ await Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ })
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+}
+
+module.exports = {
+ registerPlaySocketEvents,
+ initializePlayTasks
+}
diff --git a/ScheduledTasks.js b/ScheduledTasks.js
index 4671631..b540178 100644
--- a/ScheduledTasks.js
+++ b/ScheduledTasks.js
@@ -5,6 +5,7 @@ const { Op } = sequelize
const { User, Event, UserEvent, Notification, Post, Weave, GlassBeadGame } = require('./models')
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
+const { initializePlayTasks } = require('./Play')
function scheduleEventNotification(data) {
const {
@@ -187,37 +188,33 @@ async function scheduleGBGMoveJobs(postId, player, moveNumber, deadline) {
const sendEmail = p.emailsDisabled
? null
: await sgMail.send({
- to: p.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
- Hi ${p.name}, ${
- you ? 'You' : player.name
- } failed to make ${
- you ? 'your' : 'their'
- } move in time on this glass bead game:
+ to: p.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
+ Hi ${p.name}, ${you ? 'You' : player.name
+ } failed to make ${you ? 'your' : 'their'
+ } move in time on this glass bead game:
http://${config.appURL}/p/${postId}
The game has now ended!
`,
- html: `
+ html: `
Hi ${p.name},
- ${you ? 'You' : player.name} failed to make ${
- you ? 'your' : 'their'
- } move in time on this glass bead game.
+ ${you ? 'You' : player.name} failed to make ${you ? 'your' : 'their'
+ } move in time on this glass bead game.
The game has now ended!
`,
- })
+ })
Promise.all([createNotification, sendEmail])
.then(() => resolve())
.catch((error) => resolve(error))
@@ -344,6 +341,8 @@ async function initializeScheduledTasks() {
if (nextPlayer) scheduleGBGMoveJobs(id, nextPlayer, moveNumber, nextMoveDeadline)
}
})
+
+ initializePlayTasks()
}
module.exports = {
diff --git a/Socket.js b/Socket.js
index 9f23168..f90db7f 100644
--- a/Socket.js
+++ b/Socket.js
@@ -6,6 +6,7 @@ const socketServer = require('http').createServer()
const socketIo = require('socket.io')
const io = socketIo(socketServer, { cors: { origin: whitelist } })
// socket.io cheatsheet: https://socket.io/docs/v3/emit-cheatsheet/
+const { registerPlaySocketEvents } = require('./Play')
const sockets = []
const rooms = [] // space, chat, post, or game + id: `space-58`
@@ -200,6 +201,8 @@ io.on('connection', (socket) => {
// gameRooms[roomId] = gameRooms[roomId].filter((users) => users.socketId !== socket.id)
// }
// })
+
+ registerPlaySocketEvents(socket, io)
})
socketServer.listen(5001)
From 4687f2df54ed388dd6adcf088c392fa53572df93 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Tue, 12 Mar 2024 13:59:00 +0100
Subject: [PATCH 06/21] hm
---
Play.js | 133 +++++++++++++++++++++++++++++-----------------
package-lock.json | 11 ++++
package.json | 1 +
3 files changed, 96 insertions(+), 49 deletions(-)
diff --git a/Play.js b/Play.js
index 6361c4b..3d94edf 100644
--- a/Play.js
+++ b/Play.js
@@ -2,21 +2,19 @@ const sequelize = require('sequelize')
const { Op } = sequelize
const { User, Event, UserEvent, Notification, Post, Weave, GlassBeadGame } = require('./models')
const schedule = require('node-schedule')
+const { createPost } = require('./Helpers')
+const parseDuration = require('parse-duration')
-function getFirstLeafStep(
- step,
- variables,
- playerIds
-) {
+const getFirstLeafStep = (step, variables, playerIds) => {
if (!step) {
return undefined
}
switch (step.type) {
- case 'game':
- throw new Error('TODO')
- case 'post':
+ case "game":
+ throw new Error("TODO")
+ case "post":
return { step, variables }
- case 'rounds': {
+ case "rounds": {
const firstStep = getFirstLeafStep(step.steps[0], variables, playerIds)
if (!firstStep) {
return undefined
@@ -25,11 +23,11 @@ function getFirstLeafStep(
step: firstStep.step,
variables: {
...firstStep.variables,
- [`${step.id}_round`]: firstStep.variables[`${step.id}_round`] ?? 1,
- },
+ [`${step.id}_round`]: firstStep.variables[`${step.id}_round`] ?? 1
+ }
}
}
- case 'turns': {
+ case "turns": {
const firstStep = getFirstLeafStep(step.steps[0], variables, playerIds)
if (!firstStep) {
return undefined
@@ -38,8 +36,9 @@ function getFirstLeafStep(
step: firstStep.step,
variables: {
...firstStep.variables,
- [`${step.id}_player`]: firstStep.variables[`${step.id}_player`] ?? playerIds[0],
- },
+ [`${step.id}_player`]:
+ firstStep.variables[`${step.id}_player`] ?? playerIds[0]
+ }
}
}
default: {
@@ -49,38 +48,38 @@ function getFirstLeafStep(
}
}
-const getNextLeafStep = (
- steps,
- stepId,
- variables,
- playerIds
-) => {
- let currentFound = false
+const getTransition = (steps, stepId, variables, playerIds) => {
+ let current
let currentVariables = variables
for (let i = 0; i < steps.length; i++) {
const step = steps[i]
- if (currentFound) {
+ if (current) {
const nextStep = getFirstLeafStep(step, currentVariables, playerIds)
if (nextStep) {
- return nextStep
+ return { current, next: nextStep.step, variables: nextStep.variables }
}
} else {
switch (step.type) {
- case 'game':
- throw new Error('TODO')
- case 'post':
+ case "game":
+ throw new Error("TODO")
+ case "post":
if (step.id === stepId) {
- currentFound = true
+ current = step
}
break
- case 'rounds': {
- const result = getNextLeafStep(step.steps, stepId, currentVariables, playerIds)
+ case "rounds": {
+ const result = getTransition(
+ step.steps,
+ stepId,
+ currentVariables,
+ playerIds
+ )
if (!result) {
break
}
- if (result.step) {
+ if (result.next) {
return result
}
@@ -91,26 +90,35 @@ const getNextLeafStep = (
step,
{
...result.variables,
- [roundKey]: currentRound + 1,
+ [roundKey]: currentRound + 1
},
playerIds
)
if (firstStep) {
- return firstStep
+ return {
+ current: result.current,
+ next: firstStep.step,
+ variables: firstStep.variables
+ }
}
}
- currentFound = true
- currentVariables = omit(currentVariables, roundKey)
+ current = result.current
+ currentVariables = omit(result.variables, roundKey)
break
}
- case 'turns': {
- const result = getNextLeafStep(step.steps, stepId, currentVariables, playerIds)
+ case "turns": {
+ const result = getTransition(
+ step.steps,
+ stepId,
+ currentVariables,
+ playerIds
+ )
if (!result) {
break
}
- if (result.step) {
+ if (result.next) {
return result
}
@@ -122,17 +130,21 @@ const getNextLeafStep = (
step,
{
...result.variables,
- [playerKey]: playerIds[currentPlayerIndex + 1],
+ [playerKey]: playerIds[currentPlayerIndex + 1]
},
playerIds
)
if (firstStep) {
- return firstStep
+ return {
+ current: result.current,
+ next: firstStep.step,
+ variables: firstStep.variables
+ }
}
}
- currentFound = true
- currentVariables = omit(currentVariables, playerKey)
+ current = result.current
+ currentVariables = omit(result.variables, playerKey)
break
}
default: {
@@ -143,13 +155,34 @@ const getNextLeafStep = (
}
}
- if (currentFound) {
- return { variables: currentVariables }
+ if (current) {
+ return { current, variables: currentVariables }
}
return undefined
}
+async function startStep(post) {
+ const { current, next, variables } = getTransition(post)
+
+ switch (current.type) {
+ case 'post':
+ const timeout = parseDuration(current.post.timeout)
+ await createPost({
+ type: 'post',
+ title: current.post.title,
+ text: current.post.text,
+ }, [], post.creatorId)
+ scheduleEndStep()
+ break;
+ default:
+ throw new Error('TODO')
+ }
+
+ const newPost = {};
+ scheduleEndStep(post);
+}
+
async function endStep(post) {
// TODO
}
@@ -163,7 +196,7 @@ function scheduleEndStep(post) {
}
})
if (!deepEquals(currentPost?.play, post.play)) {
- // The state of the play has changed, we assume a new scheduled job has been triggered.
+ // The state of the play has changed, this job is outdated.
return
}
endStep(currentPost)
@@ -261,7 +294,6 @@ function registerPlaySocketEvents(socket, io) {
}
-
await Post.update({
play: newPlay
}, {
@@ -270,6 +302,9 @@ function registerPlaySocketEvents(socket, io) {
}
})
io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+
+ const newPost = { ...post, play: newPlay }
+ await startStep(newPost)
})
socket.on(EVENTS.outgoing.next, async ({ id }) => {
@@ -279,16 +314,16 @@ function registerPlaySocketEvents(socket, io) {
}
})
const play = post.play;
- const nextStep = getNextLeafStep(
+ const transition = getTransition(
play.game.steps,
play.step.id,
play.variables,
play.playerIds
)
- const newPlay = nextStep?.step ? {
+ const newPlay = transition?.next ? {
...play,
- step: nextStep.step,
- variables: nextStep.variables
+ step: transition.next,
+ variables: transition.variables
} : {
game: play.game,
gameId: play.gameId,
diff --git a/package-lock.json b/package-lock.json
index bf54eb1..e1119db 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,7 @@
"mysql2": "^3.8.0",
"node-schedule": "^2.1.1",
"nodemon": "^3.0.3",
+ "parse-duration": "^1.1.0",
"pg": "^8.11.3",
"puppeteer": "^20.3.0",
"sequelize": "^6.35.2",
@@ -6717,6 +6718,11 @@
"node": ">=6"
}
},
+ "node_modules/parse-duration": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz",
+ "integrity": "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ=="
+ },
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -13948,6 +13954,11 @@
"callsites": "^3.0.0"
}
},
+ "parse-duration": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz",
+ "integrity": "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ=="
+ },
"parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
diff --git a/package.json b/package.json
index b03271e..5991543 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"mysql2": "^3.8.0",
"node-schedule": "^2.1.1",
"nodemon": "^3.0.3",
+ "parse-duration": "^1.1.0",
"pg": "^8.11.3",
"puppeteer": "^20.3.0",
"sequelize": "^6.35.2",
From 3cff216f95dab5716047be794d840de8511d34a5 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Tue, 12 Mar 2024 13:59:07 +0100
Subject: [PATCH 07/21] hmm
---
Play.js => Play.ts | 279 +++++++++++++++++++++++++++++----------------
package-lock.json | 51 +++++++--
package.json | 5 +-
tsconfig.json | 20 ++++
4 files changed, 247 insertions(+), 108 deletions(-)
rename Play.js => Play.ts (56%)
create mode 100644 tsconfig.json
diff --git a/Play.js b/Play.ts
similarity index 56%
rename from Play.js
rename to Play.ts
index 3d94edf..e37d802 100644
--- a/Play.js
+++ b/Play.ts
@@ -1,20 +1,114 @@
-const sequelize = require('sequelize')
+import { isEqual, omit } from 'lodash'
+import schedule from 'node-schedule'
+import sequelize from 'sequelize'
+import { Server, Socket } from 'socket.io'
+
+const { Post } = require('./models')
+
const { Op } = sequelize
-const { User, Event, UserEvent, Notification, Post, Weave, GlassBeadGame } = require('./models')
-const schedule = require('node-schedule')
-const { createPost } = require('./Helpers')
-const parseDuration = require('parse-duration')
-const getFirstLeafStep = (step, variables, playerIds) => {
+export const POST_TYPE = [
+'post',
+ 'play',
+ 'comment',
+ 'bead',
+ 'poll-answer',
+ 'card-face',
+ 'gbg-room-comment',
+ 'url-block',
+ 'image-block',
+ 'audio-block',
+] as const
+
+export type PostType = (typeof POST_TYPE)[number]
+
+export type Post = {
+ id: number
+ type: PostType
+ mediaTypes: string
+ title: string
+ text: string
+ createdAt: string
+ updatedAt: string
+ totalComments: number
+ totalLikes: number
+ totalRatings: number
+ totalReposts: number
+ totalLinks: number
+ creatorId: number
+ game?: Game
+ play?: Play
+}
+
+export type Step = {
+ id: string
+} & (
+ | {
+ type: 'post'
+ post: {
+ title: string
+ text: string
+ timeout: number
+ }
+ }
+ | {
+ type: 'game'
+ gameId: number
+ }
+ | {
+ type: 'rounds'
+ amount: string
+ steps: Step[]
+ }
+ | {
+ type: 'turns'
+ steps: Step[]
+ }
+)
+
+export type LeafStep = Extract
+
+export type Game = {
+ steps: Step[]
+}
+
+export type PlayVariables = Record
+
+export type Play = {
+ game: Game
+ gameId: number
+ playerIds: number[]
+ status: 'waiting' | 'started' | 'paused' | 'stopped' | 'ended'
+ variables: PlayVariables
+} & (
+ | { status: 'waiting' | 'stopped' | 'ended' }
+ | { status: 'paused'; step: LeafStep }
+ | { status: 'started'; step: LeafStep; stepTimeout: number }
+)
+
+async function getPost(id: number) {
+ return await Post.findOne({
+ where: {
+ state: 'active',
+ id
+ }
+ })
+}
+
+const getFirstLeafStep = (
+ step: Step | undefined,
+ variables: PlayVariables,
+ playerIds: number[]
+): undefined | { step: LeafStep; variables: PlayVariables } => {
if (!step) {
return undefined
}
switch (step.type) {
- case "game":
- throw new Error("TODO")
- case "post":
+ case 'game':
+ throw new Error('TODO')
+ case 'post':
return { step, variables }
- case "rounds": {
+ case 'rounds': {
const firstStep = getFirstLeafStep(step.steps[0], variables, playerIds)
if (!firstStep) {
return undefined
@@ -23,11 +117,11 @@ const getFirstLeafStep = (step, variables, playerIds) => {
step: firstStep.step,
variables: {
...firstStep.variables,
- [`${step.id}_round`]: firstStep.variables[`${step.id}_round`] ?? 1
- }
+ [`${step.id}_round`]: firstStep.variables[`${step.id}_round`] ?? 1,
+ },
}
}
- case "turns": {
+ case 'turns': {
const firstStep = getFirstLeafStep(step.steps[0], variables, playerIds)
if (!firstStep) {
return undefined
@@ -36,20 +130,24 @@ const getFirstLeafStep = (step, variables, playerIds) => {
step: firstStep.step,
variables: {
...firstStep.variables,
- [`${step.id}_player`]:
- firstStep.variables[`${step.id}_player`] ?? playerIds[0]
- }
+ [`${step.id}_player`]: firstStep.variables[`${step.id}_player`] ?? playerIds[0],
+ },
}
}
default: {
- const exhaustivenessCheck = step
+ const exhaustivenessCheck: never = step
throw exhaustivenessCheck
}
}
}
-const getTransition = (steps, stepId, variables, playerIds) => {
- let current
+const getTransition = (
+ steps: Step[],
+ stepId: string,
+ variables: PlayVariables,
+ playerIds: number[]
+): undefined | { current: LeafStep; next?: LeafStep; variables: PlayVariables } => {
+ let current: LeafStep | undefined
let currentVariables = variables
for (let i = 0; i < steps.length; i++) {
const step = steps[i]
@@ -61,20 +159,15 @@ const getTransition = (steps, stepId, variables, playerIds) => {
}
} else {
switch (step.type) {
- case "game":
- throw new Error("TODO")
- case "post":
+ case 'game':
+ throw new Error('TODO')
+ case 'post':
if (step.id === stepId) {
current = step
}
break
- case "rounds": {
- const result = getTransition(
- step.steps,
- stepId,
- currentVariables,
- playerIds
- )
+ case 'rounds': {
+ const result = getTransition(step.steps, stepId, currentVariables, playerIds)
if (!result) {
break
}
@@ -84,13 +177,13 @@ const getTransition = (steps, stepId, variables, playerIds) => {
}
const roundKey = `${step.id}_round`
- const currentRound = currentVariables[roundKey]
+ const currentRound = currentVariables[roundKey] as number
if (currentRound < +step.amount) {
const firstStep = getFirstLeafStep(
step,
{
...result.variables,
- [roundKey]: currentRound + 1
+ [roundKey]: currentRound + 1,
},
playerIds
)
@@ -98,7 +191,7 @@ const getTransition = (steps, stepId, variables, playerIds) => {
return {
current: result.current,
next: firstStep.step,
- variables: firstStep.variables
+ variables: firstStep.variables,
}
}
}
@@ -107,13 +200,8 @@ const getTransition = (steps, stepId, variables, playerIds) => {
currentVariables = omit(result.variables, roundKey)
break
}
- case "turns": {
- const result = getTransition(
- step.steps,
- stepId,
- currentVariables,
- playerIds
- )
+ case 'turns': {
+ const result = getTransition(step.steps, stepId, currentVariables, playerIds)
if (!result) {
break
}
@@ -123,14 +211,14 @@ const getTransition = (steps, stepId, variables, playerIds) => {
}
const playerKey = `${step.id}_player`
- const currentPlayerId = result.variables[playerKey]
+ const currentPlayerId = result.variables[playerKey] as number
const currentPlayerIndex = playerIds.indexOf(currentPlayerId)
if (currentPlayerIndex < playerIds.length - 1) {
const firstStep = getFirstLeafStep(
step,
{
...result.variables,
- [playerKey]: playerIds[currentPlayerIndex + 1]
+ [playerKey]: playerIds[currentPlayerIndex + 1],
},
playerIds
)
@@ -138,7 +226,7 @@ const getTransition = (steps, stepId, variables, playerIds) => {
return {
current: result.current,
next: firstStep.step,
- variables: firstStep.variables
+ variables: firstStep.variables,
}
}
}
@@ -148,7 +236,7 @@ const getTransition = (steps, stepId, variables, playerIds) => {
break
}
default: {
- const exhaustivenessCheck = step
+ const exhaustivenessCheck: never = step
throw exhaustivenessCheck
}
}
@@ -162,40 +250,37 @@ const getTransition = (steps, stepId, variables, playerIds) => {
return undefined
}
-async function startStep(post) {
- const { current, next, variables } = getTransition(post)
- switch (current.type) {
- case 'post':
- const timeout = parseDuration(current.post.timeout)
- await createPost({
- type: 'post',
- title: current.post.title,
- text: current.post.text,
- }, [], post.creatorId)
- scheduleEndStep()
- break;
- default:
- throw new Error('TODO')
- }
-
- const newPost = {};
- scheduleEndStep(post);
+async function startStep(post: Post) {
+ const play = post.play!
+ // const { current, next, variables } = getTransition(play.game.steps, play.step.id, play.variables, play.playerIds)
+
+ // switch (current.type) {
+ // case 'post':
+ // const timeout = parseDuration(current.post.timeout)
+ // await createPost({
+ // type: 'post',
+ // title: current.post.title,
+ // text: current.post.text,
+ // }, [], post.creatorId)
+ // scheduleEndStep(post)
+ // break;
+ // default:
+ // throw new Error('TODO')
+ // }
+
+ // const newPost = {};
+ // scheduleEndStep(post);
}
-async function endStep(post) {
+async function endStep(post: Post) {
// TODO
}
-function scheduleEndStep(post) {
- schedule.scheduleJob(post.play.stepTimeout, async () => {
- const currentPost = await Post.findOne({
- where: {
- state: 'active',
- id: post.id
- }
- })
- if (!deepEquals(currentPost?.play, post.play)) {
+function scheduleEndStep(post: Post, timeout: number) {
+ schedule.scheduleJob(timeout, async () => {
+ const currentPost = await getPost(post.id)
+ if (!isEqual(currentPost?.play, post.play)) {
// The state of the play has changed, this job is outdated.
return
}
@@ -204,7 +289,7 @@ function scheduleEndStep(post) {
}
async function initializePlayTasks() {
- const plays = await Post.findAll({
+ const plays: Post[] = await Post.findAll({
where: {
state: 'active',
'play': { [Op.not]: null }
@@ -212,7 +297,7 @@ async function initializePlayTasks() {
})
for (const post of plays) {
- const play = post.play
+ const play = post.play!
if (play.status !== 'started') {
continue
}
@@ -221,7 +306,7 @@ async function initializePlayTasks() {
if (stepTimeout < new Date()) {
endStep(post)
} else {
- scheduleEndStep(post)
+ scheduleEndStep(post, play.stepTimeout)
}
}
}
@@ -239,13 +324,9 @@ const EVENTS = {
}
}
-function registerPlaySocketEvents(socket, io) {
- socket.on(EVENTS.outgoing.updateGame, async ({ id, game }) => {
- const post = await Post.findOne({
- where: {
- id
- }
- })
+function registerPlaySocketEvents(socket: Socket, io: Server) {
+ socket.on(EVENTS.outgoing.updateGame, async ({ id, game }: { id: number, game: Game }) => {
+ const post = await getPost(id)
const newPlay = {
...post.play,
@@ -263,28 +344,32 @@ function registerPlaySocketEvents(socket, io) {
}
})
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
})
- socket.on(EVENTS.outgoing.start, async ({ id }) => {
- const post = await Post.findOne({
+ socket.on(EVENTS.outgoing.start, async ({ id }: { id: number }) => {
+ const post: Post = await Post.findOne({
where: {
id
}
})
+ const play = post.play!
- let newPlay;
- if (post.play.status === 'paused') {
+ let newPlay: Play;
+ if (play.status === 'paused') {
newPlay = {
- ...post.play,
+ ...play,
status: 'started',
- stepTimeout: +new Date() + post.play.step.post.timeout
+ stepTimeout: +new Date() + play.step.post.timeout
}
} else {
- const firstStep = getFirstLeafStep(post.play.game.steps[0]);
+ const firstStep = getFirstLeafStep(play.game.steps[0], play.variables, play.playerIds);
+ if (!firstStep) {
+ throw new Error('No step.')
+ }
newPlay = {
- ...post.play,
+ ...play,
status: 'started',
step: firstStep.step,
variables: firstStep.variables,
@@ -301,13 +386,13 @@ function registerPlaySocketEvents(socket, io) {
id
}
})
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
const newPost = { ...post, play: newPlay }
await startStep(newPost)
})
- socket.on(EVENTS.outgoing.next, async ({ id }) => {
+ socket.on(EVENTS.outgoing.next, async ({ id }: { id: number }) => {
const post = await Post.findOne({
where: {
id
@@ -339,10 +424,10 @@ function registerPlaySocketEvents(socket, io) {
id
}
})
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
})
- socket.on(EVENTS.outgoing.pause, async ({ id }) => {
+ socket.on(EVENTS.outgoing.pause, async ({ id }: { id: number }) => {
const post = await Post.findOne({
where: {
id
@@ -361,10 +446,10 @@ function registerPlaySocketEvents(socket, io) {
id
}
})
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
})
- socket.on(EVENTS.outgoing.stop, async ({ id }) => {
+ socket.on(EVENTS.outgoing.stop, async ({ id }: { id: number }) => {
const post = await Post.findOne({
where: {
id
@@ -383,7 +468,7 @@ function registerPlaySocketEvents(socket, io) {
id
}
})
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay })
+ io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
})
}
diff --git a/package-lock.json b/package-lock.json
index e1119db..f1b5f72 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,8 +37,11 @@
"web-push": "^3.6.7"
},
"devDependencies": {
+ "@types/lodash": "^4.17.0",
+ "@types/node-schedule": "^2.1.6",
"eslint": "^8.56.0",
- "eslint-config-airbnb-typescript-prettier": "^5.0.0"
+ "eslint-config-airbnb-typescript-prettier": "^5.0.0",
+ "typescript": "^5.4.2"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -2204,6 +2207,12 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.0",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
+ "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
+ "dev": true
+ },
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
@@ -2217,6 +2226,15 @@
"undici-types": "~5.26.4"
}
},
+ "node_modules/@types/node-schedule": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.6.tgz",
+ "integrity": "sha512-6AlZSUiNTdaVmH5jXYxX9YgmF1zfOlbjUqw0EllTBmZCnN1R5RR/m/u3No1OiWR05bnQ4jM4/+w4FcGvkAtnKQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/semver": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
@@ -8332,11 +8350,10 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/typescript": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
- "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+ "version": "5.4.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
+ "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"devOptional": true,
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10589,6 +10606,12 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
+ "@types/lodash": {
+ "version": "4.17.0",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
+ "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
+ "dev": true
+ },
"@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
@@ -10602,6 +10625,15 @@
"undici-types": "~5.26.4"
}
},
+ "@types/node-schedule": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.6.tgz",
+ "integrity": "sha512-6AlZSUiNTdaVmH5jXYxX9YgmF1zfOlbjUqw0EllTBmZCnN1R5RR/m/u3No1OiWR05bnQ4jM4/+w4FcGvkAtnKQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/semver": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
@@ -15142,11 +15174,10 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"typescript": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
- "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
- "devOptional": true,
- "peer": true
+ "version": "5.4.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
+ "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
+ "devOptional": true
},
"umzug": {
"version": "2.3.0",
diff --git a/package.json b/package.json
index 5991543..154a846 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,10 @@
"web-push": "^3.6.7"
},
"devDependencies": {
+ "@types/lodash": "^4.17.0",
+ "@types/node-schedule": "^2.1.6",
"eslint": "^8.56.0",
- "eslint-config-airbnb-typescript-prettier": "^5.0.0"
+ "eslint-config-airbnb-typescript-prettier": "^5.0.0",
+ "typescript": "^5.4.2"
}
}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..c40c209
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "module": "commonjs",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowJs": false,
+ "outDir": "./",
+ "noEmitOnError": false,
+ "incremental": true,
+ },
+ "include": [
+ "./**/*"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
From 4c39b3f90742bc56ce2214c645c555d75643c282 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Wed, 13 Mar 2024 18:45:18 +0100
Subject: [PATCH 08/21] making progress
---
Helpers.js | 6 +-
Play.ts | 478 -----------------------
PlayServer.js | 378 ++++++++++++++++++
PlayServer.ts | 517 +++++++++++++++++++++++++
ScheduledTasks.js | 7 +-
Socket.js | 4 +-
migration-updates/add-move-to-posts.js | 24 ++
models/Post.js | 1 +
package-lock.json | 189 +++++++++
package.json | 3 +-
routes/Post.js | 50 ++-
tsconfig.json | 1 -
12 files changed, 1171 insertions(+), 487 deletions(-)
delete mode 100644 Play.ts
create mode 100644 PlayServer.js
create mode 100644 PlayServer.ts
create mode 100644 migration-updates/add-move-to-posts.js
diff --git a/Helpers.js b/Helpers.js
index e285e54..cbce5e3 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -966,7 +966,8 @@ const fullPostAttributes = [
'totalRatings',
'totalLinks',
'game',
- 'play'
+ 'play',
+ 'move'
]
// todo: replace all use cases with const fullPostAttributes above
@@ -988,6 +989,7 @@ function findFullPostAttributes(model, accountId) {
'totalLinks',
'game',
'play',
+ 'move',
// accountLike('post', model, accountId),
// accountComment('post', model, accountId),
// accountLink('post', model, accountId),
@@ -1715,6 +1717,7 @@ function createPost(data, files, accountId) {
glassBeadGame,
game,
play,
+ move,
card,
color,
watermark,
@@ -1738,6 +1741,7 @@ function createPost(data, files, accountId) {
lastActivity: new Date(),
game,
play,
+ move,
})
// todo: add the correct notification type
diff --git a/Play.ts b/Play.ts
deleted file mode 100644
index e37d802..0000000
--- a/Play.ts
+++ /dev/null
@@ -1,478 +0,0 @@
-import { isEqual, omit } from 'lodash'
-import schedule from 'node-schedule'
-import sequelize from 'sequelize'
-import { Server, Socket } from 'socket.io'
-
-const { Post } = require('./models')
-
-const { Op } = sequelize
-
-export const POST_TYPE = [
-'post',
- 'play',
- 'comment',
- 'bead',
- 'poll-answer',
- 'card-face',
- 'gbg-room-comment',
- 'url-block',
- 'image-block',
- 'audio-block',
-] as const
-
-export type PostType = (typeof POST_TYPE)[number]
-
-export type Post = {
- id: number
- type: PostType
- mediaTypes: string
- title: string
- text: string
- createdAt: string
- updatedAt: string
- totalComments: number
- totalLikes: number
- totalRatings: number
- totalReposts: number
- totalLinks: number
- creatorId: number
- game?: Game
- play?: Play
-}
-
-export type Step = {
- id: string
-} & (
- | {
- type: 'post'
- post: {
- title: string
- text: string
- timeout: number
- }
- }
- | {
- type: 'game'
- gameId: number
- }
- | {
- type: 'rounds'
- amount: string
- steps: Step[]
- }
- | {
- type: 'turns'
- steps: Step[]
- }
-)
-
-export type LeafStep = Extract
-
-export type Game = {
- steps: Step[]
-}
-
-export type PlayVariables = Record
-
-export type Play = {
- game: Game
- gameId: number
- playerIds: number[]
- status: 'waiting' | 'started' | 'paused' | 'stopped' | 'ended'
- variables: PlayVariables
-} & (
- | { status: 'waiting' | 'stopped' | 'ended' }
- | { status: 'paused'; step: LeafStep }
- | { status: 'started'; step: LeafStep; stepTimeout: number }
-)
-
-async function getPost(id: number) {
- return await Post.findOne({
- where: {
- state: 'active',
- id
- }
- })
-}
-
-const getFirstLeafStep = (
- step: Step | undefined,
- variables: PlayVariables,
- playerIds: number[]
-): undefined | { step: LeafStep; variables: PlayVariables } => {
- if (!step) {
- return undefined
- }
- switch (step.type) {
- case 'game':
- throw new Error('TODO')
- case 'post':
- return { step, variables }
- case 'rounds': {
- const firstStep = getFirstLeafStep(step.steps[0], variables, playerIds)
- if (!firstStep) {
- return undefined
- }
- return {
- step: firstStep.step,
- variables: {
- ...firstStep.variables,
- [`${step.id}_round`]: firstStep.variables[`${step.id}_round`] ?? 1,
- },
- }
- }
- case 'turns': {
- const firstStep = getFirstLeafStep(step.steps[0], variables, playerIds)
- if (!firstStep) {
- return undefined
- }
- return {
- step: firstStep.step,
- variables: {
- ...firstStep.variables,
- [`${step.id}_player`]: firstStep.variables[`${step.id}_player`] ?? playerIds[0],
- },
- }
- }
- default: {
- const exhaustivenessCheck: never = step
- throw exhaustivenessCheck
- }
- }
-}
-
-const getTransition = (
- steps: Step[],
- stepId: string,
- variables: PlayVariables,
- playerIds: number[]
-): undefined | { current: LeafStep; next?: LeafStep; variables: PlayVariables } => {
- let current: LeafStep | undefined
- let currentVariables = variables
- for (let i = 0; i < steps.length; i++) {
- const step = steps[i]
-
- if (current) {
- const nextStep = getFirstLeafStep(step, currentVariables, playerIds)
- if (nextStep) {
- return { current, next: nextStep.step, variables: nextStep.variables }
- }
- } else {
- switch (step.type) {
- case 'game':
- throw new Error('TODO')
- case 'post':
- if (step.id === stepId) {
- current = step
- }
- break
- case 'rounds': {
- const result = getTransition(step.steps, stepId, currentVariables, playerIds)
- if (!result) {
- break
- }
-
- if (result.next) {
- return result
- }
-
- const roundKey = `${step.id}_round`
- const currentRound = currentVariables[roundKey] as number
- if (currentRound < +step.amount) {
- const firstStep = getFirstLeafStep(
- step,
- {
- ...result.variables,
- [roundKey]: currentRound + 1,
- },
- playerIds
- )
- if (firstStep) {
- return {
- current: result.current,
- next: firstStep.step,
- variables: firstStep.variables,
- }
- }
- }
-
- current = result.current
- currentVariables = omit(result.variables, roundKey)
- break
- }
- case 'turns': {
- const result = getTransition(step.steps, stepId, currentVariables, playerIds)
- if (!result) {
- break
- }
-
- if (result.next) {
- return result
- }
-
- const playerKey = `${step.id}_player`
- const currentPlayerId = result.variables[playerKey] as number
- const currentPlayerIndex = playerIds.indexOf(currentPlayerId)
- if (currentPlayerIndex < playerIds.length - 1) {
- const firstStep = getFirstLeafStep(
- step,
- {
- ...result.variables,
- [playerKey]: playerIds[currentPlayerIndex + 1],
- },
- playerIds
- )
- if (firstStep) {
- return {
- current: result.current,
- next: firstStep.step,
- variables: firstStep.variables,
- }
- }
- }
-
- current = result.current
- currentVariables = omit(result.variables, playerKey)
- break
- }
- default: {
- const exhaustivenessCheck: never = step
- throw exhaustivenessCheck
- }
- }
- }
- }
-
- if (current) {
- return { current, variables: currentVariables }
- }
-
- return undefined
-}
-
-
-async function startStep(post: Post) {
- const play = post.play!
- // const { current, next, variables } = getTransition(play.game.steps, play.step.id, play.variables, play.playerIds)
-
- // switch (current.type) {
- // case 'post':
- // const timeout = parseDuration(current.post.timeout)
- // await createPost({
- // type: 'post',
- // title: current.post.title,
- // text: current.post.text,
- // }, [], post.creatorId)
- // scheduleEndStep(post)
- // break;
- // default:
- // throw new Error('TODO')
- // }
-
- // const newPost = {};
- // scheduleEndStep(post);
-}
-
-async function endStep(post: Post) {
- // TODO
-}
-
-function scheduleEndStep(post: Post, timeout: number) {
- schedule.scheduleJob(timeout, async () => {
- const currentPost = await getPost(post.id)
- if (!isEqual(currentPost?.play, post.play)) {
- // The state of the play has changed, this job is outdated.
- return
- }
- endStep(currentPost)
- })
-}
-
-async function initializePlayTasks() {
- const plays: Post[] = await Post.findAll({
- where: {
- state: 'active',
- 'play': { [Op.not]: null }
- }
- })
-
- for (const post of plays) {
- const play = post.play!
- if (play.status !== 'started') {
- continue
- }
-
- const stepTimeout = new Date(play.stepTimeout);
- if (stepTimeout < new Date()) {
- endStep(post)
- } else {
- scheduleEndStep(post, play.stepTimeout)
- }
- }
-}
-
-const EVENTS = {
- outgoing: {
- updateGame: 'play:outgoing-update-game',
- start: 'play:outgoing-start',
- next: 'play:outgoing-next',
- pause: 'play:outgoing-pause',
- stop: 'play:outgoing-stop'
- },
- incoming: {
- updated: 'play:incoming-updated'
- }
-}
-
-function registerPlaySocketEvents(socket: Socket, io: Server) {
- socket.on(EVENTS.outgoing.updateGame, async ({ id, game }: { id: number, game: Game }) => {
- const post = await getPost(id)
-
- const newPlay = {
- ...post.play,
- game
- }
-
- await Post.update({
- play: {
- ...post.play,
- game
- }
- }, {
- where: {
- id
- }
- })
-
- io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
- })
-
- socket.on(EVENTS.outgoing.start, async ({ id }: { id: number }) => {
- const post: Post = await Post.findOne({
- where: {
- id
- }
- })
- const play = post.play!
-
- let newPlay: Play;
- if (play.status === 'paused') {
- newPlay = {
- ...play,
- status: 'started',
- stepTimeout: +new Date() + play.step.post.timeout
- }
- } else {
- const firstStep = getFirstLeafStep(play.game.steps[0], play.variables, play.playerIds);
- if (!firstStep) {
- throw new Error('No step.')
- }
-
- newPlay = {
- ...play,
- status: 'started',
- step: firstStep.step,
- variables: firstStep.variables,
- stepTimeout:
- +new Date() + firstStep.step.post.timeout,
- }
-
- }
-
- await Post.update({
- play: newPlay
- }, {
- where: {
- id
- }
- })
- io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
-
- const newPost = { ...post, play: newPlay }
- await startStep(newPost)
- })
-
- socket.on(EVENTS.outgoing.next, async ({ id }: { id: number }) => {
- const post = await Post.findOne({
- where: {
- id
- }
- })
- const play = post.play;
- const transition = getTransition(
- play.game.steps,
- play.step.id,
- play.variables,
- play.playerIds
- )
- const newPlay = transition?.next ? {
- ...play,
- step: transition.next,
- variables: transition.variables
- } : {
- game: play.game,
- gameId: play.gameId,
- playerIds: play.playerIds,
- status: 'ended',
- variables: {}
- }
-
- await Post.update({
- play: newPlay
- }, {
- where: {
- id
- }
- })
- io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
- })
-
- socket.on(EVENTS.outgoing.pause, async ({ id }: { id: number }) => {
- const post = await Post.findOne({
- where: {
- id
- }
- })
-
- const newPlay = {
- ...post.play,
- status: 'paused'
- }
-
- await Post.update({
- play: newPlay
- }, {
- where: {
- id
- }
- })
- io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
- })
-
- socket.on(EVENTS.outgoing.stop, async ({ id }: { id: number }) => {
- const post = await Post.findOne({
- where: {
- id
- }
- })
-
- const newPlay = {
- ...post.play,
- status: 'stopped'
- }
-
- await Post.update({
- play: newPlay
- }, {
- where: {
- id
- }
- })
- io.in(`${id}`).emit(EVENTS.incoming.updated, { play: newPlay })
- })
-}
-
-module.exports = {
- registerPlaySocketEvents,
- initializePlayTasks
-}
diff --git a/PlayServer.js b/PlayServer.js
new file mode 100644
index 0000000..4a03588
--- /dev/null
+++ b/PlayServer.js
@@ -0,0 +1,378 @@
+"use strict";
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.registerPlayServerEvents = exports.initializePlayServerTasks = void 0;
+const lodash_1 = require("lodash");
+const node_schedule_1 = __importDefault(require("node-schedule"));
+const parse_duration_1 = __importDefault(require("parse-duration"));
+const sequelize_1 = __importDefault(require("sequelize"));
+const { Post, Link } = require('./models');
+const { createPost } = require('./Helpers');
+const { Op } = sequelize_1.default;
+const POST_TYPE = [
+ 'post',
+ 'play',
+ 'comment',
+ 'bead',
+ 'poll-answer',
+ 'card-face',
+ 'gbg-room-comment',
+ 'url-block',
+ 'image-block',
+ 'audio-block',
+];
+function getPost(id) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const post = yield Post.findOne({
+ where: {
+ state: 'active',
+ id
+ }
+ });
+ if (!post) {
+ throw new Error(`Post ${id} not found`);
+ }
+ return post;
+ });
+}
+function updatePost(id, data) {
+ return __awaiter(this, void 0, void 0, function* () {
+ return yield Post.update(data, { where: { id } });
+ });
+}
+const getFirstMove = (step, variables, playerIds) => {
+ var _a, _b;
+ if (!step) {
+ return undefined;
+ }
+ switch (step.type) {
+ case 'game':
+ throw new Error('TODO');
+ case 'move':
+ return { step, variables };
+ case 'rounds': {
+ const firstStep = getFirstMove(step.steps[0], variables, playerIds);
+ if (!firstStep) {
+ return undefined;
+ }
+ return {
+ step: firstStep.step,
+ variables: Object.assign(Object.assign({}, firstStep.variables), { [step.name]: (_a = firstStep.variables[step.name]) !== null && _a !== void 0 ? _a : 1 }),
+ };
+ }
+ case 'turns': {
+ const firstStep = getFirstMove(step.steps[0], variables, playerIds);
+ if (!firstStep) {
+ return undefined;
+ }
+ return {
+ step: firstStep.step,
+ variables: Object.assign(Object.assign({}, firstStep.variables), { [step.name]: (_b = firstStep.variables[step.name]) !== null && _b !== void 0 ? _b : playerIds[0] }),
+ };
+ }
+ default: {
+ const exhaustivenessCheck = step;
+ throw exhaustivenessCheck;
+ }
+ }
+};
+const getTransition = (steps, stepId, variables, playerIds) => {
+ let current;
+ let currentVariables = variables;
+ for (let i = 0; i < steps.length; i++) {
+ const step = steps[i];
+ if (current) {
+ const nextStep = getFirstMove(step, currentVariables, playerIds);
+ if (nextStep) {
+ return { current, next: nextStep.step, variables: nextStep.variables };
+ }
+ }
+ else {
+ switch (step.type) {
+ case 'game':
+ throw new Error('TODO');
+ case 'move':
+ if (step.id === stepId) {
+ current = step;
+ }
+ break;
+ case 'rounds':
+ case 'turns': {
+ const result = getTransition(step.steps, stepId, currentVariables, playerIds);
+ if (!result) {
+ break;
+ }
+ if (result.next) {
+ return result;
+ }
+ let next;
+ if (step.type === 'rounds') {
+ const round = currentVariables[step.name];
+ next = round < +step.amount ? round + 1 : undefined;
+ }
+ else {
+ const playerId = currentVariables[step.name];
+ const playerIndex = playerIds.indexOf(playerId);
+ next = playerIds[playerIndex + 1];
+ }
+ if (next) {
+ const firstStep = getFirstMove(step, Object.assign(Object.assign({}, result.variables), { [step.name]: next }), playerIds);
+ if (firstStep) {
+ return {
+ current: result.current,
+ next: firstStep.step,
+ variables: firstStep.variables,
+ };
+ }
+ }
+ current = result.current;
+ currentVariables = (0, lodash_1.omit)(result.variables, step.name);
+ break;
+ }
+ default: {
+ const exhaustivenessCheck = step;
+ throw exhaustivenessCheck;
+ }
+ }
+ }
+ }
+ if (current) {
+ return { current, variables: currentVariables };
+ }
+ return undefined;
+};
+function createChild(data, parent) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const { post } = yield createPost(data, [], parent.creatorId);
+ yield Link.create({
+ creatorId: parent.creatorId,
+ itemAId: parent.id,
+ itemAType: parent.type,
+ itemBId: post.id,
+ itemBType: post.type,
+ relationship: 'parent',
+ state: 'active',
+ totalLikes: 0,
+ totalComments: 0,
+ totalRatings: 0
+ });
+ return post;
+ });
+}
+function insertVariables(text, variables) {
+ return text === null || text === void 0 ? void 0 : text.replace(/\(([^)]+)\)/, (substring, variableName) => {
+ if (variableName in variables) {
+ return `${variables[variableName]}`;
+ }
+ return substring;
+ });
+}
+function startStep(playPost, step, variables, io) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const play = playPost.play;
+ const now = +new Date();
+ const timeout = now + (0, parse_duration_1.default)(step.timeout);
+ const move = {
+ status: 'started',
+ startedAt: now,
+ timeout,
+ playId: playPost.id
+ };
+ const movePost = yield createChild({
+ type: 'post',
+ mediaTypes: '',
+ title: insertVariables(step.title, variables),
+ text: insertVariables(step.text, variables),
+ move
+ }, playPost);
+ const newPlay = Object.assign(Object.assign({}, play), { status: 'started', stepId: step.id, moveId: movePost.id, variables: variables });
+ yield updatePost(playPost.id, { play: newPlay });
+ scheduleMoveTimeout(movePost, timeout, io);
+ io.in(playPost.id).emit(EVENTS.incoming.updated, { play: newPlay });
+ });
+}
+function moveTimeout(movePost, io) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const move = movePost.move;
+ const newMove = Object.assign(Object.assign({}, move), { status: 'ended' });
+ updatePost(movePost.id, {
+ move: newMove
+ });
+ if (move.playId) {
+ const playPost = yield getPost(move.playId);
+ nextStep(playPost, io);
+ }
+ });
+}
+function nextStep(playPost, io) {
+ return __awaiter(this, void 0, void 0, function* () {
+ var _a;
+ const play = playPost.play;
+ if (play.status !== 'started') {
+ return;
+ }
+ const transition = getTransition(play.game.steps, play.stepId, play.variables, play.playerIds);
+ if (transition === null || transition === void 0 ? void 0 : transition.next) {
+ console.log('next', transition.next, transition.variables);
+ yield startStep(playPost, transition.next, transition.variables, io);
+ }
+ else {
+ const newPlay = {
+ game: play.game,
+ gameId: play.gameId,
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: (_a = transition === null || transition === void 0 ? void 0 : transition.variables) !== null && _a !== void 0 ? _a : play.variables
+ };
+ // await createChild({
+ // type: 'post',
+ // mediaTypes: '',
+ // text: 'Play ended!',
+ // }, playPost)
+ yield updatePost(playPost.id, { play: newPlay });
+ io.in(playPost.id).emit(EVENTS.incoming.updated, { play: newPlay });
+ }
+ });
+}
+function scheduleMoveTimeout(post, timeout, io) {
+ node_schedule_1.default.scheduleJob(timeout, () => __awaiter(this, void 0, void 0, function* () {
+ const currentPost = yield getPost(post.id);
+ if (!(0, lodash_1.isEqual)(currentPost.move, post.move)) {
+ // The state of the move has changed, this job is outdated.
+ return;
+ }
+ moveTimeout(currentPost, io);
+ }));
+}
+const EVENTS = {
+ outgoing: {
+ updateGame: 'outgoing-update-play-game',
+ start: 'outgoing-start-play',
+ stop: 'outgoing-stop-play',
+ skip: 'outgoing-skip-move',
+ pause: 'outgoing-pause-move',
+ },
+ incoming: {
+ updated: 'incoming-play-updated'
+ }
+};
+function initializePlayServerTasks(io) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const moves = yield Post.findAll({
+ where: {
+ state: 'active',
+ 'move': { [Op.not]: null }
+ }
+ });
+ for (const post of moves) {
+ const move = post.move;
+ if (move.status !== 'started') {
+ continue;
+ }
+ console.log(move.timeout, +new Date());
+ if (move.timeout < +new Date()) {
+ moveTimeout(post, io);
+ }
+ else {
+ scheduleMoveTimeout(post, move.timeout, io);
+ }
+ }
+ });
+}
+exports.initializePlayServerTasks = initializePlayServerTasks;
+function registerPlayServerEvents(socket, io) {
+ return __awaiter(this, void 0, void 0, function* () {
+ socket.on(EVENTS.outgoing.updateGame, (_a) => __awaiter(this, [_a], void 0, function* ({ id, game }) {
+ const post = yield getPost(id);
+ const newPlay = Object.assign(Object.assign({}, post.play), { game });
+ yield updatePost(id, {
+ play: newPlay
+ });
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay });
+ }));
+ socket.on(EVENTS.outgoing.start, (_b) => __awaiter(this, [_b], void 0, function* ({ id }) {
+ const playPost = yield Post.findOne({
+ where: {
+ id
+ }
+ });
+ const play = playPost.play;
+ if (play.status === 'started') {
+ return;
+ }
+ const step = getFirstMove(play.game.steps[0], play.variables, play.playerIds);
+ if (!step) {
+ throw new Error('No step.');
+ }
+ yield startStep(playPost, step.step, step.variables, io);
+ }));
+ socket.on(EVENTS.outgoing.skip, (_c) => __awaiter(this, [_c], void 0, function* ({ id }) {
+ const post = yield Post.findOne({
+ where: {
+ id
+ }
+ });
+ const play = post.play;
+ const transition = getTransition(play.game.steps, play.step.id, play.variables, play.playerIds);
+ const newPlay = (transition === null || transition === void 0 ? void 0 : transition.next) ? Object.assign(Object.assign({}, play), { step: transition.next, variables: transition.variables }) : {
+ game: play.game,
+ gameId: play.gameId,
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: {}
+ };
+ yield Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ });
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay });
+ }));
+ socket.on(EVENTS.outgoing.pause, (_d) => __awaiter(this, [_d], void 0, function* ({ id }) {
+ const post = yield Post.findOne({
+ where: {
+ id
+ }
+ });
+ const newPlay = Object.assign(Object.assign({}, post.play), { status: 'paused' });
+ yield Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ });
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay });
+ }));
+ socket.on(EVENTS.outgoing.stop, (_e) => __awaiter(this, [_e], void 0, function* ({ id }) {
+ const post = yield Post.findOne({
+ where: {
+ id
+ }
+ });
+ const newPlay = Object.assign(Object.assign({}, post.play), { status: 'stopped' });
+ yield Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ });
+ io.in(id).emit(EVENTS.incoming.updated, { play: newPlay });
+ }));
+ });
+}
+exports.registerPlayServerEvents = registerPlayServerEvents;
diff --git a/PlayServer.ts b/PlayServer.ts
new file mode 100644
index 0000000..8489326
--- /dev/null
+++ b/PlayServer.ts
@@ -0,0 +1,517 @@
+import { isEqual, omit } from 'lodash'
+import schedule from 'node-schedule'
+import parseDuration from 'parse-duration'
+import sequelize from 'sequelize'
+import { Server, Socket } from 'socket.io'
+
+const { Post, Link } = require('./models')
+const { createPost } = require('./Helpers')
+
+const { Op } = sequelize
+
+const POST_TYPE = [
+ 'post',
+ 'play',
+ 'comment',
+ 'bead',
+ 'poll-answer',
+ 'card-face',
+ 'gbg-room-comment',
+ 'url-block',
+ 'image-block',
+ 'audio-block',
+] as const
+
+type PostType = (typeof POST_TYPE)[number]
+
+type Post = {
+ id: number
+ type: PostType
+ mediaTypes: string
+ title: string
+ text: string
+ createdAt: string
+ updatedAt: string
+ totalComments: number
+ totalLikes: number
+ totalRatings: number
+ totalReposts: number
+ totalLinks: number
+ creatorId: number
+ game?: Game
+ play?: Play
+ move?: Move
+}
+
+type Step = {
+ id: string
+} & (
+ | {
+ type: 'move'
+ title: string
+ text: string
+ timeout: string
+ }
+ | {
+ type: 'game'
+ gameId: number
+ }
+ | {
+ type: 'rounds'
+ name: string
+ amount: string
+ steps: Step[]
+ }
+ | {
+ type: 'turns'
+ name: string
+ steps: Step[]
+ }
+ )
+
+type MoveStep = Extract
+
+type Game = {
+ steps: Step[]
+}
+
+type Move = (
+ | { status: 'skipped' | 'ended' }
+ | { status: 'paused'; elapsedTime: number }
+ | {
+ status: 'started'
+ startedAt: number
+ timeout: number
+ }
+) & { playId?: number }
+
+type PlayVariables = Record
+
+export type Play = {
+ game: Game
+ gameId: number
+ playerIds: number[]
+ variables: PlayVariables
+} & (
+ | { status: 'waiting' | 'stopped' | 'ended' }
+ | { status: 'started'; stepId: string; moveId: number }
+ )
+
+async function getPost(id: number): Promise {
+ const post = await Post.findOne({
+ where: {
+ state: 'active',
+ id
+ }
+ })
+
+ if (!post) {
+ throw new Error(`Post ${id} not found`)
+ }
+
+ return post;
+}
+
+async function updatePost(id: number, data: Partial) {
+ return await Post.update(data, { where: { id } })
+}
+
+const getFirstMove = (
+ step: Step | undefined,
+ variables: PlayVariables,
+ playerIds: number[]
+): undefined | { step: MoveStep; variables: PlayVariables } => {
+ if (!step) {
+ return undefined
+ }
+ switch (step.type) {
+ case 'game':
+ throw new Error('TODO')
+ case 'move':
+ return { step, variables }
+ case 'rounds': {
+ const firstStep = getFirstMove(step.steps[0], variables, playerIds)
+ if (!firstStep) {
+ return undefined
+ }
+ return {
+ step: firstStep.step,
+ variables: {
+ ...firstStep.variables,
+ [step.name]: firstStep.variables[step.name] ?? 1,
+ },
+ }
+ }
+ case 'turns': {
+ const firstStep = getFirstMove(step.steps[0], variables, playerIds)
+ if (!firstStep) {
+ return undefined
+ }
+ return {
+ step: firstStep.step,
+ variables: {
+ ...firstStep.variables,
+ [step.name]: firstStep.variables[step.name] ?? playerIds[0],
+ },
+ }
+ }
+ default: {
+ const exhaustivenessCheck: never = step
+ throw exhaustivenessCheck
+ }
+ }
+}
+
+const getTransition = (
+ steps: Step[],
+ stepId: string,
+ variables: PlayVariables,
+ playerIds: number[]
+): undefined | { current: MoveStep; next?: MoveStep; variables: PlayVariables } => {
+ let current: MoveStep | undefined
+ let currentVariables = variables
+ for (let i = 0; i < steps.length; i++) {
+ const step = steps[i]
+
+ if (current) {
+ const nextStep = getFirstMove(step, currentVariables, playerIds)
+ if (nextStep) {
+ return { current, next: nextStep.step, variables: nextStep.variables }
+ }
+ } else {
+ switch (step.type) {
+ case 'game':
+ throw new Error('TODO')
+ case 'move':
+ if (step.id === stepId) {
+ current = step
+ }
+ break
+ case 'rounds':
+ case 'turns': {
+ const result = getTransition(step.steps, stepId, currentVariables, playerIds)
+ if (!result) {
+ break
+ }
+
+ if (result.next) {
+ return result
+ }
+
+ let next;
+ if (step.type === 'rounds') {
+ const round = currentVariables[step.name] as number
+ next = round < +step.amount ? round + 1 : undefined
+ } else {
+ const playerId = currentVariables[step.name] as number
+ const playerIndex = playerIds.indexOf(playerId)
+ next = playerIds[playerIndex + 1]
+ }
+ if (next) {
+ const firstStep = getFirstMove(
+ step,
+ {
+ ...result.variables,
+ [step.name]: next,
+ },
+ playerIds
+ )
+ if (firstStep) {
+ return {
+ current: result.current,
+ next: firstStep.step,
+ variables: firstStep.variables,
+ }
+ }
+ }
+
+ current = result.current
+ currentVariables = omit(result.variables, step.name)
+ break
+ }
+ default: {
+ const exhaustivenessCheck: never = step
+ throw exhaustivenessCheck
+ }
+ }
+ }
+ }
+
+ if (current) {
+ return { current, variables: currentVariables }
+ }
+
+ return undefined
+}
+
+
+async function createChild(data: any, parent: Post) {
+ const { post } = await createPost(data, [], parent.creatorId)
+ await Link.create({
+ creatorId: parent.creatorId,
+ itemAId: parent.id,
+ itemAType: parent.type,
+ itemBId: post.id,
+ itemBType: post.type,
+ relationship: 'parent',
+ state: 'active',
+ totalLikes: 0,
+ totalComments: 0,
+ totalRatings: 0
+ })
+ return post
+}
+
+function insertVariables(text: string, variables: PlayVariables) {
+ return text?.replace(/\(([^)]+)\)/, (substring, variableName) => {
+ if (variableName in variables) {
+ return `${variables[variableName]}`
+ }
+ return substring
+ })
+}
+
+async function startStep(playPost: Post, step: MoveStep, variables: PlayVariables, io: Server) {
+ const play = playPost.play!
+ const now = +new Date();
+ const timeout = now + parseDuration(step.timeout)!
+
+ const move: Move = {
+ status: 'started',
+ startedAt: now,
+ timeout,
+ playId: playPost.id
+ }
+ const movePost = await createChild({
+ type: 'post',
+ mediaTypes: '',
+ title: insertVariables(step.title, variables),
+ text: insertVariables(step.text, variables),
+ move
+ }, playPost)
+
+ const newPlay: Play = {
+ ...play,
+ status: 'started',
+ stepId: step.id,
+ moveId: movePost.id,
+ variables: variables,
+ }
+
+ await updatePost(playPost.id, { play: newPlay })
+
+ scheduleMoveTimeout(movePost, timeout, io)
+ io.in(playPost.id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+}
+
+async function moveTimeout(movePost: Post, io: Server) {
+ const move = movePost.move!
+ const newMove: Move = {
+ ...move,
+ status: 'ended'
+ }
+ updatePost(movePost.id, {
+ move: newMove
+ })
+ if (move.playId) {
+ const playPost = await getPost(move.playId);
+ nextStep(playPost, io)
+ }
+}
+
+async function nextStep(playPost: Post, io: Server) {
+ const play = playPost.play!;
+ if (play.status !== 'started') {
+ return;
+ }
+ const transition = getTransition(
+ play.game.steps,
+ play.stepId,
+ play.variables,
+ play.playerIds
+ )
+
+ if (transition?.next) {
+ console.log('next', transition.next, transition.variables)
+ await startStep(playPost, transition.next, transition.variables, io)
+ } else {
+ const newPlay: Play = {
+ game: play.game,
+ gameId: play.gameId,
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: transition?.variables ?? play.variables
+ }
+ // await createChild({
+ // type: 'post',
+ // mediaTypes: '',
+ // text: 'Play ended!',
+ // }, playPost)
+ await updatePost(playPost.id, { play: newPlay })
+ io.in(playPost.id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ }
+}
+
+function scheduleMoveTimeout(post: Post, timeout: number, io: Server) {
+ schedule.scheduleJob(timeout, async () => {
+ const currentPost = await getPost(post.id)
+ if (!isEqual(currentPost.move, post.move)) {
+ // The state of the move has changed, this job is outdated.
+ return
+ }
+ moveTimeout(currentPost, io)
+ })
+}
+
+const EVENTS = {
+ outgoing: {
+ updateGame: 'outgoing-update-play-game',
+ start: 'outgoing-start-play',
+ stop: 'outgoing-stop-play',
+
+ skip: 'outgoing-skip-move',
+ pause: 'outgoing-pause-move',
+ },
+ incoming: {
+ updated: 'incoming-play-updated'
+ }
+}
+
+export async function initializePlayServerTasks(io: Server) {
+ const moves: Post[] = await Post.findAll({
+ where: {
+ state: 'active',
+ 'move': { [Op.not]: null }
+ }
+ })
+
+ for (const post of moves) {
+ const move = post.move!
+ if (move.status !== 'started') {
+ continue
+ }
+
+ console.log(move.timeout, +new Date())
+ if (move.timeout < +new Date()) {
+ moveTimeout(post, io)
+ } else {
+ scheduleMoveTimeout(post, move.timeout, io)
+ }
+ }
+}
+
+export async function registerPlayServerEvents(socket: Socket, io: Server) {
+ socket.on(EVENTS.outgoing.updateGame, async ({ id, game }: { id: number, game: Game }) => {
+ const post = await getPost(id)
+
+ const newPlay: Play = {
+ ...post.play!,
+ game
+ }
+
+ await updatePost(id, {
+ play: newPlay
+ })
+
+ io.in(id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+
+ socket.on(EVENTS.outgoing.start, async ({ id }: { id: number }) => {
+ const playPost: Post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+ const play = playPost.play!
+
+ if (play.status === 'started') {
+ return
+ }
+
+ const step = getFirstMove(play.game.steps[0], play.variables, play.playerIds);
+ if (!step) {
+ throw new Error('No step.')
+ }
+
+ await startStep(playPost, step.step, step.variables, io)
+ })
+
+ socket.on(EVENTS.outgoing.skip, async ({ id }: { id: number }) => {
+ const post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+ const play = post.play;
+ const transition = getTransition(
+ play.game.steps,
+ play.step.id,
+ play.variables,
+ play.playerIds
+ )
+ const newPlay = transition?.next ? {
+ ...play,
+ step: transition.next,
+ variables: transition.variables
+ } : {
+ game: play.game,
+ gameId: play.gameId,
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: {}
+ }
+
+ await Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ })
+ io.in(id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+
+ socket.on(EVENTS.outgoing.pause, async ({ id }: { id: number }) => {
+ const post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+
+ const newPlay = {
+ ...post.play,
+ status: 'paused'
+ }
+
+ await Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ })
+ io.in(id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+
+ socket.on(EVENTS.outgoing.stop, async ({ id }: { id: number }) => {
+ const post = await Post.findOne({
+ where: {
+ id
+ }
+ })
+
+ const newPlay = {
+ ...post.play,
+ status: 'stopped'
+ }
+
+ await Post.update({
+ play: newPlay
+ }, {
+ where: {
+ id
+ }
+ })
+ io.in(id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ })
+}
diff --git a/ScheduledTasks.js b/ScheduledTasks.js
index b540178..52a2330 100644
--- a/ScheduledTasks.js
+++ b/ScheduledTasks.js
@@ -5,9 +5,10 @@ const { Op } = sequelize
const { User, Event, UserEvent, Notification, Post, Weave, GlassBeadGame } = require('./models')
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
-const { initializePlayTasks } = require('./Play')
+const { io } = require('./Socket')
+const { initializePlayServerTasks } = require('./PlayServer')
-function scheduleEventNotification(data) {
+async function scheduleEventNotification(data) {
const {
type,
postId,
@@ -342,7 +343,7 @@ async function initializeScheduledTasks() {
}
})
- initializePlayTasks()
+ await initializePlayServerTasks(io)
}
module.exports = {
diff --git a/Socket.js b/Socket.js
index f90db7f..fa86e79 100644
--- a/Socket.js
+++ b/Socket.js
@@ -6,7 +6,7 @@ const socketServer = require('http').createServer()
const socketIo = require('socket.io')
const io = socketIo(socketServer, { cors: { origin: whitelist } })
// socket.io cheatsheet: https://socket.io/docs/v3/emit-cheatsheet/
-const { registerPlaySocketEvents } = require('./Play')
+const { registerPlayServerEvents } = require('./PlayServer')
const sockets = []
const rooms = [] // space, chat, post, or game + id: `space-58`
@@ -202,7 +202,7 @@ io.on('connection', (socket) => {
// }
// })
- registerPlaySocketEvents(socket, io)
+ registerPlayServerEvents(socket, io)
})
socketServer.listen(5001)
diff --git a/migration-updates/add-move-to-posts.js b/migration-updates/add-move-to-posts.js
new file mode 100644
index 0000000..6c9e860
--- /dev/null
+++ b/migration-updates/add-move-to-posts.js
@@ -0,0 +1,24 @@
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) => {
+ return Promise.all([
+ queryInterface.addColumn(
+ 'Posts',
+ 'move',
+ {
+ type: Sequelize.DataTypes.JSON,
+ },
+ { transaction: t }
+ ),
+ ])
+ })
+ },
+
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.sequelize.transaction((t) => {
+ return Promise.all([
+ queryInterface.removeColumn('Posts', 'move', { transaction: t }),
+ ])
+ })
+ },
+}
diff --git a/models/Post.js b/models/Post.js
index 8c37f02..9d76627 100644
--- a/models/Post.js
+++ b/models/Post.js
@@ -27,6 +27,7 @@ module.exports = (sequelize, DataTypes) => {
totalGlassBeadGames: DataTypes.INTEGER,
game: DataTypes.JSON,
play: DataTypes.JSON,
+ move: DataTypes.JSON,
lastActivity: DataTypes.DATE,
},
{}
diff --git a/package-lock.json b/package-lock.json
index f1b5f72..8e00e5a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,6 +39,7 @@
"devDependencies": {
"@types/lodash": "^4.17.0",
"@types/node-schedule": "^2.1.6",
+ "concurrently": "^8.2.2",
"eslint": "^8.56.0",
"eslint-config-airbnb-typescript-prettier": "^5.0.0",
"typescript": "^5.4.2"
@@ -3290,6 +3291,66 @@
"typedarray": "^0.0.6"
}
},
+ "node_modules/concurrently": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
+ "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "date-fns": "^2.30.0",
+ "lodash": "^4.17.21",
+ "rxjs": "^7.8.1",
+ "shell-quote": "^1.8.1",
+ "spawn-command": "0.0.2",
+ "supports-color": "^8.1.1",
+ "tree-kill": "^1.2.2",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": "^14.13.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
@@ -3437,6 +3498,22 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
+ "node_modules/date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -7334,6 +7411,15 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
"node_modules/safe-array-concat": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz",
@@ -7713,6 +7799,15 @@
"node": ">=8"
}
},
+ "node_modules/shell-quote": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
+ "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -7813,6 +7908,12 @@
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
"integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
},
+ "node_modules/spawn-command": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
+ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
+ "dev": true
+ },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -8200,6 +8301,15 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@@ -11402,6 +11512,49 @@
"typedarray": "^0.0.6"
}
},
+ "concurrently": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
+ "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.2",
+ "date-fns": "^2.30.0",
+ "lodash": "^4.17.21",
+ "rxjs": "^7.8.1",
+ "shell-quote": "^1.8.1",
+ "spawn-command": "0.0.2",
+ "supports-color": "^8.1.1",
+ "tree-kill": "^1.2.2",
+ "yargs": "^17.7.2"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "requires": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ }
+ }
+ }
+ },
"config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
@@ -11519,6 +11672,15 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
+ "date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.21.0"
+ }
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -14401,6 +14563,15 @@
"queue-microtask": "^1.2.2"
}
},
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
"safe-array-concat": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz",
@@ -14674,6 +14845,12 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
+ "shell-quote": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
+ "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
+ "dev": true
+ },
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -14747,6 +14924,12 @@
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
"integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
},
+ "spawn-command": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
+ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
+ "dev": true
+ },
"split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
@@ -15058,6 +15241,12 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
+ "tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true
+ },
"tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
diff --git a/package.json b/package.json
index 154a846..400c664 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"deps": "docker-compose up --build mysql",
"migrate": "sequelize db:migrate",
"seed": "sequelize db:seed:all",
- "dev": "nodemon Server.js",
+ "dev": "concurrently -n node,ts 'nodemon Server.js' 'tsc --watch'",
"start": "node Server.js"
},
"keywords": [],
@@ -44,6 +44,7 @@
"devDependencies": {
"@types/lodash": "^4.17.0",
"@types/node-schedule": "^2.1.6",
+ "concurrently": "^8.2.2",
"eslint": "^8.56.0",
"eslint-config-airbnb-typescript-prettier": "^5.0.0",
"typescript": "^5.4.2"
diff --git a/routes/Post.js b/routes/Post.js
index f74d981..a126031 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -758,6 +758,38 @@ router.get('/post-comments', async (req, res) => {
.catch((error) => res.status(500).json({ message: 'Error', error }))
})
+router.get('/post-children', async (req, res) => {
+ const accountId = req.user ? req.user.id : null
+ const { postId, limit, offset } = req.query;
+
+ const links = await Link.findAll({
+ offset: +offset,
+ limit: +limit,
+ order: [['createdAt', 'DESC']],
+ attributes: ['state'],
+ include: [
+ {
+ model: Post,
+ attributes: fullPostAttributes,
+ include: [
+ {
+ model: User,
+ as: 'Creator',
+ attributes: ['id', 'handle', 'name', 'flagImagePath', 'coverImagePath'],
+ }
+ ]
+ }
+ ],
+ where: {
+ state: 'active',
+ relationship: 'parent',
+ itemAType: 'post',
+ itemAId: postId,
+ }
+ })
+ res.status(200).json({ children: links.map(link => link.Post) })
+})
+
router.get('/post-indirect-spaces', async (req, res) => {
const { postId } = req.query
const post = await Post.findOne({
@@ -1734,7 +1766,7 @@ router.post('/update-post', authenticateToken, async (req, res) => {
if (!post) res.status(401).json({ message: 'Unauthorized' })
else {
const toUpdate = {};
- for (const key of ['mediaTypes', 'title', 'text', 'searchableText', 'game', 'play']) {
+ for (const key of ['mediaTypes', 'title', 'text', 'searchableText', 'game', 'play', 'move']) {
if (key in req.body) {
toUpdate[key] = req.body[key]
}
@@ -3126,6 +3158,22 @@ router.post('/delete-comment', authenticateToken, async (req, res) => {
{ state: 'deleted' },
{ where: { id: postId, creatorId: accountId } }
)
+ await Link.update(
+ { state: 'deleted', },
+ {
+ where: {
+ state: 'active',
+ [Op.or]: [
+ {
+ itemAId: postId
+ },
+ {
+ itemBId: postId
+ }
+ ]
+ }
+ }
+ );
// get links & root post for tally updates
const rootLink = await Link.findOne({
where: { itemBId: postId, itemBType: 'comment', relationship: 'root' },
diff --git a/tsconfig.json b/tsconfig.json
index c40c209..c946444 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -9,7 +9,6 @@
"allowJs": false,
"outDir": "./",
"noEmitOnError": false,
- "incremental": true,
},
"include": [
"./**/*"
From 9df24d469406a143a763d22bf346f8e2c4a6752e Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Wed, 13 Mar 2024 21:30:54 +0100
Subject: [PATCH 09/21] even more progress
---
PlayServer.js => GameServer.js | 235 ++++++++++---------
PlayServer.ts => GameServer.ts | 308 +++++++++++++++----------
Helpers.js | 21 +-
ScheduledTasks.js | 6 +-
Socket.js | 4 +-
migration-updates/add-play-to-posts.js | 24 --
models/Post.js | 4 +-
routes/Post.js | 2 +-
8 files changed, 331 insertions(+), 273 deletions(-)
rename PlayServer.js => GameServer.js (58%)
rename PlayServer.ts => GameServer.ts (59%)
delete mode 100644 migration-updates/add-play-to-posts.js
diff --git a/PlayServer.js b/GameServer.js
similarity index 58%
rename from PlayServer.js
rename to GameServer.js
index 4a03588..638f2f4 100644
--- a/PlayServer.js
+++ b/GameServer.js
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.registerPlayServerEvents = exports.initializePlayServerTasks = void 0;
+exports.registerGameServerEvents = exports.initializeGameServerTasks = void 0;
const lodash_1 = require("lodash");
const node_schedule_1 = __importDefault(require("node-schedule"));
const parse_duration_1 = __importDefault(require("parse-duration"));
@@ -22,7 +22,6 @@ const { createPost } = require('./Helpers');
const { Op } = sequelize_1.default;
const POST_TYPE = [
'post',
- 'play',
'comment',
'bead',
'poll-answer',
@@ -178,16 +177,18 @@ function insertVariables(text, variables) {
return substring;
});
}
-function startStep(playPost, step, variables, io) {
+function startStep(gamePost, step, variables, io) {
return __awaiter(this, void 0, void 0, function* () {
- const play = playPost.play;
+ const game = gamePost.game;
+ const play = game.play;
const now = +new Date();
const timeout = now + (0, parse_duration_1.default)(step.timeout);
const move = {
status: 'started',
+ elapsedTime: 0,
startedAt: now,
timeout,
- playId: playPost.id
+ gameId: gamePost.id
};
const movePost = yield createChild({
type: 'post',
@@ -195,79 +196,80 @@ function startStep(playPost, step, variables, io) {
title: insertVariables(step.title, variables),
text: insertVariables(step.text, variables),
move
- }, playPost);
- const newPlay = Object.assign(Object.assign({}, play), { status: 'started', stepId: step.id, moveId: movePost.id, variables: variables });
- yield updatePost(playPost.id, { play: newPlay });
- scheduleMoveTimeout(movePost, timeout, io);
- io.in(playPost.id).emit(EVENTS.incoming.updated, { play: newPlay });
+ }, gamePost);
+ const newGame = Object.assign(Object.assign({}, game), { play: Object.assign(Object.assign({}, play), { status: 'started', step, moveId: movePost.id, variables: variables }) });
+ yield updatePost(gamePost.id, { game: newGame });
+ scheduleMoveTimeout(movePost.id, move, timeout, io);
+ console.log('hu');
+ io.in(gamePost.id).emit(EVENTS.incoming.updated, { game: newGame });
});
}
-function moveTimeout(movePost, io) {
+function moveTimeout(id, move, io) {
return __awaiter(this, void 0, void 0, function* () {
- const move = movePost.move;
+ console.log('move timeout!');
const newMove = Object.assign(Object.assign({}, move), { status: 'ended' });
- updatePost(movePost.id, {
+ yield updatePost(id, {
move: newMove
});
- if (move.playId) {
- const playPost = yield getPost(move.playId);
- nextStep(playPost, io);
+ if (move.gameId) {
+ const gamePost = yield getPost(move.gameId);
+ console.log(gamePost);
+ nextStep(gamePost, io);
}
});
}
-function nextStep(playPost, io) {
+function nextStep(gamePost, io) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
- const play = playPost.play;
+ const game = gamePost.game;
+ const play = game.play;
if (play.status !== 'started') {
return;
}
- const transition = getTransition(play.game.steps, play.stepId, play.variables, play.playerIds);
+ const transition = getTransition(game.steps, play.step.id, play.variables, play.playerIds);
if (transition === null || transition === void 0 ? void 0 : transition.next) {
- console.log('next', transition.next, transition.variables);
- yield startStep(playPost, transition.next, transition.variables, io);
+ yield startStep(gamePost, transition.next, transition.variables, io);
}
else {
- const newPlay = {
- game: play.game,
- gameId: play.gameId,
- playerIds: play.playerIds,
- status: 'ended',
- variables: (_a = transition === null || transition === void 0 ? void 0 : transition.variables) !== null && _a !== void 0 ? _a : play.variables
- };
+ const newGame = Object.assign(Object.assign({}, game), { play: {
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: (_a = transition === null || transition === void 0 ? void 0 : transition.variables) !== null && _a !== void 0 ? _a : play.variables
+ } });
// await createChild({
// type: 'post',
// mediaTypes: '',
// text: 'Play ended!',
// }, playPost)
- yield updatePost(playPost.id, { play: newPlay });
- io.in(playPost.id).emit(EVENTS.incoming.updated, { play: newPlay });
+ yield updatePost(gamePost.id, { game: newGame });
+ io.in(gamePost.id).emit(EVENTS.incoming.updated, { game: newGame });
}
});
}
-function scheduleMoveTimeout(post, timeout, io) {
+function scheduleMoveTimeout(id, move, timeout, io) {
node_schedule_1.default.scheduleJob(timeout, () => __awaiter(this, void 0, void 0, function* () {
- const currentPost = yield getPost(post.id);
- if (!(0, lodash_1.isEqual)(currentPost.move, post.move)) {
+ const currentPost = yield getPost(id);
+ if (!(0, lodash_1.isEqual)(currentPost.move, move)) {
+ console.log(currentPost.move, move);
// The state of the move has changed, this job is outdated.
return;
}
- moveTimeout(currentPost, io);
+ moveTimeout(id, move, io);
}));
}
const EVENTS = {
outgoing: {
- updateGame: 'outgoing-update-play-game',
- start: 'outgoing-start-play',
- stop: 'outgoing-stop-play',
- skip: 'outgoing-skip-move',
- pause: 'outgoing-pause-move',
+ update: 'gs:outgoing-update-game',
+ start: 'gs:outgoing-start-game',
+ stop: 'gs:outgoing-stop-game',
+ skip: 'gs:outgoing-skip-move',
+ pause: 'gs:outgoing-pause-move',
},
incoming: {
- updated: 'incoming-play-updated'
+ updated: 'gs:incoming-updated-game'
}
};
-function initializePlayServerTasks(io) {
+function initializeGameServerTasks(io) {
return __awaiter(this, void 0, void 0, function* () {
const moves = yield Post.findAll({
where: {
@@ -280,99 +282,118 @@ function initializePlayServerTasks(io) {
if (move.status !== 'started') {
continue;
}
- console.log(move.timeout, +new Date());
if (move.timeout < +new Date()) {
- moveTimeout(post, io);
+ moveTimeout(post.id, move, io);
}
else {
- scheduleMoveTimeout(post, move.timeout, io);
+ scheduleMoveTimeout(post.id, move, move.timeout, io);
}
}
});
}
-exports.initializePlayServerTasks = initializePlayServerTasks;
-function registerPlayServerEvents(socket, io) {
+exports.initializeGameServerTasks = initializeGameServerTasks;
+function registerGameServerEvents(socket, io) {
return __awaiter(this, void 0, void 0, function* () {
- socket.on(EVENTS.outgoing.updateGame, (_a) => __awaiter(this, [_a], void 0, function* ({ id, game }) {
- const post = yield getPost(id);
- const newPlay = Object.assign(Object.assign({}, post.play), { game });
+ socket.on(EVENTS.outgoing.update, (_a) => __awaiter(this, [_a], void 0, function* ({ id, game }) {
yield updatePost(id, {
- play: newPlay
+ game
});
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay });
+ io.in(id).emit(EVENTS.incoming.updated, { game });
}));
socket.on(EVENTS.outgoing.start, (_b) => __awaiter(this, [_b], void 0, function* ({ id }) {
- const playPost = yield Post.findOne({
+ const gamePost = yield Post.findOne({
where: {
id
}
});
- const play = playPost.play;
+ const game = gamePost.game;
+ const play = game.play;
if (play.status === 'started') {
return;
}
- const step = getFirstMove(play.game.steps[0], play.variables, play.playerIds);
- if (!step) {
- throw new Error('No step.');
+ if (play.status === 'paused') {
+ const newGame = Object.assign(Object.assign({}, game), { play: Object.assign(Object.assign({}, play), { status: 'started' }) });
+ yield updatePost(id, {
+ game: newGame
+ });
+ const movePost = yield getPost(play.moveId);
+ const move = movePost.move;
+ if (move.status === 'paused') {
+ const now = +new Date();
+ const timeout = now + (0, parse_duration_1.default)(play.step.timeout) - move.elapsedTime;
+ const newMove = Object.assign(Object.assign({}, move), { status: 'started', elapsedTime: move.elapsedTime, startedAt: now, timeout });
+ yield updatePost(play.moveId, {
+ move: newMove
+ });
+ scheduleMoveTimeout(play.moveId, newMove, timeout, io);
+ }
+ io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
+ }
+ else {
+ const step = getFirstMove(game.steps[0], play.variables, play.playerIds);
+ if (!step) {
+ throw new Error('No step.');
+ }
+ yield startStep(gamePost, step.step, step.variables, io);
}
- yield startStep(playPost, step.step, step.variables, io);
}));
socket.on(EVENTS.outgoing.skip, (_c) => __awaiter(this, [_c], void 0, function* ({ id }) {
- const post = yield Post.findOne({
- where: {
- id
- }
- });
- const play = post.play;
- const transition = getTransition(play.game.steps, play.step.id, play.variables, play.playerIds);
- const newPlay = (transition === null || transition === void 0 ? void 0 : transition.next) ? Object.assign(Object.assign({}, play), { step: transition.next, variables: transition.variables }) : {
- game: play.game,
- gameId: play.gameId,
- playerIds: play.playerIds,
- status: 'ended',
- variables: {}
- };
- yield Post.update({
- play: newPlay
- }, {
- where: {
- id
- }
+ const post = yield getPost(id);
+ const game = post.game;
+ const play = game.play;
+ if (play.status !== 'started') {
+ return;
+ }
+ const transition = getTransition(game.steps, play.step.id, play.variables, play.playerIds);
+ const newGame = Object.assign(Object.assign({}, game), { play: (transition === null || transition === void 0 ? void 0 : transition.next) ? Object.assign(Object.assign({}, play), { step: transition.next, variables: transition.variables }) : {
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: {}
+ } });
+ yield updatePost(id, {
+ game: newGame
});
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay });
+ io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
}));
socket.on(EVENTS.outgoing.pause, (_d) => __awaiter(this, [_d], void 0, function* ({ id }) {
- const post = yield Post.findOne({
- where: {
- id
- }
- });
- const newPlay = Object.assign(Object.assign({}, post.play), { status: 'paused' });
- yield Post.update({
- play: newPlay
- }, {
- where: {
- id
- }
- });
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay });
+ const post = yield getPost(id);
+ const game = post.game;
+ const play = game.play;
+ if (play.status !== 'started') {
+ return;
+ }
+ const newGame = Object.assign(Object.assign({}, game), { play: Object.assign(Object.assign({}, play), { status: 'paused' }) });
+ yield updatePost(id, { game: newGame });
+ const movePost = yield getPost(play.moveId);
+ const move = movePost.move;
+ if (move.status === 'started') {
+ const now = +new Date();
+ const newMove = Object.assign(Object.assign({}, move), { status: 'paused', elapsedTime: move.elapsedTime + now - move.startedAt, remainingTime: move.timeout - now });
+ yield updatePost(play.moveId, {
+ move: newMove
+ });
+ }
+ io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
}));
socket.on(EVENTS.outgoing.stop, (_e) => __awaiter(this, [_e], void 0, function* ({ id }) {
- const post = yield Post.findOne({
- where: {
- id
- }
- });
- const newPlay = Object.assign(Object.assign({}, post.play), { status: 'stopped' });
- yield Post.update({
- play: newPlay
- }, {
- where: {
- id
- }
- });
- io.in(id).emit(EVENTS.incoming.updated, { play: newPlay });
+ const post = yield getPost(id);
+ const game = post.game;
+ const play = game.play;
+ if (play.status !== 'started' && play.status !== 'paused') {
+ return;
+ }
+ const newGame = Object.assign(Object.assign({}, game), { play: Object.assign(Object.assign({}, play), { status: 'stopped' }) });
+ yield updatePost(id, { game: newGame });
+ const movePost = yield getPost(play.moveId);
+ const move = movePost.move;
+ if (move.status === 'started' || move.status === 'paused') {
+ const newMove = Object.assign(Object.assign({}, move), { status: 'stopped' });
+ yield updatePost(play.moveId, {
+ move: newMove
+ });
+ }
+ io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
}));
});
}
-exports.registerPlayServerEvents = registerPlayServerEvents;
+exports.registerGameServerEvents = registerGameServerEvents;
diff --git a/PlayServer.ts b/GameServer.ts
similarity index 59%
rename from PlayServer.ts
rename to GameServer.ts
index 8489326..0cd295d 100644
--- a/PlayServer.ts
+++ b/GameServer.ts
@@ -11,7 +11,6 @@ const { Op } = sequelize
const POST_TYPE = [
'post',
- 'play',
'comment',
'bead',
'poll-answer',
@@ -39,7 +38,6 @@ type Post = {
totalLinks: number
creatorId: number
game?: Game
- play?: Play
move?: Move
}
@@ -73,28 +71,28 @@ type MoveStep = Extract
type Game = {
steps: Step[]
+ play: Play
}
type Move = (
- | { status: 'skipped' | 'ended' }
- | { status: 'paused'; elapsedTime: number }
+ | { status: 'skipped' | 'ended' | 'stopped' }
+ | { status: 'paused'; elapsedTime: number; remainingTime: number }
| {
status: 'started'
+ elapsedTime: number
startedAt: number
timeout: number
}
-) & { playId?: number }
+) & { gameId?: number }
type PlayVariables = Record
export type Play = {
- game: Game
- gameId: number
playerIds: number[]
variables: PlayVariables
} & (
| { status: 'waiting' | 'stopped' | 'ended' }
- | { status: 'started'; stepId: string; moveId: number }
+ | { status: 'started' | 'paused'; step: MoveStep; moveId: number }
)
async function getPost(id: number): Promise {
@@ -271,16 +269,18 @@ function insertVariables(text: string, variables: PlayVariables) {
})
}
-async function startStep(playPost: Post, step: MoveStep, variables: PlayVariables, io: Server) {
- const play = playPost.play!
+async function startStep(gamePost: Post, step: MoveStep, variables: PlayVariables, io: Server) {
+ const game = gamePost.game!
+ const play = game.play
const now = +new Date();
const timeout = now + parseDuration(step.timeout)!
const move: Move = {
status: 'started',
+ elapsedTime: 0,
startedAt: now,
timeout,
- playId: playPost.id
+ gameId: gamePost.id
}
const movePost = await createChild({
type: 'post',
@@ -288,96 +288,103 @@ async function startStep(playPost: Post, step: MoveStep, variables: PlayVariable
title: insertVariables(step.title, variables),
text: insertVariables(step.text, variables),
move
- }, playPost)
+ }, gamePost)
- const newPlay: Play = {
- ...play,
- status: 'started',
- stepId: step.id,
- moveId: movePost.id,
- variables: variables,
+ const newGame: Game = {
+ ...game,
+ play: {
+ ...play,
+ status: 'started',
+ step,
+ moveId: movePost.id,
+ variables: variables,
+ }
}
- await updatePost(playPost.id, { play: newPlay })
+ await updatePost(gamePost.id, { game: newGame })
- scheduleMoveTimeout(movePost, timeout, io)
- io.in(playPost.id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ scheduleMoveTimeout(movePost.id, move, timeout, io)
+ console.log('hu')
+ io.in(gamePost.id as any).emit(EVENTS.incoming.updated, { game: newGame })
}
-async function moveTimeout(movePost: Post, io: Server) {
- const move = movePost.move!
+async function moveTimeout(id: number, move: Move, io: Server) {
+ console.log('move timeout!')
const newMove: Move = {
...move,
status: 'ended'
}
- updatePost(movePost.id, {
+ await updatePost(id, {
move: newMove
})
- if (move.playId) {
- const playPost = await getPost(move.playId);
- nextStep(playPost, io)
+ if (move.gameId) {
+ const gamePost = await getPost(move.gameId);
+ console.log(gamePost)
+ nextStep(gamePost, io)
}
}
-async function nextStep(playPost: Post, io: Server) {
- const play = playPost.play!;
+async function nextStep(gamePost: Post, io: Server) {
+ const game = gamePost.game!;
+ const play = game.play!;
if (play.status !== 'started') {
return;
}
const transition = getTransition(
- play.game.steps,
- play.stepId,
+ game.steps,
+ play.step.id,
play.variables,
play.playerIds
)
if (transition?.next) {
- console.log('next', transition.next, transition.variables)
- await startStep(playPost, transition.next, transition.variables, io)
+ await startStep(gamePost, transition.next, transition.variables, io)
} else {
- const newPlay: Play = {
- game: play.game,
- gameId: play.gameId,
- playerIds: play.playerIds,
- status: 'ended',
- variables: transition?.variables ?? play.variables
+ const newGame: Game = {
+ ...game,
+ play: {
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: transition?.variables ?? play.variables
+ }
}
// await createChild({
// type: 'post',
// mediaTypes: '',
// text: 'Play ended!',
// }, playPost)
- await updatePost(playPost.id, { play: newPlay })
- io.in(playPost.id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ await updatePost(gamePost.id, { game: newGame })
+ io.in(gamePost.id as any).emit(EVENTS.incoming.updated, { game: newGame })
}
}
-function scheduleMoveTimeout(post: Post, timeout: number, io: Server) {
+function scheduleMoveTimeout(id: number, move: Move, timeout: number, io: Server) {
schedule.scheduleJob(timeout, async () => {
- const currentPost = await getPost(post.id)
- if (!isEqual(currentPost.move, post.move)) {
+ const currentPost = await getPost(id)
+ if (!isEqual(currentPost.move, move)) {
+ console.log(currentPost.move, move)
// The state of the move has changed, this job is outdated.
return
}
- moveTimeout(currentPost, io)
+ moveTimeout(id, move, io)
})
}
const EVENTS = {
outgoing: {
- updateGame: 'outgoing-update-play-game',
- start: 'outgoing-start-play',
- stop: 'outgoing-stop-play',
+ update: 'gs:outgoing-update-game',
+ start: 'gs:outgoing-start-game',
+ stop: 'gs:outgoing-stop-game',
- skip: 'outgoing-skip-move',
- pause: 'outgoing-pause-move',
+ skip: 'gs:outgoing-skip-move',
+ pause: 'gs:outgoing-pause-move',
},
incoming: {
- updated: 'incoming-play-updated'
+ updated: 'gs:incoming-updated-game'
}
}
-export async function initializePlayServerTasks(io: Server) {
+export async function initializeGameServerTasks(io: Server) {
const moves: Post[] = await Post.findAll({
where: {
state: 'active',
@@ -391,127 +398,174 @@ export async function initializePlayServerTasks(io: Server) {
continue
}
- console.log(move.timeout, +new Date())
if (move.timeout < +new Date()) {
- moveTimeout(post, io)
+ moveTimeout(post.id, move, io)
} else {
- scheduleMoveTimeout(post, move.timeout, io)
+ scheduleMoveTimeout(post.id, move, move.timeout, io)
}
}
}
-export async function registerPlayServerEvents(socket: Socket, io: Server) {
- socket.on(EVENTS.outgoing.updateGame, async ({ id, game }: { id: number, game: Game }) => {
- const post = await getPost(id)
-
- const newPlay: Play = {
- ...post.play!,
- game
- }
-
+export async function registerGameServerEvents(socket: Socket, io: Server) {
+ socket.on(EVENTS.outgoing.update, async ({ id, game }: { id: number, game: Game }) => {
await updatePost(id, {
- play: newPlay
+ game
})
- io.in(id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ io.in(id as any).emit(EVENTS.incoming.updated, { game })
})
socket.on(EVENTS.outgoing.start, async ({ id }: { id: number }) => {
- const playPost: Post = await Post.findOne({
+ const gamePost: Post = await Post.findOne({
where: {
id
}
})
- const play = playPost.play!
+ const game = gamePost.game!
+ const play = game.play;
if (play.status === 'started') {
return
}
- const step = getFirstMove(play.game.steps[0], play.variables, play.playerIds);
- if (!step) {
- throw new Error('No step.')
- }
+ if (play.status === 'paused') {
+ const newGame: Game = {
+ ...game,
+ play: {
+ ...play,
+ status: 'started'
+ }
+ }
+ await updatePost(id, {
+ game: newGame
+ })
+ const movePost = await getPost(play.moveId);
+ const move = movePost.move!;
+ if (move.status === 'paused') {
+ const now = + new Date();
+ const timeout = now + parseDuration(play.step.timeout)! - move.elapsedTime;
+ const newMove: Move = {
+ ...move,
+ status: 'started',
+ elapsedTime: move.elapsedTime,
+ startedAt: now,
+ timeout,
+ }
+ await updatePost(play.moveId, {
+ move: newMove
+ })
+ scheduleMoveTimeout(play.moveId, newMove, timeout, io)
+ }
+ io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
+ } else {
+ const step = getFirstMove(game.steps[0], play.variables, play.playerIds);
+ if (!step) {
+ throw new Error('No step.')
+ }
- await startStep(playPost, step.step, step.variables, io)
+ await startStep(gamePost, step.step, step.variables, io)
+ }
})
socket.on(EVENTS.outgoing.skip, async ({ id }: { id: number }) => {
- const post = await Post.findOne({
- where: {
- id
- }
- })
- const play = post.play;
+ const post = await getPost(id);
+ const game = post.game!;
+ const play = game.play;
+ if (play.status !== 'started') {
+ return
+ }
const transition = getTransition(
- play.game.steps,
+ game.steps,
play.step.id,
play.variables,
play.playerIds
)
- const newPlay = transition?.next ? {
- ...play,
- step: transition.next,
- variables: transition.variables
- } : {
- game: play.game,
- gameId: play.gameId,
- playerIds: play.playerIds,
- status: 'ended',
- variables: {}
+ const newGame: Game = {
+ ...game,
+ play: transition?.next ? {
+ ...play,
+ step: transition.next,
+ variables: transition.variables
+ } : {
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: {}
+ }
}
- await Post.update({
- play: newPlay
- }, {
- where: {
- id
- }
+ await updatePost(id, {
+ game: newGame
})
- io.in(id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
})
socket.on(EVENTS.outgoing.pause, async ({ id }: { id: number }) => {
- const post = await Post.findOne({
- where: {
- id
- }
- })
+ const post = await getPost(id)
+ const game = post.game!
+ const play = game.play;
- const newPlay = {
- ...post.play,
- status: 'paused'
+ if (play.status !== 'started') {
+ return;
}
- await Post.update({
- play: newPlay
- }, {
- where: {
- id
+
+ const newGame: Game = {
+ ...game,
+ play: {
+ ...play,
+ status: 'paused'
}
- })
- io.in(id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ }
+ await updatePost(id, { game: newGame })
+
+ const movePost = await getPost(play.moveId);
+ const move = movePost.move!;
+ if (move.status === 'started') {
+ const now = + new Date();
+ const newMove: Move = {
+ ...move,
+ status: 'paused',
+ elapsedTime: move.elapsedTime + now - move.startedAt,
+ remainingTime: move.timeout - now
+ }
+ await updatePost(play.moveId, {
+ move: newMove
+ })
+ }
+
+ io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
})
socket.on(EVENTS.outgoing.stop, async ({ id }: { id: number }) => {
- const post = await Post.findOne({
- where: {
- id
- }
- })
+ const post = await getPost(id)
+ const game = post.game!
+ const play = game.play;
- const newPlay = {
- ...post.play,
- status: 'stopped'
+ if (play.status !== 'started' && play.status !== 'paused') {
+ return
}
- await Post.update({
- play: newPlay
- }, {
- where: {
- id
+ const newGame: Game = {
+ ...game,
+ play: {
+ ...play,
+ status: 'stopped'
}
- })
- io.in(id as any).emit(EVENTS.incoming.updated, { play: newPlay })
+ }
+ await updatePost(id, { game: newGame })
+
+ const movePost = await getPost(play.moveId);
+ const move = movePost.move!;
+ if (move.status === 'started' || move.status === 'paused') {
+ const newMove: Move = {
+ ...move,
+ status: 'stopped',
+ }
+ await updatePost(play.moveId, {
+ move: newMove
+ })
+ }
+
+ io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
})
}
diff --git a/Helpers.js b/Helpers.js
index cbce5e3..8e2a04f 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -966,7 +966,6 @@ const fullPostAttributes = [
'totalRatings',
'totalLinks',
'game',
- 'play',
'move'
]
@@ -988,7 +987,6 @@ function findFullPostAttributes(model, accountId) {
'totalRatings',
'totalLinks',
'game',
- 'play',
'move',
// accountLike('post', model, accountId),
// accountComment('post', model, accountId),
@@ -1150,13 +1148,24 @@ function findPostInclude(accountId) {
},
{
model: Link,
- as: 'Plays',
+ as: 'Original',
+ required: false,
+ where: { relationship: 'spawn', state: 'active' },
+ include: {
+ model: Post,
+ as: 'Parent',
+ attributes: ['id', 'title', 'game', 'state']
+ }
+ },
+ {
+ model: Link,
+ as: 'Spawns',
separate: true,
- where: { relationship: 'play', state: 'active' },
+ where: { relationship: 'spawn', state: 'active' },
order: [['index', 'ASC']],
include: {
model: Post,
- attributes: ['id', 'title', 'play', 'state']
+ attributes: ['id', 'title', 'game', 'state']
}
},
{
@@ -1716,7 +1725,6 @@ function createPost(data, files, accountId) {
poll,
glassBeadGame,
game,
- play,
move,
card,
color,
@@ -1740,7 +1748,6 @@ function createPost(data, files, accountId) {
watermark: !!watermark,
lastActivity: new Date(),
game,
- play,
move,
})
diff --git a/ScheduledTasks.js b/ScheduledTasks.js
index 52a2330..a8aa667 100644
--- a/ScheduledTasks.js
+++ b/ScheduledTasks.js
@@ -5,8 +5,8 @@ const { Op } = sequelize
const { User, Event, UserEvent, Notification, Post, Weave, GlassBeadGame } = require('./models')
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
-const { io } = require('./Socket')
-const { initializePlayServerTasks } = require('./PlayServer')
+const io = require('./Socket')
+const { initializeGameServerTasks } = require('./GameServer')
async function scheduleEventNotification(data) {
const {
@@ -343,7 +343,7 @@ async function initializeScheduledTasks() {
}
})
- await initializePlayServerTasks(io)
+ await initializeGameServerTasks(io)
}
module.exports = {
diff --git a/Socket.js b/Socket.js
index fa86e79..6d7b37d 100644
--- a/Socket.js
+++ b/Socket.js
@@ -6,7 +6,7 @@ const socketServer = require('http').createServer()
const socketIo = require('socket.io')
const io = socketIo(socketServer, { cors: { origin: whitelist } })
// socket.io cheatsheet: https://socket.io/docs/v3/emit-cheatsheet/
-const { registerPlayServerEvents } = require('./PlayServer')
+const { registerGameServerEvents } = require('./GameServer')
const sockets = []
const rooms = [] // space, chat, post, or game + id: `space-58`
@@ -202,7 +202,7 @@ io.on('connection', (socket) => {
// }
// })
- registerPlayServerEvents(socket, io)
+ registerGameServerEvents(socket, io)
})
socketServer.listen(5001)
diff --git a/migration-updates/add-play-to-posts.js b/migration-updates/add-play-to-posts.js
deleted file mode 100644
index e07a28a..0000000
--- a/migration-updates/add-play-to-posts.js
+++ /dev/null
@@ -1,24 +0,0 @@
-module.exports = {
- up: (queryInterface, Sequelize) => {
- return queryInterface.sequelize.transaction((t) => {
- return Promise.all([
- queryInterface.addColumn(
- 'Posts',
- 'play',
- {
- type: Sequelize.DataTypes.JSON,
- },
- { transaction: t }
- ),
- ])
- })
- },
-
- down: (queryInterface, Sequelize) => {
- return queryInterface.sequelize.transaction((t) => {
- return Promise.all([
- queryInterface.removeColumn('Posts', 'play', { transaction: t }),
- ])
- })
- },
-}
diff --git a/models/Post.js b/models/Post.js
index 9d76627..de24265 100644
--- a/models/Post.js
+++ b/models/Post.js
@@ -26,7 +26,6 @@ module.exports = (sequelize, DataTypes) => {
totalRatings: DataTypes.INTEGER,
totalGlassBeadGames: DataTypes.INTEGER,
game: DataTypes.JSON,
- play: DataTypes.JSON,
move: DataTypes.JSON,
lastActivity: DataTypes.DATE,
},
@@ -51,7 +50,8 @@ module.exports = (sequelize, DataTypes) => {
Post.hasMany(models.Link, { as: 'UrlBlocks', foreignKey: 'itemAId' })
Post.hasMany(models.Link, { as: 'ImageBlocks', foreignKey: 'itemAId' })
Post.hasMany(models.Link, { as: 'AudioBlocks', foreignKey: 'itemAId' })
- Post.hasMany(models.Link, { as: 'Plays', foreignKey: 'itemAId' })
+ Post.hasOne(models.Link, { as: 'Original', foreignKey: 'itemBId' })
+ Post.hasMany(models.Link, { as: 'Spawns', foreignKey: 'itemAId' })
Post.hasOne(models.Link, { as: 'MediaLink', foreignKey: 'itemAId' })
// used for post map (todo: rethink...)
Post.hasMany(models.Link, { as: 'OutgoingPostLinks', foreignKey: 'itemAId' })
diff --git a/routes/Post.js b/routes/Post.js
index a126031..83e8979 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -1766,7 +1766,7 @@ router.post('/update-post', authenticateToken, async (req, res) => {
if (!post) res.status(401).json({ message: 'Unauthorized' })
else {
const toUpdate = {};
- for (const key of ['mediaTypes', 'title', 'text', 'searchableText', 'game', 'play', 'move']) {
+ for (const key of ['mediaTypes', 'title', 'text', 'searchableText', 'game', 'move']) {
if (key in req.body) {
toUpdate[key] = req.body[key]
}
From a4b01a4f7dd44d2741f0df712363878bd41ae5e8 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Mon, 25 Mar 2024 14:09:51 +0100
Subject: [PATCH 10/21] remixes
---
GameServer.js | 207 +++++++++++++++++++++----------------
GameServer.ts | 274 ++++++++++++++++++++++++-------------------------
Helpers.js | 58 ++++++++++-
models/Post.js | 4 +-
routes/Post.js | 21 +++-
5 files changed, 322 insertions(+), 242 deletions(-)
diff --git a/GameServer.js b/GameServer.js
index 638f2f4..2c6afb7 100644
--- a/GameServer.js
+++ b/GameServer.js
@@ -51,33 +51,24 @@ function updatePost(id, data) {
});
}
const getFirstMove = (step, variables, playerIds) => {
- var _a, _b;
+ var _a;
if (!step) {
return undefined;
}
switch (step.type) {
- case 'game':
- throw new Error('TODO');
case 'move':
return { step, variables };
- case 'rounds': {
+ case 'sequence': {
const firstStep = getFirstMove(step.steps[0], variables, playerIds);
if (!firstStep) {
return undefined;
}
- return {
- step: firstStep.step,
- variables: Object.assign(Object.assign({}, firstStep.variables), { [step.name]: (_a = firstStep.variables[step.name]) !== null && _a !== void 0 ? _a : 1 }),
- };
- }
- case 'turns': {
- const firstStep = getFirstMove(step.steps[0], variables, playerIds);
- if (!firstStep) {
- return undefined;
+ if (!step.repeat) {
+ return firstStep;
}
return {
step: firstStep.step,
- variables: Object.assign(Object.assign({}, firstStep.variables), { [step.name]: (_b = firstStep.variables[step.name]) !== null && _b !== void 0 ? _b : playerIds[0] }),
+ variables: Object.assign(Object.assign({}, firstStep.variables), { [step.name]: (_a = firstStep.variables[step.name]) !== null && _a !== void 0 ? _a : (step.repeat.type === 'rounds' ? 1 : playerIds[0]) }),
};
}
default: {
@@ -99,15 +90,12 @@ const getTransition = (steps, stepId, variables, playerIds) => {
}
else {
switch (step.type) {
- case 'game':
- throw new Error('TODO');
case 'move':
if (step.id === stepId) {
current = step;
}
break;
- case 'rounds':
- case 'turns': {
+ case 'sequence': {
const result = getTransition(step.steps, stepId, currentVariables, playerIds);
if (!result) {
break;
@@ -115,24 +103,26 @@ const getTransition = (steps, stepId, variables, playerIds) => {
if (result.next) {
return result;
}
- let next;
- if (step.type === 'rounds') {
- const round = currentVariables[step.name];
- next = round < +step.amount ? round + 1 : undefined;
- }
- else {
- const playerId = currentVariables[step.name];
- const playerIndex = playerIds.indexOf(playerId);
- next = playerIds[playerIndex + 1];
- }
- if (next) {
- const firstStep = getFirstMove(step, Object.assign(Object.assign({}, result.variables), { [step.name]: next }), playerIds);
- if (firstStep) {
- return {
- current: result.current,
- next: firstStep.step,
- variables: firstStep.variables,
- };
+ if (step.repeat) {
+ let nextValue;
+ if (step.repeat.type === 'rounds') {
+ const round = currentVariables[step.name];
+ nextValue = round < +step.repeat.amount ? round + 1 : undefined;
+ }
+ else {
+ const playerId = currentVariables[step.name];
+ const playerIndex = playerIds.indexOf(playerId);
+ nextValue = playerIds[playerIndex + 1];
+ }
+ if (nextValue) {
+ const firstStep = getFirstMove(step, Object.assign(Object.assign({}, result.variables), { [step.name]: nextValue }), playerIds);
+ if (firstStep) {
+ return {
+ current: result.current,
+ next: firstStep.step,
+ variables: firstStep.variables,
+ };
+ }
}
}
current = result.current;
@@ -170,7 +160,7 @@ function createChild(data, parent) {
});
}
function insertVariables(text, variables) {
- return text === null || text === void 0 ? void 0 : text.replace(/\(([^)]+)\)/, (substring, variableName) => {
+ return text === null || text === void 0 ? void 0 : text.replace(/\[([^\]]+)\]/, (substring, variableName) => {
if (variableName in variables) {
return `${variables[variableName]}`;
}
@@ -197,10 +187,15 @@ function startStep(gamePost, step, variables, io) {
text: insertVariables(step.text, variables),
move
}, gamePost);
- const newGame = Object.assign(Object.assign({}, game), { play: Object.assign(Object.assign({}, play), { status: 'started', step, moveId: movePost.id, variables: variables }) });
+ const newGame = Object.assign(Object.assign({}, game), { play: {
+ status: 'started',
+ step,
+ moveId: movePost.id,
+ variables: variables,
+ playerIds: play.playerIds,
+ } });
yield updatePost(gamePost.id, { game: newGame });
scheduleMoveTimeout(movePost.id, move, timeout, io);
- console.log('hu');
io.in(gamePost.id).emit(EVENTS.incoming.updated, { game: newGame });
});
}
@@ -223,12 +218,24 @@ function nextStep(gamePost, io) {
var _a;
const game = gamePost.game;
const play = game.play;
- if (play.status !== 'started') {
+ if (play.status !== 'started' && play.status !== 'paused') {
return;
}
const transition = getTransition(game.steps, play.step.id, play.variables, play.playerIds);
if (transition === null || transition === void 0 ? void 0 : transition.next) {
- yield startStep(gamePost, transition.next, transition.variables, io);
+ if (play.status === 'started') {
+ yield startStep(gamePost, transition.next, transition.variables, io);
+ }
+ else {
+ const newGame = Object.assign(Object.assign({}, game), { play: {
+ status: 'paused',
+ step: transition.next,
+ variables: transition.variables,
+ playerIds: play.playerIds,
+ } });
+ yield updatePost(gamePost.id, { game: newGame });
+ io.in(gamePost.id).emit(EVENTS.incoming.updated, { game: newGame });
+ }
}
else {
const newGame = Object.assign(Object.assign({}, game), { play: {
@@ -236,11 +243,6 @@ function nextStep(gamePost, io) {
status: 'ended',
variables: (_a = transition === null || transition === void 0 ? void 0 : transition.variables) !== null && _a !== void 0 ? _a : play.variables
} });
- // await createChild({
- // type: 'post',
- // mediaTypes: '',
- // text: 'Play ended!',
- // }, playPost)
yield updatePost(gamePost.id, { game: newGame });
io.in(gamePost.id).emit(EVENTS.incoming.updated, { game: newGame });
}
@@ -259,14 +261,14 @@ function scheduleMoveTimeout(id, move, timeout, io) {
}
const EVENTS = {
outgoing: {
- update: 'gs:outgoing-update-game',
- start: 'gs:outgoing-start-game',
- stop: 'gs:outgoing-stop-game',
- skip: 'gs:outgoing-skip-move',
- pause: 'gs:outgoing-pause-move',
+ update: 'gs:outgoing-update',
+ start: 'gs:outgoing-start',
+ stop: 'gs:outgoing-stop',
+ skip: 'gs:outgoing-skip',
+ pause: 'gs:outgoing-pause',
},
incoming: {
- updated: 'gs:incoming-updated-game'
+ updated: 'gs:incoming-updated'
}
};
function initializeGameServerTasks(io) {
@@ -308,29 +310,43 @@ function registerGameServerEvents(socket, io) {
});
const game = gamePost.game;
const play = game.play;
+ console.log(play);
if (play.status === 'started') {
return;
}
if (play.status === 'paused') {
- const newGame = Object.assign(Object.assign({}, game), { play: Object.assign(Object.assign({}, play), { status: 'started' }) });
- yield updatePost(id, {
- game: newGame
- });
- const movePost = yield getPost(play.moveId);
- const move = movePost.move;
- if (move.status === 'paused') {
- const now = +new Date();
- const timeout = now + (0, parse_duration_1.default)(play.step.timeout) - move.elapsedTime;
- const newMove = Object.assign(Object.assign({}, move), { status: 'started', elapsedTime: move.elapsedTime, startedAt: now, timeout });
- yield updatePost(play.moveId, {
- move: newMove
+ const moveId = play.moveId;
+ if (!moveId) {
+ console.log('startstep');
+ yield startStep(gamePost, play.step, play.variables, io);
+ }
+ else {
+ const newGame = Object.assign(Object.assign({}, game), { play: {
+ status: 'started',
+ moveId,
+ playerIds: play.playerIds,
+ step: play.step,
+ variables: play.variables,
+ } });
+ yield updatePost(id, {
+ game: newGame
});
- scheduleMoveTimeout(play.moveId, newMove, timeout, io);
+ const movePost = yield getPost(moveId);
+ const move = movePost.move;
+ if (move.status === 'paused') {
+ const now = +new Date();
+ const timeout = now + (0, parse_duration_1.default)(play.step.timeout) - move.elapsedTime;
+ const newMove = Object.assign(Object.assign({}, move), { status: 'started', elapsedTime: move.elapsedTime, startedAt: now, timeout });
+ yield updatePost(moveId, {
+ move: newMove
+ });
+ scheduleMoveTimeout(moveId, newMove, timeout, io);
+ }
+ io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
}
- io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
}
else {
- const step = getFirstMove(game.steps[0], play.variables, play.playerIds);
+ const step = getFirstMove(game.steps[0], {}, play.playerIds);
if (!step) {
throw new Error('No step.');
}
@@ -338,22 +354,19 @@ function registerGameServerEvents(socket, io) {
}
}));
socket.on(EVENTS.outgoing.skip, (_c) => __awaiter(this, [_c], void 0, function* ({ id }) {
- const post = yield getPost(id);
- const game = post.game;
- const play = game.play;
- if (play.status !== 'started') {
- return;
+ const gamePost = yield getPost(id);
+ const play = gamePost.game.play;
+ if ((play.status === 'started' || play.status === 'paused') && play.moveId) {
+ const movePost = yield getPost(play.moveId);
+ const move = movePost.move;
+ if (move.status === 'started' || move.status === 'paused') {
+ const newMove = Object.assign(Object.assign({}, move), { status: 'skipped' });
+ yield updatePost(play.moveId, {
+ move: newMove
+ });
+ }
}
- const transition = getTransition(game.steps, play.step.id, play.variables, play.playerIds);
- const newGame = Object.assign(Object.assign({}, game), { play: (transition === null || transition === void 0 ? void 0 : transition.next) ? Object.assign(Object.assign({}, play), { step: transition.next, variables: transition.variables }) : {
- playerIds: play.playerIds,
- status: 'ended',
- variables: {}
- } });
- yield updatePost(id, {
- game: newGame
- });
- io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
+ yield nextStep(gamePost, io);
}));
socket.on(EVENTS.outgoing.pause, (_d) => __awaiter(this, [_d], void 0, function* ({ id }) {
const post = yield getPost(id);
@@ -362,7 +375,13 @@ function registerGameServerEvents(socket, io) {
if (play.status !== 'started') {
return;
}
- const newGame = Object.assign(Object.assign({}, game), { play: Object.assign(Object.assign({}, play), { status: 'paused' }) });
+ const newGame = Object.assign(Object.assign({}, game), { play: {
+ status: 'paused',
+ step: play.step,
+ variables: play.variables,
+ moveId: play.moveId,
+ playerIds: play.playerIds,
+ } });
yield updatePost(id, { game: newGame });
const movePost = yield getPost(play.moveId);
const move = movePost.move;
@@ -382,15 +401,21 @@ function registerGameServerEvents(socket, io) {
if (play.status !== 'started' && play.status !== 'paused') {
return;
}
- const newGame = Object.assign(Object.assign({}, game), { play: Object.assign(Object.assign({}, play), { status: 'stopped' }) });
+ const newGame = Object.assign(Object.assign({}, game), { play: {
+ status: 'stopped',
+ variables: play.variables,
+ playerIds: play.playerIds,
+ } });
yield updatePost(id, { game: newGame });
- const movePost = yield getPost(play.moveId);
- const move = movePost.move;
- if (move.status === 'started' || move.status === 'paused') {
- const newMove = Object.assign(Object.assign({}, move), { status: 'stopped' });
- yield updatePost(play.moveId, {
- move: newMove
- });
+ if (play.moveId) {
+ const movePost = yield getPost(play.moveId);
+ const move = movePost.move;
+ if (move.status === 'started' || move.status === 'paused') {
+ const newMove = Object.assign(Object.assign({}, move), { status: 'stopped' });
+ yield updatePost(play.moveId, {
+ move: newMove
+ });
+ }
}
io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
}));
diff --git a/GameServer.ts b/GameServer.ts
index 0cd295d..9e1032d 100644
--- a/GameServer.ts
+++ b/GameServer.ts
@@ -41,28 +41,20 @@ type Post = {
move?: Move
}
-type Step = {
+export type Step = {
id: string
+ name: string
+ originalStep?: { gameId: number; stepId: string }
} & (
| {
type: 'move'
- title: string
+ title?: string
text: string
timeout: string
}
| {
- type: 'game'
- gameId: number
- }
- | {
- type: 'rounds'
- name: string
- amount: string
- steps: Step[]
- }
- | {
- type: 'turns'
- name: string
+ type: 'sequence'
+ repeat?: { type: 'rounds'; amount: number } | { type: 'turns' }
steps: Step[]
}
)
@@ -92,6 +84,7 @@ export type Play = {
variables: PlayVariables
} & (
| { status: 'waiting' | 'stopped' | 'ended' }
+ | { status: 'paused'; step: MoveStep; moveId?: number }
| { status: 'started' | 'paused'; step: MoveStep; moveId: number }
)
@@ -123,33 +116,22 @@ const getFirstMove = (
return undefined
}
switch (step.type) {
- case 'game':
- throw new Error('TODO')
case 'move':
return { step, variables }
- case 'rounds': {
+ case 'sequence': {
const firstStep = getFirstMove(step.steps[0], variables, playerIds)
if (!firstStep) {
return undefined
}
- return {
- step: firstStep.step,
- variables: {
- ...firstStep.variables,
- [step.name]: firstStep.variables[step.name] ?? 1,
- },
- }
- }
- case 'turns': {
- const firstStep = getFirstMove(step.steps[0], variables, playerIds)
- if (!firstStep) {
- return undefined
+ if (!step.repeat) {
+ return firstStep
}
+
return {
step: firstStep.step,
variables: {
...firstStep.variables,
- [step.name]: firstStep.variables[step.name] ?? playerIds[0],
+ [step.name]: firstStep.variables[step.name] ?? (step.repeat.type === 'rounds' ? 1 : playerIds[0]),
},
}
}
@@ -178,15 +160,12 @@ const getTransition = (
}
} else {
switch (step.type) {
- case 'game':
- throw new Error('TODO')
case 'move':
if (step.id === stepId) {
current = step
}
break
- case 'rounds':
- case 'turns': {
+ case 'sequence': {
const result = getTransition(step.steps, stepId, currentVariables, playerIds)
if (!result) {
break
@@ -196,29 +175,31 @@ const getTransition = (
return result
}
- let next;
- if (step.type === 'rounds') {
- const round = currentVariables[step.name] as number
- next = round < +step.amount ? round + 1 : undefined
- } else {
- const playerId = currentVariables[step.name] as number
- const playerIndex = playerIds.indexOf(playerId)
- next = playerIds[playerIndex + 1]
- }
- if (next) {
- const firstStep = getFirstMove(
- step,
- {
- ...result.variables,
- [step.name]: next,
- },
- playerIds
- )
- if (firstStep) {
- return {
- current: result.current,
- next: firstStep.step,
- variables: firstStep.variables,
+ if (step.repeat) {
+ let nextValue;
+ if (step.repeat.type === 'rounds') {
+ const round = currentVariables[step.name] as number
+ nextValue = round < +step.repeat.amount ? round + 1 : undefined
+ } else {
+ const playerId = currentVariables[step.name] as number
+ const playerIndex = playerIds.indexOf(playerId)
+ nextValue = playerIds[playerIndex + 1]
+ }
+ if (nextValue) {
+ const firstStep = getFirstMove(
+ step,
+ {
+ ...result.variables,
+ [step.name]: nextValue,
+ },
+ playerIds
+ )
+ if (firstStep) {
+ return {
+ current: result.current,
+ next: firstStep.step,
+ variables: firstStep.variables,
+ }
}
}
}
@@ -260,8 +241,8 @@ async function createChild(data: any, parent: Post) {
return post
}
-function insertVariables(text: string, variables: PlayVariables) {
- return text?.replace(/\(([^)]+)\)/, (substring, variableName) => {
+function insertVariables(text: string | undefined, variables: PlayVariables) {
+ return text?.replace(/\[([^\]]+)\]/, (substring, variableName) => {
if (variableName in variables) {
return `${variables[variableName]}`
}
@@ -293,18 +274,17 @@ async function startStep(gamePost: Post, step: MoveStep, variables: PlayVariable
const newGame: Game = {
...game,
play: {
- ...play,
status: 'started',
step,
moveId: movePost.id,
variables: variables,
+ playerIds: play.playerIds,
}
}
await updatePost(gamePost.id, { game: newGame })
scheduleMoveTimeout(movePost.id, move, timeout, io)
- console.log('hu')
io.in(gamePost.id as any).emit(EVENTS.incoming.updated, { game: newGame })
}
@@ -327,7 +307,7 @@ async function moveTimeout(id: number, move: Move, io: Server) {
async function nextStep(gamePost: Post, io: Server) {
const game = gamePost.game!;
const play = game.play!;
- if (play.status !== 'started') {
+ if (play.status !== 'started' && play.status !== 'paused') {
return;
}
const transition = getTransition(
@@ -338,7 +318,21 @@ async function nextStep(gamePost: Post, io: Server) {
)
if (transition?.next) {
- await startStep(gamePost, transition.next, transition.variables, io)
+ if (play.status === 'started') {
+ await startStep(gamePost, transition.next, transition.variables, io)
+ } else {
+ const newGame: Game = {
+ ...game,
+ play: {
+ status: 'paused',
+ step: transition.next,
+ variables: transition.variables,
+ playerIds: play.playerIds,
+ }
+ }
+ await updatePost(gamePost.id, { game: newGame })
+ io.in(gamePost.id as any).emit(EVENTS.incoming.updated, { game: newGame })
+ }
} else {
const newGame: Game = {
...game,
@@ -348,11 +342,6 @@ async function nextStep(gamePost: Post, io: Server) {
variables: transition?.variables ?? play.variables
}
}
- // await createChild({
- // type: 'post',
- // mediaTypes: '',
- // text: 'Play ended!',
- // }, playPost)
await updatePost(gamePost.id, { game: newGame })
io.in(gamePost.id as any).emit(EVENTS.incoming.updated, { game: newGame })
}
@@ -372,15 +361,15 @@ function scheduleMoveTimeout(id: number, move: Move, timeout: number, io: Server
const EVENTS = {
outgoing: {
- update: 'gs:outgoing-update-game',
- start: 'gs:outgoing-start-game',
- stop: 'gs:outgoing-stop-game',
+ update: 'gs:outgoing-update',
+ start: 'gs:outgoing-start',
+ stop: 'gs:outgoing-stop',
- skip: 'gs:outgoing-skip-move',
- pause: 'gs:outgoing-pause-move',
+ skip: 'gs:outgoing-skip',
+ pause: 'gs:outgoing-pause',
},
incoming: {
- updated: 'gs:incoming-updated-game'
+ updated: 'gs:incoming-updated'
}
}
@@ -424,41 +413,52 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
const game = gamePost.game!
const play = game.play;
+ console.log(play)
+
if (play.status === 'started') {
return
}
if (play.status === 'paused') {
- const newGame: Game = {
- ...game,
- play: {
- ...play,
- status: 'started'
- }
- }
- await updatePost(id, {
- game: newGame
- })
- const movePost = await getPost(play.moveId);
- const move = movePost.move!;
- if (move.status === 'paused') {
- const now = + new Date();
- const timeout = now + parseDuration(play.step.timeout)! - move.elapsedTime;
- const newMove: Move = {
- ...move,
- status: 'started',
- elapsedTime: move.elapsedTime,
- startedAt: now,
- timeout,
+ const moveId = play.moveId;
+ if (!moveId) {
+ console.log('startstep')
+ await startStep(gamePost, play.step, play.variables, io)
+ } else {
+ const newGame: Game = {
+ ...game,
+ play: {
+ status: 'started',
+ moveId,
+ playerIds: play.playerIds,
+ step: play.step,
+ variables: play.variables,
+ }
}
- await updatePost(play.moveId, {
- move: newMove
+ await updatePost(id, {
+ game: newGame
})
- scheduleMoveTimeout(play.moveId, newMove, timeout, io)
+ const movePost = await getPost(moveId);
+ const move = movePost.move!;
+ if (move.status === 'paused') {
+ const now = + new Date();
+ const timeout = now + parseDuration(play.step.timeout)! - move.elapsedTime;
+ const newMove: Move = {
+ ...move,
+ status: 'started',
+ elapsedTime: move.elapsedTime,
+ startedAt: now,
+ timeout,
+ }
+ await updatePost(moveId, {
+ move: newMove
+ })
+ scheduleMoveTimeout(moveId, newMove, timeout, io)
+ }
+ io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
}
- io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
} else {
- const step = getFirstMove(game.steps[0], play.variables, play.playerIds);
+ const step = getFirstMove(game.steps[0], {}, play.playerIds);
if (!step) {
throw new Error('No step.')
}
@@ -468,35 +468,22 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
})
socket.on(EVENTS.outgoing.skip, async ({ id }: { id: number }) => {
- const post = await getPost(id);
- const game = post.game!;
- const play = game.play;
- if (play.status !== 'started') {
- return
- }
- const transition = getTransition(
- game.steps,
- play.step.id,
- play.variables,
- play.playerIds
- )
- const newGame: Game = {
- ...game,
- play: transition?.next ? {
- ...play,
- step: transition.next,
- variables: transition.variables
- } : {
- playerIds: play.playerIds,
- status: 'ended',
- variables: {}
+ const gamePost = await getPost(id);
+ const play = gamePost.game!.play
+ if ((play.status === 'started' || play.status === 'paused') && play.moveId) {
+ const movePost = await getPost(play.moveId);
+ const move = movePost.move!;
+ if (move.status === 'started' || move.status === 'paused') {
+ const newMove: Move = {
+ ...move,
+ status: 'skipped',
+ }
+ await updatePost(play.moveId, {
+ move: newMove
+ })
}
}
-
- await updatePost(id, {
- game: newGame
- })
- io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
+ await nextStep(gamePost, io)
})
socket.on(EVENTS.outgoing.pause, async ({ id }: { id: number }) => {
@@ -508,12 +495,14 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
return;
}
-
const newGame: Game = {
...game,
play: {
- ...play,
- status: 'paused'
+ status: 'paused',
+ step: play.step,
+ variables: play.variables,
+ moveId: play.moveId,
+ playerIds: play.playerIds,
}
}
await updatePost(id, { game: newGame })
@@ -548,22 +537,25 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
const newGame: Game = {
...game,
play: {
- ...play,
- status: 'stopped'
+ status: 'stopped',
+ variables: play.variables,
+ playerIds: play.playerIds,
}
}
await updatePost(id, { game: newGame })
- const movePost = await getPost(play.moveId);
- const move = movePost.move!;
- if (move.status === 'started' || move.status === 'paused') {
- const newMove: Move = {
- ...move,
- status: 'stopped',
+ if (play.moveId) {
+ const movePost = await getPost(play.moveId);
+ const move = movePost.move!;
+ if (move.status === 'started' || move.status === 'paused') {
+ const newMove: Move = {
+ ...move,
+ status: 'stopped',
+ }
+ await updatePost(play.moveId, {
+ move: newMove
+ })
}
- await updatePost(play.moveId, {
- move: newMove
- })
}
io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
diff --git a/Helpers.js b/Helpers.js
index 8e2a04f..cab2476 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -39,6 +39,7 @@ var ffmpeg = require('fluent-ffmpeg')
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path
ffmpeg.setFfmpegPath(ffmpegPath)
const sgMail = require('@sendgrid/mail')
+const { uniq } = require('lodash')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
const imageMBLimit = 10
@@ -1148,9 +1149,9 @@ function findPostInclude(accountId) {
},
{
model: Link,
- as: 'Original',
+ as: 'Originals',
required: false,
- where: { relationship: 'spawn', state: 'active' },
+ where: { relationship: 'remix', state: 'active' },
include: {
model: Post,
as: 'Parent',
@@ -1159,9 +1160,9 @@ function findPostInclude(accountId) {
},
{
model: Link,
- as: 'Spawns',
+ as: 'Remixes',
separate: true,
- where: { relationship: 'spawn', state: 'active' },
+ where: { relationship: 'remix', state: 'active' },
order: [['index', 'ASC']],
include: {
model: Post,
@@ -1707,6 +1708,50 @@ function addGBGPlayers(postId, creator, settings) {
})
}
+async function addRemixes(accountId, game, postId) {
+ let originals = []
+ function findOriginals(steps) {
+ for (const step of steps) {
+ if (step.originalStep) {
+ originals.push(step.originalStep.gameId)
+ }
+ if (step.type === 'sequence') {
+ findOriginals(step.steps)
+ }
+ }
+ }
+ findOriginals(game.steps)
+ originals = uniq(originals);
+ const links = await Link.findAll({
+ attributes: ['itemAId'],
+ where: {
+ state: 'active',
+ itemAType: 'post',
+ itemBType: 'post',
+ itemAId: originals,
+ itemBId: postId,
+ relationship: 'remix'
+ }
+ })
+ for (const originalId of originals) {
+ if (links?.some(link => link.itemAId === originalId)) {
+ continue
+ }
+ await Link.create({
+ creatorId: accountId,
+ state: 'active',
+ itemAType: 'post',
+ itemBType: 'post',
+ itemAId: originalId,
+ itemBId: postId,
+ relationship: 'remix',
+ totalLikes: 0,
+ totalComments: 0,
+ totalRatings: 0
+ })
+ }
+}
+
// todo:
// + check notifyMentions is adding the correct notification type
function createPost(data, files, accountId) {
@@ -1751,6 +1796,10 @@ function createPost(data, files, accountId) {
move,
})
+ if (game) {
+ await addRemixes(accountId, game, post.id)
+ }
+
// todo: add the correct notification type
const notifyMentions = mentions?.length
? await new Promise(async (resolve) => {
@@ -2245,6 +2294,7 @@ module.exports = {
accountLink,
uploadFiles,
createPost,
+ addRemixes,
attachComment,
scheduleNextBeadDeadline,
pushNotification,
diff --git a/models/Post.js b/models/Post.js
index de24265..2050873 100644
--- a/models/Post.js
+++ b/models/Post.js
@@ -50,8 +50,8 @@ module.exports = (sequelize, DataTypes) => {
Post.hasMany(models.Link, { as: 'UrlBlocks', foreignKey: 'itemAId' })
Post.hasMany(models.Link, { as: 'ImageBlocks', foreignKey: 'itemAId' })
Post.hasMany(models.Link, { as: 'AudioBlocks', foreignKey: 'itemAId' })
- Post.hasOne(models.Link, { as: 'Original', foreignKey: 'itemBId' })
- Post.hasMany(models.Link, { as: 'Spawns', foreignKey: 'itemAId' })
+ Post.hasOne(models.Link, { as: 'Originals', foreignKey: 'itemBId' })
+ Post.hasMany(models.Link, { as: 'Remixes', foreignKey: 'itemAId' })
Post.hasOne(models.Link, { as: 'MediaLink', foreignKey: 'itemAId' })
// used for post map (todo: rethink...)
Post.hasMany(models.Link, { as: 'OutgoingPostLinks', foreignKey: 'itemAId' })
diff --git a/routes/Post.js b/routes/Post.js
index 83e8979..6aa999e 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -34,6 +34,7 @@ const {
pushNotification,
fullPostAttributes,
defaultPostValues,
+ addRemixes,
} = require('../Helpers')
const {
Space,
@@ -363,20 +364,29 @@ router.get('/link-data', authenticateToken, async (req, res) => {
res.status(200).json({ source, link, target })
})
-router.get('/target-from-text', authenticateToken, async (req, res) => {
+router.get('/search', authenticateToken, async (req, res) => {
const accountId = req.user ? req.user.id : null
- const { type, sourceId, text, userId } = req.query
+ const { type, sourceId, search, userId, mediaType } = req.query
const where = {
type: type.toLowerCase(),
state: 'active',
- [Op.or]: [{ text: { [Op.like]: `%${text}%` } }, { title: { [Op.like]: `%${text}%` } }],
+ [Op.or]: [{ text: { [Op.like]: `%${search}%` } }, { title: { [Op.like]: `%${search}%` } }],
}
if (sourceId) where[Op.not] = { id: sourceId }
if (userId) where.creatorId = userId
+ if (mediaType) {
+ if (mediaType === 'game') {
+ where[Op.and] = [{ mediaTypes: { [Op.like]: `%${mediaType}%` } }, { [Op.not]: { mediaTypes: { [Op.like]: `%glass-bead-game%` } } }]
+ }
+ } else {
+ where.mediaTypes = { [Op.like]: `%${mediaType}%` }
+ }
+
const matchingPosts = await Post.findAll({
where,
limit: 10,
- include: findPostInclude(accountId),
+ // TODO: no idea why this fails
+ include: findPostInclude(accountId).filter(include => !['UrlBlocks', 'ImageBlocks', 'AudioBlocks'].includes(include.as)),
})
res.status(200).json(matchingPosts)
})
@@ -1777,6 +1787,9 @@ router.post('/update-post', authenticateToken, async (req, res) => {
{ where: { id, creatorId: accountId } }
)
promises.push(updatePost)
+ if ('game' in req.body) {
+ await addRemixes(accountId, req.body.game, id)
+ }
if ('urls' in req.body) {
const newUrls = req.body.urls;
// update urls
From c14feed43d2a7d6f6371e1c80d0c382a7f69565e Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Tue, 26 Mar 2024 16:14:25 +0100
Subject: [PATCH 11/21] improve flow
---
GameServer.js | 226 ++++++++++++++++++++------------------
GameServer.ts | 293 ++++++++++++++++++++++++++-----------------------
routes/Post.js | 19 +++-
3 files changed, 292 insertions(+), 246 deletions(-)
diff --git a/GameServer.js b/GameServer.js
index 2c6afb7..ae1bc54 100644
--- a/GameServer.js
+++ b/GameServer.js
@@ -34,10 +34,12 @@ const POST_TYPE = [
function getPost(id) {
return __awaiter(this, void 0, void 0, function* () {
const post = yield Post.findOne({
+ raw: true,
+ nest: true,
where: {
state: 'active',
id
- }
+ },
});
if (!post) {
throw new Error(`Post ${id} not found`);
@@ -45,9 +47,10 @@ function getPost(id) {
return post;
});
}
-function updatePost(id, data) {
+function updatePost(post, data) {
return __awaiter(this, void 0, void 0, function* () {
- return yield Post.update(data, { where: { id } });
+ yield Post.update(data, { where: { id: post.id } });
+ return Object.assign(Object.assign({}, post), data);
});
}
const getFirstMove = (step, variables, playerIds) => {
@@ -167,7 +170,7 @@ function insertVariables(text, variables) {
return substring;
});
}
-function startStep(gamePost, step, variables, io) {
+function startNewMove(gamePost, step, variables, io) {
return __awaiter(this, void 0, void 0, function* () {
const game = gamePost.game;
const play = game.play;
@@ -187,33 +190,20 @@ function startStep(gamePost, step, variables, io) {
text: insertVariables(step.text, variables),
move
}, gamePost);
- const newGame = Object.assign(Object.assign({}, game), { play: {
- status: 'started',
- step,
- moveId: movePost.id,
- variables: variables,
- playerIds: play.playerIds,
- } });
- yield updatePost(gamePost.id, { game: newGame });
- scheduleMoveTimeout(movePost.id, move, timeout, io);
- io.in(gamePost.id).emit(EVENTS.incoming.updated, { game: newGame });
- });
-}
-function moveTimeout(id, move, io) {
- return __awaiter(this, void 0, void 0, function* () {
- console.log('move timeout!');
- const newMove = Object.assign(Object.assign({}, move), { status: 'ended' });
- yield updatePost(id, {
- move: newMove
+ scheduleMoveTimeout(movePost, io);
+ const changedGamePost = yield updatePost(gamePost, {
+ game: Object.assign(Object.assign({}, game), { play: {
+ status: 'started',
+ step,
+ moveId: movePost.id,
+ variables: variables,
+ playerIds: play.playerIds,
+ } })
});
- if (move.gameId) {
- const gamePost = yield getPost(move.gameId);
- console.log(gamePost);
- nextStep(gamePost, io);
- }
+ return { changedGamePost, changedMoves: [movePost] };
});
}
-function nextStep(gamePost, io) {
+function nextMove(gamePost, io) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const game = gamePost.game;
@@ -224,39 +214,58 @@ function nextStep(gamePost, io) {
const transition = getTransition(game.steps, play.step.id, play.variables, play.playerIds);
if (transition === null || transition === void 0 ? void 0 : transition.next) {
if (play.status === 'started') {
- yield startStep(gamePost, transition.next, transition.variables, io);
+ return yield startNewMove(gamePost, transition.next, transition.variables, io);
}
else {
- const newGame = Object.assign(Object.assign({}, game), { play: {
- status: 'paused',
- step: transition.next,
- variables: transition.variables,
- playerIds: play.playerIds,
- } });
- yield updatePost(gamePost.id, { game: newGame });
- io.in(gamePost.id).emit(EVENTS.incoming.updated, { game: newGame });
+ const changedGamePost = yield updatePost(gamePost, {
+ game: Object.assign(Object.assign({}, game), { play: {
+ status: 'paused',
+ step: transition.next,
+ variables: transition.variables,
+ playerIds: play.playerIds,
+ } })
+ });
+ return {
+ changedGamePost,
+ };
}
}
else {
- const newGame = Object.assign(Object.assign({}, game), { play: {
- playerIds: play.playerIds,
- status: 'ended',
- variables: (_a = transition === null || transition === void 0 ? void 0 : transition.variables) !== null && _a !== void 0 ? _a : play.variables
- } });
- yield updatePost(gamePost.id, { game: newGame });
- io.in(gamePost.id).emit(EVENTS.incoming.updated, { game: newGame });
+ const changedGamePost = yield updatePost(gamePost, {
+ game: Object.assign(Object.assign({}, game), { play: {
+ playerIds: play.playerIds,
+ status: 'ended',
+ variables: (_a = transition === null || transition === void 0 ? void 0 : transition.variables) !== null && _a !== void 0 ? _a : play.variables
+ } })
+ });
+ return { changedGamePost };
+ }
+ });
+}
+function moveTimeout(movePost, io) {
+ return __awaiter(this, void 0, void 0, function* () {
+ var _a;
+ const move = movePost.move;
+ const newMovePost = yield updatePost(movePost, {
+ move: Object.assign(Object.assign({}, move), { status: 'ended' })
+ });
+ if (move.gameId) {
+ const gamePost = yield getPost(move.gameId);
+ const changes = yield nextMove(gamePost, io);
+ if (changes) {
+ emitChanges(io, Object.assign(Object.assign({}, changes), { changedMoves: [newMovePost, ...(_a = changes.changedMoves) !== null && _a !== void 0 ? _a : []] }));
+ }
}
});
}
-function scheduleMoveTimeout(id, move, timeout, io) {
- node_schedule_1.default.scheduleJob(timeout, () => __awaiter(this, void 0, void 0, function* () {
- const currentPost = yield getPost(id);
- if (!(0, lodash_1.isEqual)(currentPost.move, move)) {
- console.log(currentPost.move, move);
- // The state of the move has changed, this job is outdated.
+function scheduleMoveTimeout(movePost, io) {
+ node_schedule_1.default.scheduleJob(movePost.move.timeout, () => __awaiter(this, void 0, void 0, function* () {
+ const currentMovePost = yield getPost(movePost.id);
+ if (!(0, lodash_1.isEqual)(currentMovePost.move, movePost.move)) {
+ console.log('The state of the move has changed, skipping job.');
return;
}
- moveTimeout(id, move, io);
+ yield moveTimeout(currentMovePost, io);
}));
}
const EVENTS = {
@@ -285,64 +294,69 @@ function initializeGameServerTasks(io) {
continue;
}
if (move.timeout < +new Date()) {
- moveTimeout(post.id, move, io);
+ yield moveTimeout(post, io);
}
else {
- scheduleMoveTimeout(post.id, move, move.timeout, io);
+ scheduleMoveTimeout(post, io);
}
}
});
}
exports.initializeGameServerTasks = initializeGameServerTasks;
+function emitChanges(io, { changedGamePost, changedMoves }) {
+ io.in(changedGamePost.id).emit(EVENTS.incoming.updated, { game: changedGamePost.game, changedChildren: changedMoves });
+}
function registerGameServerEvents(socket, io) {
return __awaiter(this, void 0, void 0, function* () {
socket.on(EVENTS.outgoing.update, (_a) => __awaiter(this, [_a], void 0, function* ({ id, game }) {
- yield updatePost(id, {
+ const post = yield getPost(id);
+ const changedGamePost = yield updatePost(post, {
game
});
- io.in(id).emit(EVENTS.incoming.updated, { game });
+ emitChanges(io, { changedGamePost });
}));
socket.on(EVENTS.outgoing.start, (_b) => __awaiter(this, [_b], void 0, function* ({ id }) {
- const gamePost = yield Post.findOne({
- where: {
- id
- }
- });
+ const gamePost = yield getPost(id);
const game = gamePost.game;
const play = game.play;
- console.log(play);
if (play.status === 'started') {
return;
}
+ let changes;
if (play.status === 'paused') {
const moveId = play.moveId;
if (!moveId) {
- console.log('startstep');
- yield startStep(gamePost, play.step, play.variables, io);
+ changes = yield startNewMove(gamePost, play.step, play.variables, io);
}
else {
- const newGame = Object.assign(Object.assign({}, game), { play: {
- status: 'started',
- moveId,
- playerIds: play.playerIds,
- step: play.step,
- variables: play.variables,
- } });
- yield updatePost(id, {
- game: newGame
+ const changedGamePost = yield updatePost(gamePost, {
+ game: Object.assign(Object.assign({}, game), { play: {
+ status: 'started',
+ moveId,
+ playerIds: play.playerIds,
+ step: play.step,
+ variables: play.variables,
+ } })
});
const movePost = yield getPost(moveId);
const move = movePost.move;
if (move.status === 'paused') {
const now = +new Date();
const timeout = now + (0, parse_duration_1.default)(play.step.timeout) - move.elapsedTime;
- const newMove = Object.assign(Object.assign({}, move), { status: 'started', elapsedTime: move.elapsedTime, startedAt: now, timeout });
- yield updatePost(moveId, {
- move: newMove
+ const newMovePost = yield updatePost(movePost, {
+ move: Object.assign(Object.assign({}, move), { status: 'started', elapsedTime: move.elapsedTime, startedAt: now, timeout })
});
- scheduleMoveTimeout(moveId, newMove, timeout, io);
+ scheduleMoveTimeout(newMovePost, io);
+ changes = {
+ changedGamePost,
+ changedMoves: [newMovePost]
+ };
+ }
+ else {
+ changes = {
+ changedGamePost
+ };
}
- io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
}
}
else {
@@ -350,23 +364,25 @@ function registerGameServerEvents(socket, io) {
if (!step) {
throw new Error('No step.');
}
- yield startStep(gamePost, step.step, step.variables, io);
+ changes = yield startNewMove(gamePost, step.step, step.variables, io);
}
+ emitChanges(io, changes);
}));
socket.on(EVENTS.outgoing.skip, (_c) => __awaiter(this, [_c], void 0, function* ({ id }) {
const gamePost = yield getPost(id);
const play = gamePost.game.play;
+ let skippedMovePost;
if ((play.status === 'started' || play.status === 'paused') && play.moveId) {
const movePost = yield getPost(play.moveId);
const move = movePost.move;
if (move.status === 'started' || move.status === 'paused') {
- const newMove = Object.assign(Object.assign({}, move), { status: 'skipped' });
- yield updatePost(play.moveId, {
- move: newMove
+ skippedMovePost = yield updatePost(movePost, {
+ move: Object.assign(Object.assign({}, move), { status: 'skipped' })
});
}
}
- yield nextStep(gamePost, io);
+ const changes = yield nextMove(gamePost, io);
+ emitChanges(io, Object.assign(Object.assign({}, changes), { changedMoves: skippedMovePost && [skippedMovePost] }));
}));
socket.on(EVENTS.outgoing.pause, (_d) => __awaiter(this, [_d], void 0, function* ({ id }) {
const post = yield getPost(id);
@@ -375,24 +391,25 @@ function registerGameServerEvents(socket, io) {
if (play.status !== 'started') {
return;
}
- const newGame = Object.assign(Object.assign({}, game), { play: {
- status: 'paused',
- step: play.step,
- variables: play.variables,
- moveId: play.moveId,
- playerIds: play.playerIds,
- } });
- yield updatePost(id, { game: newGame });
+ const changedGamePost = yield updatePost(post, {
+ game: Object.assign(Object.assign({}, game), { play: {
+ status: 'paused',
+ step: play.step,
+ variables: play.variables,
+ moveId: play.moveId,
+ playerIds: play.playerIds,
+ } })
+ });
const movePost = yield getPost(play.moveId);
const move = movePost.move;
+ let pausedMovePost;
if (move.status === 'started') {
const now = +new Date();
- const newMove = Object.assign(Object.assign({}, move), { status: 'paused', elapsedTime: move.elapsedTime + now - move.startedAt, remainingTime: move.timeout - now });
- yield updatePost(play.moveId, {
- move: newMove
+ pausedMovePost = yield updatePost(movePost, {
+ move: Object.assign(Object.assign({}, move), { status: 'paused', elapsedTime: move.elapsedTime + now - move.startedAt, remainingTime: move.timeout - now })
});
}
- io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
+ emitChanges(io, { changedGamePost, changedMoves: pausedMovePost && [pausedMovePost] });
}));
socket.on(EVENTS.outgoing.stop, (_e) => __awaiter(this, [_e], void 0, function* ({ id }) {
const post = yield getPost(id);
@@ -401,23 +418,24 @@ function registerGameServerEvents(socket, io) {
if (play.status !== 'started' && play.status !== 'paused') {
return;
}
- const newGame = Object.assign(Object.assign({}, game), { play: {
- status: 'stopped',
- variables: play.variables,
- playerIds: play.playerIds,
- } });
- yield updatePost(id, { game: newGame });
+ const changedGamePost = yield updatePost(post, {
+ game: Object.assign(Object.assign({}, game), { play: {
+ status: 'stopped',
+ variables: play.variables,
+ playerIds: play.playerIds,
+ } })
+ });
+ let stoppedMovePost;
if (play.moveId) {
const movePost = yield getPost(play.moveId);
const move = movePost.move;
if (move.status === 'started' || move.status === 'paused') {
- const newMove = Object.assign(Object.assign({}, move), { status: 'stopped' });
- yield updatePost(play.moveId, {
- move: newMove
+ stoppedMovePost = yield updatePost(movePost, {
+ move: Object.assign(Object.assign({}, move), { status: 'stopped' })
});
}
}
- io.in(id).emit(EVENTS.incoming.updated, { game: newGame });
+ emitChanges(io, { changedGamePost, changedMoves: stoppedMovePost && [stoppedMovePost] });
}));
});
}
diff --git a/GameServer.ts b/GameServer.ts
index 9e1032d..7702a30 100644
--- a/GameServer.ts
+++ b/GameServer.ts
@@ -90,10 +90,12 @@ export type Play = {
async function getPost(id: number): Promise {
const post = await Post.findOne({
+ raw: true,
+ nest: true,
where: {
state: 'active',
id
- }
+ },
})
if (!post) {
@@ -103,8 +105,12 @@ async function getPost(id: number): Promise {
return post;
}
-async function updatePost(id: number, data: Partial) {
- return await Post.update(data, { where: { id } })
+async function updatePost(post: Post, data: Partial): Promise {
+ await Post.update(data, { where: { id: post.id } })
+ return {
+ ...post,
+ ...data,
+ }
}
const getFirstMove = (
@@ -224,7 +230,7 @@ const getTransition = (
}
-async function createChild(data: any, parent: Post) {
+async function createChild(data: any, parent: Post): Promise {
const { post } = await createPost(data, [], parent.creatorId)
await Link.create({
creatorId: parent.creatorId,
@@ -250,7 +256,9 @@ function insertVariables(text: string | undefined, variables: PlayVariables) {
})
}
-async function startStep(gamePost: Post, step: MoveStep, variables: PlayVariables, io: Server) {
+type Changes = { changedGamePost: Post, changedMoves?: Post[] }
+
+async function startNewMove(gamePost: Post, step: MoveStep, variables: PlayVariables, io: Server): Promise {
const game = gamePost.game!
const play = game.play
const now = +new Date();
@@ -270,41 +278,25 @@ async function startStep(gamePost: Post, step: MoveStep, variables: PlayVariable
text: insertVariables(step.text, variables),
move
}, gamePost)
+ scheduleMoveTimeout(movePost, io)
- const newGame: Game = {
- ...game,
- play: {
- status: 'started',
- step,
- moveId: movePost.id,
- variables: variables,
- playerIds: play.playerIds,
+ const changedGamePost = await updatePost(gamePost, {
+ game: {
+ ...game,
+ play: {
+ status: 'started',
+ step,
+ moveId: movePost.id,
+ variables: variables,
+ playerIds: play.playerIds,
+ }
}
- }
-
- await updatePost(gamePost.id, { game: newGame })
-
- scheduleMoveTimeout(movePost.id, move, timeout, io)
- io.in(gamePost.id as any).emit(EVENTS.incoming.updated, { game: newGame })
-}
-
-async function moveTimeout(id: number, move: Move, io: Server) {
- console.log('move timeout!')
- const newMove: Move = {
- ...move,
- status: 'ended'
- }
- await updatePost(id, {
- move: newMove
})
- if (move.gameId) {
- const gamePost = await getPost(move.gameId);
- console.log(gamePost)
- nextStep(gamePost, io)
- }
+
+ return { changedGamePost, changedMoves: [movePost] }
}
-async function nextStep(gamePost: Post, io: Server) {
+async function nextMove(gamePost: Post, io: Server): Promise {
const game = gamePost.game!;
const play = game.play!;
if (play.status !== 'started' && play.status !== 'paused') {
@@ -319,43 +311,64 @@ async function nextStep(gamePost: Post, io: Server) {
if (transition?.next) {
if (play.status === 'started') {
- await startStep(gamePost, transition.next, transition.variables, io)
+ return await startNewMove(gamePost, transition.next, transition.variables, io)
} else {
- const newGame: Game = {
+ const changedGamePost = await updatePost(gamePost, {
+ game: {
+ ...game,
+ play: {
+ status: 'paused',
+ step: transition.next,
+ variables: transition.variables,
+ playerIds: play.playerIds,
+ }
+ }
+ })
+ return {
+ changedGamePost,
+ }
+ }
+ } else {
+ const changedGamePost = await updatePost(gamePost, {
+ game: {
...game,
play: {
- status: 'paused',
- step: transition.next,
- variables: transition.variables,
playerIds: play.playerIds,
+ status: 'ended',
+ variables: transition?.variables ?? play.variables
}
}
- await updatePost(gamePost.id, { game: newGame })
- io.in(gamePost.id as any).emit(EVENTS.incoming.updated, { game: newGame })
+ })
+ return { changedGamePost }
+ }
+}
+
+
+async function moveTimeout(movePost: Post, io: Server) {
+ const move = movePost.move!
+ const newMovePost = await updatePost(movePost, {
+ move: {
+ ...move,
+ status: 'ended'
}
- } else {
- const newGame: Game = {
- ...game,
- play: {
- playerIds: play.playerIds,
- status: 'ended',
- variables: transition?.variables ?? play.variables
- }
+ })
+ if (move.gameId) {
+ const gamePost = await getPost(move.gameId);
+ const changes = await nextMove(gamePost, io)
+ if (changes) {
+ emitChanges(io, { ...changes!, changedMoves: [newMovePost, ...changes.changedMoves ?? []] })
}
- await updatePost(gamePost.id, { game: newGame })
- io.in(gamePost.id as any).emit(EVENTS.incoming.updated, { game: newGame })
}
}
-function scheduleMoveTimeout(id: number, move: Move, timeout: number, io: Server) {
- schedule.scheduleJob(timeout, async () => {
- const currentPost = await getPost(id)
- if (!isEqual(currentPost.move, move)) {
- console.log(currentPost.move, move)
- // The state of the move has changed, this job is outdated.
+function scheduleMoveTimeout(movePost: Post, io: Server) {
+ schedule.scheduleJob((movePost.move! as Extract).timeout, async () => {
+ const currentMovePost = await getPost(movePost.id)
+ if (!isEqual(currentMovePost.move, movePost.move)) {
+ console.log('The state of the move has changed, skipping job.')
return
}
- moveTimeout(id, move, io)
+ await moveTimeout(currentMovePost, io)
})
}
@@ -388,74 +401,78 @@ export async function initializeGameServerTasks(io: Server) {
}
if (move.timeout < +new Date()) {
- moveTimeout(post.id, move, io)
+ await moveTimeout(post, io)
} else {
- scheduleMoveTimeout(post.id, move, move.timeout, io)
+ scheduleMoveTimeout(post, io)
}
}
}
+function emitChanges(io: Server, { changedGamePost, changedMoves }: Changes) {
+ io.in(changedGamePost.id as any).emit(EVENTS.incoming.updated, { game: changedGamePost.game!, changedChildren: changedMoves })
+}
+
export async function registerGameServerEvents(socket: Socket, io: Server) {
socket.on(EVENTS.outgoing.update, async ({ id, game }: { id: number, game: Game }) => {
- await updatePost(id, {
+ const post = await getPost(id)
+ const changedGamePost = await updatePost(post, {
game
})
- io.in(id as any).emit(EVENTS.incoming.updated, { game })
+ emitChanges(io, { changedGamePost })
})
socket.on(EVENTS.outgoing.start, async ({ id }: { id: number }) => {
- const gamePost: Post = await Post.findOne({
- where: {
- id
- }
- })
+ const gamePost: Post = await getPost(id)
const game = gamePost.game!
const play = game.play;
- console.log(play)
-
if (play.status === 'started') {
return
}
+ let changes: Changes;
if (play.status === 'paused') {
const moveId = play.moveId;
if (!moveId) {
- console.log('startstep')
- await startStep(gamePost, play.step, play.variables, io)
+ changes = await startNewMove(gamePost, play.step, play.variables, io)
} else {
- const newGame: Game = {
- ...game,
- play: {
- status: 'started',
- moveId,
- playerIds: play.playerIds,
- step: play.step,
- variables: play.variables,
+ const changedGamePost = await updatePost(gamePost, {
+ game: {
+ ...game,
+ play: {
+ status: 'started',
+ moveId,
+ playerIds: play.playerIds,
+ step: play.step,
+ variables: play.variables,
+ }
}
- }
- await updatePost(id, {
- game: newGame
})
const movePost = await getPost(moveId);
const move = movePost.move!;
if (move.status === 'paused') {
const now = + new Date();
const timeout = now + parseDuration(play.step.timeout)! - move.elapsedTime;
- const newMove: Move = {
- ...move,
- status: 'started',
- elapsedTime: move.elapsedTime,
- startedAt: now,
- timeout,
- }
- await updatePost(moveId, {
- move: newMove
+ const newMovePost = await updatePost(movePost, {
+ move: {
+ ...move,
+ status: 'started',
+ elapsedTime: move.elapsedTime,
+ startedAt: now,
+ timeout,
+ }
})
- scheduleMoveTimeout(moveId, newMove, timeout, io)
+ scheduleMoveTimeout(newMovePost, io)
+ changes = {
+ changedGamePost,
+ changedMoves: [newMovePost]
+ }
+ } else {
+ changes = {
+ changedGamePost
+ }
}
- io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
}
} else {
const step = getFirstMove(game.steps[0], {}, play.playerIds);
@@ -463,27 +480,29 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
throw new Error('No step.')
}
- await startStep(gamePost, step.step, step.variables, io)
+ changes = await startNewMove(gamePost, step.step, step.variables, io)
}
+ emitChanges(io, changes)
})
socket.on(EVENTS.outgoing.skip, async ({ id }: { id: number }) => {
const gamePost = await getPost(id);
const play = gamePost.game!.play
+ let skippedMovePost: Post | undefined;
if ((play.status === 'started' || play.status === 'paused') && play.moveId) {
const movePost = await getPost(play.moveId);
const move = movePost.move!;
if (move.status === 'started' || move.status === 'paused') {
- const newMove: Move = {
- ...move,
- status: 'skipped',
- }
- await updatePost(play.moveId, {
- move: newMove
+ skippedMovePost = await updatePost(movePost, {
+ move: {
+ ...move,
+ status: 'skipped',
+ }
})
}
}
- await nextStep(gamePost, io)
+ const changes = await nextMove(gamePost, io)
+ emitChanges(io, { ...changes!, changedMoves: skippedMovePost && [skippedMovePost] })
})
socket.on(EVENTS.outgoing.pause, async ({ id }: { id: number }) => {
@@ -495,34 +514,35 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
return;
}
- const newGame: Game = {
- ...game,
- play: {
- status: 'paused',
- step: play.step,
- variables: play.variables,
- moveId: play.moveId,
- playerIds: play.playerIds,
+ const changedGamePost = await updatePost(post, {
+ game: {
+ ...game,
+ play: {
+ status: 'paused',
+ step: play.step,
+ variables: play.variables,
+ moveId: play.moveId,
+ playerIds: play.playerIds,
+ }
}
- }
- await updatePost(id, { game: newGame })
+ })
const movePost = await getPost(play.moveId);
const move = movePost.move!;
+ let pausedMovePost: Post | undefined;
if (move.status === 'started') {
const now = + new Date();
- const newMove: Move = {
- ...move,
- status: 'paused',
- elapsedTime: move.elapsedTime + now - move.startedAt,
- remainingTime: move.timeout - now
- }
- await updatePost(play.moveId, {
- move: newMove
+ pausedMovePost = await updatePost(movePost, {
+ move: {
+ ...move,
+ status: 'paused',
+ elapsedTime: move.elapsedTime + now - move.startedAt,
+ remainingTime: move.timeout - now
+ }
})
}
- io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
+ emitChanges(io, { changedGamePost, changedMoves: pausedMovePost && [pausedMovePost] })
})
socket.on(EVENTS.outgoing.stop, async ({ id }: { id: number }) => {
@@ -534,30 +554,31 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
return
}
- const newGame: Game = {
- ...game,
- play: {
- status: 'stopped',
- variables: play.variables,
- playerIds: play.playerIds,
+ const changedGamePost = await updatePost(post, {
+ game: {
+ ...game,
+ play: {
+ status: 'stopped',
+ variables: play.variables,
+ playerIds: play.playerIds,
+ }
}
- }
- await updatePost(id, { game: newGame })
+ })
+ let stoppedMovePost: Post | undefined;
if (play.moveId) {
const movePost = await getPost(play.moveId);
const move = movePost.move!;
if (move.status === 'started' || move.status === 'paused') {
- const newMove: Move = {
- ...move,
- status: 'stopped',
- }
- await updatePost(play.moveId, {
- move: newMove
+ stoppedMovePost = await updatePost(movePost, {
+ move: {
+ ...move,
+ status: 'stopped',
+ }
})
}
}
- io.in(id as any).emit(EVENTS.incoming.updated, { game: newGame })
+ emitChanges(io, { changedGamePost, changedMoves: stoppedMovePost && [stoppedMovePost] })
})
}
diff --git a/routes/Post.js b/routes/Post.js
index 6aa999e..11ae6ec 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -366,7 +366,7 @@ router.get('/link-data', authenticateToken, async (req, res) => {
router.get('/search', authenticateToken, async (req, res) => {
const accountId = req.user ? req.user.id : null
- const { type, sourceId, search, userId, mediaType } = req.query
+ const { type, sourceId, search, userId, mediaType, ids } = req.query
const where = {
type: type.toLowerCase(),
state: 'active',
@@ -770,11 +770,9 @@ router.get('/post-comments', async (req, res) => {
router.get('/post-children', async (req, res) => {
const accountId = req.user ? req.user.id : null
- const { postId, limit, offset } = req.query;
+ const { postId, limit, offset, childrenIds } = req.query;
- const links = await Link.findAll({
- offset: +offset,
- limit: +limit,
+ const query = {
order: [['createdAt', 'DESC']],
attributes: ['state'],
include: [
@@ -796,7 +794,16 @@ router.get('/post-children', async (req, res) => {
itemAType: 'post',
itemAId: postId,
}
- })
+ }
+
+ if (childrenIds) {
+ query.where.itemBId = childrenIds.split(',')
+ } else {
+ query.offset = +offset;
+ query.limit = +limit;
+ }
+
+ const links = await Link.findAll(query)
res.status(200).json({ children: links.map(link => link.Post) })
})
From 2b005b1033fa794de04700480ad8f7f3deaf4374 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Tue, 26 Mar 2024 16:31:36 +0100
Subject: [PATCH 12/21] add new post notification
---
routes/Post.js | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/routes/Post.js b/routes/Post.js
index 11ae6ec..fb2e78c 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -1442,7 +1442,7 @@ router.post('/create-comment', authenticateToken, async (req, res) => {
})
const parentPost = await Post.findOne({
where: { id: parent.id },
- attributes: ['id', 'type'],
+ attributes: ['id', 'type', 'game'],
include: {
model: User,
as: 'Creator',
@@ -1486,6 +1486,12 @@ router.post('/create-comment', authenticateToken, async (req, res) => {
`,
})
+
+ if (parentPost.game) {
+ const io = req.app.get('socketio')
+ io.to(parent.id).emit('gs:incoming-updated', { changedChildren: [post] })
+ }
+
Promise.all([createNotification, sendEmail])
.then(() => resolve())
.catch((error) => resolve(error))
From 7a8cadfbc359ee3be11fb07a5af8cc4b0840d08c Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Wed, 27 Mar 2024 03:02:14 +0100
Subject: [PATCH 13/21] improve variable handling
---
GameServer.js | 54 +++++++++++++++++++++++----------------
GameServer.ts | 70 +++++++++++++++++++++++++++++++++------------------
2 files changed, 79 insertions(+), 45 deletions(-)
diff --git a/GameServer.js b/GameServer.js
index ae1bc54..ed0f885 100644
--- a/GameServer.js
+++ b/GameServer.js
@@ -53,7 +53,7 @@ function updatePost(post, data) {
return Object.assign(Object.assign({}, post), data);
});
}
-const getFirstMove = (step, variables, playerIds) => {
+const getFirstMove = (step, variables, players) => {
var _a;
if (!step) {
return undefined;
@@ -62,7 +62,10 @@ const getFirstMove = (step, variables, playerIds) => {
case 'move':
return { step, variables };
case 'sequence': {
- const firstStep = getFirstMove(step.steps[0], variables, playerIds);
+ if (step.repeat && ((step.repeat.type === 'rounds' && step.repeat.amount === 0) || (step.repeat.type === 'turns' && players.length === 0))) {
+ return undefined;
+ }
+ const firstStep = getFirstMove(step.steps[0], variables, players);
if (!firstStep) {
return undefined;
}
@@ -71,7 +74,7 @@ const getFirstMove = (step, variables, playerIds) => {
}
return {
step: firstStep.step,
- variables: Object.assign(Object.assign({}, firstStep.variables), { [step.name]: (_a = firstStep.variables[step.name]) !== null && _a !== void 0 ? _a : (step.repeat.type === 'rounds' ? 1 : playerIds[0]) }),
+ variables: Object.assign(Object.assign({}, firstStep.variables), { [step.name]: (_a = firstStep.variables[step.name]) !== null && _a !== void 0 ? _a : (step.repeat.type === 'rounds' ? 1 : players[0]) }),
};
}
default: {
@@ -80,13 +83,13 @@ const getFirstMove = (step, variables, playerIds) => {
}
}
};
-const getTransition = (steps, stepId, variables, playerIds) => {
+const getTransition = (steps, stepId, variables, players) => {
let current;
let currentVariables = variables;
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
if (current) {
- const nextStep = getFirstMove(step, currentVariables, playerIds);
+ const nextStep = getFirstMove(step, currentVariables, players);
if (nextStep) {
return { current, next: nextStep.step, variables: nextStep.variables };
}
@@ -99,7 +102,7 @@ const getTransition = (steps, stepId, variables, playerIds) => {
}
break;
case 'sequence': {
- const result = getTransition(step.steps, stepId, currentVariables, playerIds);
+ const result = getTransition(step.steps, stepId, currentVariables, players);
if (!result) {
break;
}
@@ -113,12 +116,12 @@ const getTransition = (steps, stepId, variables, playerIds) => {
nextValue = round < +step.repeat.amount ? round + 1 : undefined;
}
else {
- const playerId = currentVariables[step.name];
- const playerIndex = playerIds.indexOf(playerId);
- nextValue = playerIds[playerIndex + 1];
+ const player = currentVariables[step.name];
+ const playerIndex = players.findIndex(p => p.id === player.id);
+ nextValue = players[playerIndex + 1];
}
if (nextValue) {
- const firstStep = getFirstMove(step, Object.assign(Object.assign({}, result.variables), { [step.name]: nextValue }), playerIds);
+ const firstStep = getFirstMove(step, Object.assign(Object.assign({}, result.variables), { [step.name]: nextValue }), players);
if (firstStep) {
return {
current: result.current,
@@ -165,6 +168,10 @@ function createChild(data, parent) {
function insertVariables(text, variables) {
return text === null || text === void 0 ? void 0 : text.replace(/\[([^\]]+)\]/, (substring, variableName) => {
if (variableName in variables) {
+ const value = variables[variableName];
+ if (typeof value === 'object') {
+ return `@${value.name}`;
+ }
return `${variables[variableName]}`;
}
return substring;
@@ -197,7 +204,6 @@ function startNewMove(gamePost, step, variables, io) {
step,
moveId: movePost.id,
variables: variables,
- playerIds: play.playerIds,
} })
});
return { changedGamePost, changedMoves: [movePost] };
@@ -211,7 +217,7 @@ function nextMove(gamePost, io) {
if (play.status !== 'started' && play.status !== 'paused') {
return;
}
- const transition = getTransition(game.steps, play.step.id, play.variables, play.playerIds);
+ const transition = getTransition(game.steps, play.step.id, play.variables, game.players);
if (transition === null || transition === void 0 ? void 0 : transition.next) {
if (play.status === 'started') {
return yield startNewMove(gamePost, transition.next, transition.variables, io);
@@ -222,7 +228,6 @@ function nextMove(gamePost, io) {
status: 'paused',
step: transition.next,
variables: transition.variables,
- playerIds: play.playerIds,
} })
});
return {
@@ -233,7 +238,6 @@ function nextMove(gamePost, io) {
else {
const changedGamePost = yield updatePost(gamePost, {
game: Object.assign(Object.assign({}, game), { play: {
- playerIds: play.playerIds,
status: 'ended',
variables: (_a = transition === null || transition === void 0 ? void 0 : transition.variables) !== null && _a !== void 0 ? _a : play.variables
} })
@@ -333,7 +337,6 @@ function registerGameServerEvents(socket, io) {
game: Object.assign(Object.assign({}, game), { play: {
status: 'started',
moveId,
- playerIds: play.playerIds,
step: play.step,
variables: play.variables,
} })
@@ -360,11 +363,22 @@ function registerGameServerEvents(socket, io) {
}
}
else {
- const step = getFirstMove(game.steps[0], {}, play.playerIds);
- if (!step) {
- throw new Error('No step.');
+ const variables = {};
+ const step = getFirstMove(game.steps[0], variables, game.players);
+ if (step) {
+ changes = yield startNewMove(gamePost, step.step, step.variables, io);
+ }
+ else {
+ const changedGamePost = yield updatePost(gamePost, {
+ game: Object.assign(Object.assign({}, game), { play: {
+ status: 'ended',
+ variables
+ } })
+ });
+ changes = {
+ changedGamePost
+ };
}
- changes = yield startNewMove(gamePost, step.step, step.variables, io);
}
emitChanges(io, changes);
}));
@@ -397,7 +411,6 @@ function registerGameServerEvents(socket, io) {
step: play.step,
variables: play.variables,
moveId: play.moveId,
- playerIds: play.playerIds,
} })
});
const movePost = yield getPost(play.moveId);
@@ -422,7 +435,6 @@ function registerGameServerEvents(socket, io) {
game: Object.assign(Object.assign({}, game), { play: {
status: 'stopped',
variables: play.variables,
- playerIds: play.playerIds,
} })
});
let stoppedMovePost;
diff --git a/GameServer.ts b/GameServer.ts
index 7702a30..ed0c048 100644
--- a/GameServer.ts
+++ b/GameServer.ts
@@ -64,8 +64,17 @@ type MoveStep = Extract
type Game = {
steps: Step[]
play: Play
+ players: BaseUser[]
}
+export type BaseUser = {
+ id: number
+ handle: string
+ name: string
+ flagImagePath: string
+}
+
+
type Move = (
| { status: 'skipped' | 'ended' | 'stopped' }
| { status: 'paused'; elapsedTime: number; remainingTime: number }
@@ -77,10 +86,9 @@ type Move = (
}
) & { gameId?: number }
-type PlayVariables = Record
+type PlayVariables = Record
export type Play = {
- playerIds: number[]
variables: PlayVariables
} & (
| { status: 'waiting' | 'stopped' | 'ended' }
@@ -116,7 +124,7 @@ async function updatePost(post: Post, data: Partial): Promise {
const getFirstMove = (
step: Step | undefined,
variables: PlayVariables,
- playerIds: number[]
+ players: BaseUser[]
): undefined | { step: MoveStep; variables: PlayVariables } => {
if (!step) {
return undefined
@@ -125,7 +133,11 @@ const getFirstMove = (
case 'move':
return { step, variables }
case 'sequence': {
- const firstStep = getFirstMove(step.steps[0], variables, playerIds)
+ if (step.repeat && ((step.repeat.type === 'rounds' && step.repeat.amount === 0) || (step.repeat.type === 'turns' && players.length === 0))) {
+ return undefined
+ }
+
+ const firstStep = getFirstMove(step.steps[0], variables, players)
if (!firstStep) {
return undefined
}
@@ -137,7 +149,7 @@ const getFirstMove = (
step: firstStep.step,
variables: {
...firstStep.variables,
- [step.name]: firstStep.variables[step.name] ?? (step.repeat.type === 'rounds' ? 1 : playerIds[0]),
+ [step.name]: firstStep.variables[step.name] ?? (step.repeat.type === 'rounds' ? 1 : players[0]),
},
}
}
@@ -152,7 +164,7 @@ const getTransition = (
steps: Step[],
stepId: string,
variables: PlayVariables,
- playerIds: number[]
+ players: BaseUser[]
): undefined | { current: MoveStep; next?: MoveStep; variables: PlayVariables } => {
let current: MoveStep | undefined
let currentVariables = variables
@@ -160,7 +172,7 @@ const getTransition = (
const step = steps[i]
if (current) {
- const nextStep = getFirstMove(step, currentVariables, playerIds)
+ const nextStep = getFirstMove(step, currentVariables, players)
if (nextStep) {
return { current, next: nextStep.step, variables: nextStep.variables }
}
@@ -172,7 +184,7 @@ const getTransition = (
}
break
case 'sequence': {
- const result = getTransition(step.steps, stepId, currentVariables, playerIds)
+ const result = getTransition(step.steps, stepId, currentVariables, players)
if (!result) {
break
}
@@ -187,9 +199,9 @@ const getTransition = (
const round = currentVariables[step.name] as number
nextValue = round < +step.repeat.amount ? round + 1 : undefined
} else {
- const playerId = currentVariables[step.name] as number
- const playerIndex = playerIds.indexOf(playerId)
- nextValue = playerIds[playerIndex + 1]
+ const player = currentVariables[step.name] as BaseUser
+ const playerIndex = players.findIndex(p => p.id === player.id)
+ nextValue = players[playerIndex + 1]
}
if (nextValue) {
const firstStep = getFirstMove(
@@ -198,7 +210,7 @@ const getTransition = (
...result.variables,
[step.name]: nextValue,
},
- playerIds
+ players
)
if (firstStep) {
return {
@@ -250,6 +262,10 @@ async function createChild(data: any, parent: Post): Promise {
function insertVariables(text: string | undefined, variables: PlayVariables) {
return text?.replace(/\[([^\]]+)\]/, (substring, variableName) => {
if (variableName in variables) {
+ const value = variables[variableName]
+ if (typeof value === 'object') {
+ return `@${value.name}`
+ }
return `${variables[variableName]}`
}
return substring
@@ -288,7 +304,6 @@ async function startNewMove(gamePost: Post, step: MoveStep, variables: PlayVaria
step,
moveId: movePost.id,
variables: variables,
- playerIds: play.playerIds,
}
}
})
@@ -306,7 +321,7 @@ async function nextMove(gamePost: Post, io: Server): Promise
Date: Wed, 27 Mar 2024 05:30:47 +0100
Subject: [PATCH 14/21] resolve player variable
---
GameServer.js | 25 ++++++++++++++-----------
GameServer.ts | 31 ++++++++++++++++++++++++++-----
2 files changed, 40 insertions(+), 16 deletions(-)
diff --git a/GameServer.js b/GameServer.js
index ed0f885..0325f0f 100644
--- a/GameServer.js
+++ b/GameServer.js
@@ -165,8 +165,9 @@ function createChild(data, parent) {
return post;
});
}
+const variableRegex = /\[([^\]]+)\]/;
function insertVariables(text, variables) {
- return text === null || text === void 0 ? void 0 : text.replace(/\[([^\]]+)\]/, (substring, variableName) => {
+ return text === null || text === void 0 ? void 0 : text.replace(variableRegex, (substring, variableName) => {
if (variableName in variables) {
const value = variables[variableName];
if (typeof value === 'object') {
@@ -177,19 +178,20 @@ function insertVariables(text, variables) {
return substring;
});
}
+function resolveVariable(text, variables) {
+ const match = text === null || text === void 0 ? void 0 : text.match(variableRegex);
+ console.log(text, match, variables);
+ if (match) {
+ return variables[match[1]];
+ }
+}
function startNewMove(gamePost, step, variables, io) {
return __awaiter(this, void 0, void 0, function* () {
const game = gamePost.game;
const play = game.play;
const now = +new Date();
const timeout = now + (0, parse_duration_1.default)(step.timeout);
- const move = {
- status: 'started',
- elapsedTime: 0,
- startedAt: now,
- timeout,
- gameId: gamePost.id
- };
+ const move = Object.assign(Object.assign({ status: 'started', elapsedTime: 0, startedAt: now, timeout, gameId: gamePost.id }, step.move), { player: resolveVariable(step.move.player, variables) });
const movePost = yield createChild({
type: 'post',
mediaTypes: '',
@@ -383,6 +385,7 @@ function registerGameServerEvents(socket, io) {
emitChanges(io, changes);
}));
socket.on(EVENTS.outgoing.skip, (_c) => __awaiter(this, [_c], void 0, function* ({ id }) {
+ var _d;
const gamePost = yield getPost(id);
const play = gamePost.game.play;
let skippedMovePost;
@@ -396,9 +399,9 @@ function registerGameServerEvents(socket, io) {
}
}
const changes = yield nextMove(gamePost, io);
- emitChanges(io, Object.assign(Object.assign({}, changes), { changedMoves: skippedMovePost && [skippedMovePost] }));
+ emitChanges(io, Object.assign(Object.assign({}, changes), { changedMoves: skippedMovePost && [skippedMovePost, ...(_d = changes === null || changes === void 0 ? void 0 : changes.changedMoves) !== null && _d !== void 0 ? _d : []] }));
}));
- socket.on(EVENTS.outgoing.pause, (_d) => __awaiter(this, [_d], void 0, function* ({ id }) {
+ socket.on(EVENTS.outgoing.pause, (_e) => __awaiter(this, [_e], void 0, function* ({ id }) {
const post = yield getPost(id);
const game = post.game;
const play = game.play;
@@ -424,7 +427,7 @@ function registerGameServerEvents(socket, io) {
}
emitChanges(io, { changedGamePost, changedMoves: pausedMovePost && [pausedMovePost] });
}));
- socket.on(EVENTS.outgoing.stop, (_e) => __awaiter(this, [_e], void 0, function* ({ id }) {
+ socket.on(EVENTS.outgoing.stop, (_f) => __awaiter(this, [_f], void 0, function* ({ id }) {
const post = yield getPost(id);
const game = post.game;
const play = game.play;
diff --git a/GameServer.ts b/GameServer.ts
index ed0c048..0b44f74 100644
--- a/GameServer.ts
+++ b/GameServer.ts
@@ -51,6 +51,7 @@ export type Step = {
title?: string
text: string
timeout: string
+ move: MoveConfig & { player: string }
}
| {
type: 'sequence'
@@ -75,7 +76,15 @@ export type BaseUser = {
}
-type Move = (
+type MoveConfig = (
+ | {
+ type: 'audio'
+ maxDuration: number
+ }
+ | { type: 'text' }
+)
+
+export type Move = (
| { status: 'skipped' | 'ended' | 'stopped' }
| { status: 'paused'; elapsedTime: number; remainingTime: number }
| {
@@ -84,7 +93,7 @@ type Move = (
startedAt: number
timeout: number
}
-) & { gameId?: number }
+) & { gameId?: number; player?: BaseUser } & MoveConfig
type PlayVariables = Record
@@ -259,8 +268,10 @@ async function createChild(data: any, parent: Post): Promise {
return post
}
+const variableRegex = /\[([^\]]+)\]/
+
function insertVariables(text: string | undefined, variables: PlayVariables) {
- return text?.replace(/\[([^\]]+)\]/, (substring, variableName) => {
+ return text?.replace(variableRegex, (substring, variableName) => {
if (variableName in variables) {
const value = variables[variableName]
if (typeof value === 'object') {
@@ -272,6 +283,14 @@ function insertVariables(text: string | undefined, variables: PlayVariables) {
})
}
+function resolveVariable(text: string | undefined, variables: PlayVariables) {
+ const match = text?.match(variableRegex);
+ console.log(text, match, variables)
+ if (match) {
+ return variables[match[1]]
+ }
+}
+
type Changes = { changedGamePost: Post, changedMoves?: Post[] }
async function startNewMove(gamePost: Post, step: MoveStep, variables: PlayVariables, io: Server): Promise {
@@ -285,7 +304,9 @@ async function startNewMove(gamePost: Post, step: MoveStep, variables: PlayVaria
elapsedTime: 0,
startedAt: now,
timeout,
- gameId: gamePost.id
+ gameId: gamePost.id,
+ ...step.move,
+ player: resolveVariable(step.move.player, variables) as BaseUser
}
const movePost = await createChild({
type: 'post',
@@ -526,7 +547,7 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
}
}
const changes = await nextMove(gamePost, io)
- emitChanges(io, { ...changes!, changedMoves: skippedMovePost && [skippedMovePost] })
+ emitChanges(io, { ...changes!, changedMoves: skippedMovePost && [skippedMovePost, ...changes?.changedMoves ?? []] })
})
socket.on(EVENTS.outgoing.pause, async ({ id }: { id: number }) => {
From 683636ecf145df4d8c40ff0d549ade12f35c1b97 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Wed, 27 Mar 2024 11:29:02 +0100
Subject: [PATCH 15/21] submissions
---
GameServer.js | 23 +++++++++++++++++++----
GameServer.ts | 19 ++++++++++++++++++-
Helpers.js | 14 ++++++++++++++
models/Post.js | 1 +
routes/Post.js | 18 ++++++++++++++++++
5 files changed, 70 insertions(+), 5 deletions(-)
diff --git a/GameServer.js b/GameServer.js
index 0325f0f..7a7ab4e 100644
--- a/GameServer.js
+++ b/GameServer.js
@@ -281,6 +281,7 @@ const EVENTS = {
stop: 'gs:outgoing-stop',
skip: 'gs:outgoing-skip',
pause: 'gs:outgoing-pause',
+ submit: 'gs:outgoing-submit'
},
incoming: {
updated: 'gs:incoming-updated'
@@ -384,9 +385,23 @@ function registerGameServerEvents(socket, io) {
}
emitChanges(io, changes);
}));
- socket.on(EVENTS.outgoing.skip, (_c) => __awaiter(this, [_c], void 0, function* ({ id }) {
+ socket.on(EVENTS.outgoing.submit, (_c) => __awaiter(this, [_c], void 0, function* ({ id, moveId }) {
var _d;
const gamePost = yield getPost(id);
+ const movePost = yield getPost(moveId);
+ const move = movePost.move;
+ if (move.status !== 'started') {
+ return;
+ }
+ let completedMovePost = yield updatePost(movePost, {
+ move: Object.assign(Object.assign({}, move), { status: 'ended' })
+ });
+ const changes = yield nextMove(gamePost, io);
+ emitChanges(io, Object.assign(Object.assign({}, changes), { changedMoves: [completedMovePost, ...(_d = changes === null || changes === void 0 ? void 0 : changes.changedMoves) !== null && _d !== void 0 ? _d : []] }));
+ }));
+ socket.on(EVENTS.outgoing.skip, (_e) => __awaiter(this, [_e], void 0, function* ({ id }) {
+ var _f;
+ const gamePost = yield getPost(id);
const play = gamePost.game.play;
let skippedMovePost;
if ((play.status === 'started' || play.status === 'paused') && play.moveId) {
@@ -399,9 +414,9 @@ function registerGameServerEvents(socket, io) {
}
}
const changes = yield nextMove(gamePost, io);
- emitChanges(io, Object.assign(Object.assign({}, changes), { changedMoves: skippedMovePost && [skippedMovePost, ...(_d = changes === null || changes === void 0 ? void 0 : changes.changedMoves) !== null && _d !== void 0 ? _d : []] }));
+ emitChanges(io, Object.assign(Object.assign({}, changes), { changedMoves: skippedMovePost && [skippedMovePost, ...(_f = changes === null || changes === void 0 ? void 0 : changes.changedMoves) !== null && _f !== void 0 ? _f : []] }));
}));
- socket.on(EVENTS.outgoing.pause, (_e) => __awaiter(this, [_e], void 0, function* ({ id }) {
+ socket.on(EVENTS.outgoing.pause, (_g) => __awaiter(this, [_g], void 0, function* ({ id }) {
const post = yield getPost(id);
const game = post.game;
const play = game.play;
@@ -427,7 +442,7 @@ function registerGameServerEvents(socket, io) {
}
emitChanges(io, { changedGamePost, changedMoves: pausedMovePost && [pausedMovePost] });
}));
- socket.on(EVENTS.outgoing.stop, (_f) => __awaiter(this, [_f], void 0, function* ({ id }) {
+ socket.on(EVENTS.outgoing.stop, (_h) => __awaiter(this, [_h], void 0, function* ({ id }) {
const post = yield getPost(id);
const game = post.game;
const play = game.play;
diff --git a/GameServer.ts b/GameServer.ts
index 0b44f74..bdb2923 100644
--- a/GameServer.ts
+++ b/GameServer.ts
@@ -411,9 +411,9 @@ const EVENTS = {
update: 'gs:outgoing-update',
start: 'gs:outgoing-start',
stop: 'gs:outgoing-stop',
-
skip: 'gs:outgoing-skip',
pause: 'gs:outgoing-pause',
+ submit: 'gs:outgoing-submit'
},
incoming: {
updated: 'gs:incoming-updated'
@@ -530,6 +530,23 @@ export async function registerGameServerEvents(socket: Socket, io: Server) {
emitChanges(io, changes)
})
+ socket.on(EVENTS.outgoing.submit, async ({ id, moveId }) => {
+ const gamePost = await getPost(id);
+ const movePost = await getPost(moveId)
+ const move = movePost.move!
+ if (move.status !== 'started') {
+ return
+ }
+ let completedMovePost = await updatePost(movePost, {
+ move: {
+ ...move,
+ status: 'ended'
+ }
+ })
+ const changes = await nextMove(gamePost, io)
+ emitChanges(io, { ...changes!, changedMoves: [completedMovePost, ...changes?.changedMoves ?? []] })
+ })
+
socket.on(EVENTS.outgoing.skip, async ({ id }: { id: number }) => {
const gamePost = await getPost(id);
const play = gamePost.game!.play
diff --git a/Helpers.js b/Helpers.js
index cab2476..353655d 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -2023,6 +2023,20 @@ function attachComment(comment, parent, accountId) {
totalComments: 0,
totalRatings: 0,
})
+ if (parent.relationship !== 'parent') {
+ await Link.create({
+ creatorId: accountId,
+ itemAId: parent.id,
+ itemAType: parent.type,
+ itemBId: comment.id,
+ itemBType: comment.type,
+ relationship: parent.relationship,
+ state: 'active',
+ totalLikes: 0,
+ totalComments: 0,
+ totalRatings: 0,
+ })
+ }
const addRootLink = await Link.create({
creatorId: accountId,
itemAId: rootPost.id,
diff --git a/models/Post.js b/models/Post.js
index 2050873..0c70f69 100644
--- a/models/Post.js
+++ b/models/Post.js
@@ -52,6 +52,7 @@ module.exports = (sequelize, DataTypes) => {
Post.hasMany(models.Link, { as: 'AudioBlocks', foreignKey: 'itemAId' })
Post.hasOne(models.Link, { as: 'Originals', foreignKey: 'itemBId' })
Post.hasMany(models.Link, { as: 'Remixes', foreignKey: 'itemAId' })
+ Post.hasMany(models.Link, { as: 'Submissions', foreignKey: 'itemAId' })
Post.hasOne(models.Link, { as: 'MediaLink', foreignKey: 'itemAId' })
// used for post map (todo: rethink...)
Post.hasMany(models.Link, { as: 'OutgoingPostLinks', foreignKey: 'itemAId' })
diff --git a/routes/Post.js b/routes/Post.js
index fb2e78c..3ab98d8 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -784,6 +784,24 @@ router.get('/post-children', async (req, res) => {
model: User,
as: 'Creator',
attributes: ['id', 'handle', 'name', 'flagImagePath', 'coverImagePath'],
+ },
+ {
+ model: Link,
+ as: 'Submissions',
+ separate: true,
+ where: { relationship: 'submission', state: 'active' },
+ order: [['index', 'ASC']],
+ include: {
+ model: Post,
+ attributes: ['id', 'type', 'text'],
+ include: [
+ {
+ model: User,
+ as: 'Creator',
+ attributes: ['id', 'handle', 'name', 'flagImagePath', 'coverImagePath'],
+ },
+ ]
+ }
}
]
}
From 0a01d76974d9650cdfc9ec5e422d95b8d21b4a67 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Wed, 27 Mar 2024 12:57:21 +0100
Subject: [PATCH 16/21] submission part optional
---
GameServer.js | 16 ++++++++--------
GameServer.ts | 21 ++++++++-------------
2 files changed, 16 insertions(+), 21 deletions(-)
diff --git a/GameServer.js b/GameServer.js
index 7a7ab4e..0160499 100644
--- a/GameServer.js
+++ b/GameServer.js
@@ -178,20 +178,20 @@ function insertVariables(text, variables) {
return substring;
});
}
-function resolveVariable(text, variables) {
- const match = text === null || text === void 0 ? void 0 : text.match(variableRegex);
- console.log(text, match, variables);
- if (match) {
- return variables[match[1]];
- }
-}
function startNewMove(gamePost, step, variables, io) {
return __awaiter(this, void 0, void 0, function* () {
const game = gamePost.game;
const play = game.play;
const now = +new Date();
const timeout = now + (0, parse_duration_1.default)(step.timeout);
- const move = Object.assign(Object.assign({ status: 'started', elapsedTime: 0, startedAt: now, timeout, gameId: gamePost.id }, step.move), { player: resolveVariable(step.move.player, variables) });
+ const move = {
+ status: 'started',
+ elapsedTime: 0,
+ startedAt: now,
+ timeout,
+ gameId: gamePost.id,
+ submission: step.submission && (Object.assign(Object.assign({}, step.submission), { player: variables[step.submission.player] }))
+ };
const movePost = yield createChild({
type: 'post',
mediaTypes: '',
diff --git a/GameServer.ts b/GameServer.ts
index bdb2923..c54bf88 100644
--- a/GameServer.ts
+++ b/GameServer.ts
@@ -51,7 +51,7 @@ export type Step = {
title?: string
text: string
timeout: string
- move: MoveConfig & { player: string }
+ submission?: SubmissionConfig & { player: string }
}
| {
type: 'sequence'
@@ -76,7 +76,7 @@ export type BaseUser = {
}
-type MoveConfig = (
+type SubmissionConfig = (
| {
type: 'audio'
maxDuration: number
@@ -93,7 +93,7 @@ export type Move = (
startedAt: number
timeout: number
}
-) & { gameId?: number; player?: BaseUser } & MoveConfig
+) & { gameId?: number; submission?: { player?: BaseUser } & SubmissionConfig }
type PlayVariables = Record
@@ -283,14 +283,6 @@ function insertVariables(text: string | undefined, variables: PlayVariables) {
})
}
-function resolveVariable(text: string | undefined, variables: PlayVariables) {
- const match = text?.match(variableRegex);
- console.log(text, match, variables)
- if (match) {
- return variables[match[1]]
- }
-}
-
type Changes = { changedGamePost: Post, changedMoves?: Post[] }
async function startNewMove(gamePost: Post, step: MoveStep, variables: PlayVariables, io: Server): Promise {
@@ -299,14 +291,17 @@ async function startNewMove(gamePost: Post, step: MoveStep, variables: PlayVaria
const now = +new Date();
const timeout = now + parseDuration(step.timeout)!
+
const move: Move = {
status: 'started',
elapsedTime: 0,
startedAt: now,
timeout,
gameId: gamePost.id,
- ...step.move,
- player: resolveVariable(step.move.player, variables) as BaseUser
+ submission: step.submission && ({
+ ...step.submission,
+ player: variables[step.submission.player] as BaseUser
+ })
}
const movePost = await createChild({
type: 'post',
From ebbd19d25c186e8badc861b473d0d1d68a396c78 Mon Sep 17 00:00:00 2001
From: Nicola Marcacci Rossi
Date: Wed, 27 Mar 2024 18:30:45 +0100
Subject: [PATCH 17/21] audio moves
---
GameServer.js | 14 +--
GameServer.ts | 42 +++++---
routes/Post.js | 277 ++++++++++++++++++++++++++++---------------------
3 files changed, 189 insertions(+), 144 deletions(-)
diff --git a/GameServer.js b/GameServer.js
index 0160499..9ebb7a9 100644
--- a/GameServer.js
+++ b/GameServer.js
@@ -180,18 +180,14 @@ function insertVariables(text, variables) {
}
function startNewMove(gamePost, step, variables, io) {
return __awaiter(this, void 0, void 0, function* () {
+ var _a, _b;
const game = gamePost.game;
const play = game.play;
const now = +new Date();
- const timeout = now + (0, parse_duration_1.default)(step.timeout);
- const move = {
- status: 'started',
- elapsedTime: 0,
- startedAt: now,
- timeout,
- gameId: gamePost.id,
- submission: step.submission && (Object.assign(Object.assign({}, step.submission), { player: variables[step.submission.player] }))
- };
+ const timeout = now + ((_a = (0, parse_duration_1.default)(step.timeout)) !== null && _a !== void 0 ? _a : 0);
+ const move = Object.assign({ status: 'started', elapsedTime: 0, startedAt: now, timeout, gameId: gamePost.id }, step.submission && ({
+ submission: Object.assign(Object.assign({}, step.submission.type === 'audio' ? Object.assign(Object.assign({}, step.submission), { maxDuration: (_b = (0, parse_duration_1.default)(step.submission.maxDuration)) !== null && _b !== void 0 ? _b : 0 }) : Object.assign({}, step.submission)), { player: variables[step.submission.player] })
+ }));
const movePost = yield createChild({
type: 'post',
mediaTypes: '',
diff --git a/GameServer.ts b/GameServer.ts
index c54bf88..9f0de02 100644
--- a/GameServer.ts
+++ b/GameServer.ts
@@ -51,7 +51,11 @@ export type Step = {
title?: string
text: string
timeout: string
- submission?: SubmissionConfig & { player: string }
+ submission?: { player: string } & ({
+ type: 'audio'
+ maxDuration: string
+ }
+ | { type: 'text' })
}
| {
type: 'sequence'
@@ -75,17 +79,8 @@ export type BaseUser = {
flagImagePath: string
}
-
-type SubmissionConfig = (
- | {
- type: 'audio'
- maxDuration: number
- }
- | { type: 'text' }
-)
-
export type Move = (
- | { status: 'skipped' | 'ended' | 'stopped' }
+ | { status: 'skipped' | 'ended' | 'stopped' | 'timeout' }
| { status: 'paused'; elapsedTime: number; remainingTime: number }
| {
status: 'started'
@@ -93,7 +88,15 @@ export type Move = (
startedAt: number
timeout: number
}
-) & { gameId?: number; submission?: { player?: BaseUser } & SubmissionConfig }
+) & { gameId?: number; submission?: MoveSubmission }
+export type MoveSubmission = { player?: BaseUser } & ({
+ type: 'audio'
+ maxDuration: number
+}
+ | { type: 'text' })
+export type MoveSubmissionAudio = Extract
+
+export type MoveStatus = Move['status']
type PlayVariables = Record
@@ -289,7 +292,7 @@ async function startNewMove(gamePost: Post, step: MoveStep, variables: PlayVaria
const game = gamePost.game!
const play = game.play
const now = +new Date();
- const timeout = now + parseDuration(step.timeout)!
+ const timeout = now + (parseDuration(step.timeout) ?? 0)
const move: Move = {
@@ -298,9 +301,16 @@ async function startNewMove(gamePost: Post, step: MoveStep, variables: PlayVaria
startedAt: now,
timeout,
gameId: gamePost.id,
- submission: step.submission && ({
- ...step.submission,
- player: variables[step.submission.player] as BaseUser
+ ...step.submission && ({
+ submission: {
+ ...step.submission.type === 'audio' ? {
+ ...step.submission,
+ maxDuration: parseDuration(step.submission.maxDuration) ?? 0
+ } : {
+ ...step.submission
+ },
+ player: variables[step.submission.player] as BaseUser,
+ }
})
}
const movePost = await createChild({
diff --git a/routes/Post.js b/routes/Post.js
index 3ab98d8..68752f1 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -800,6 +800,28 @@ router.get('/post-children', async (req, res) => {
as: 'Creator',
attributes: ['id', 'handle', 'name', 'flagImagePath', 'coverImagePath'],
},
+ {
+ model: Link,
+ as: 'AudioBlocks',
+ separate: true,
+ where: { itemBType: 'audio-block' },
+ attributes: ['index'],
+ order: [['index', 'ASC']],
+ include: {
+ model: Post,
+ attributes: ['id', 'text'],
+ include: {
+ model: Link,
+ as: 'MediaLink',
+ where: { state: 'active', relationship: 'parent' },
+ attributes: ['id'],
+ include: {
+ model: Audio,
+ attributes: ['url'],
+ },
+ },
+ },
+ },
]
}
}
@@ -1645,107 +1667,111 @@ router.post('/create-poll-answer', authenticateToken, async (req, res) => {
})
router.post('/create-bead', authenticateToken, async (req, res) => {
- const accountId = req.user ? req.user.id : null
- if (!accountId) res.status(401).json({ message: 'Unauthorized' })
- else {
- const { postData, files } = await uploadFiles(req, res, accountId)
- const { post: newBead } = await createPost(postData, files, accountId)
- const { parent } = postData.links
+ try {
+ const accountId = req.user ? req.user.id : null
+ if (!accountId) res.status(401).json({ message: 'Unauthorized' })
+ else {
+ const { postData, files } = await uploadFiles(req, res, accountId)
+ const { post: newBead } = await createPost(postData, files, accountId)
+ const { parent } = postData.links
- const creator = await User.findOne({
- where: { id: accountId },
- attributes: ['name', 'handle'],
- })
+ const creator = await User.findOne({
+ where: { id: accountId },
+ attributes: ['name', 'handle'],
+ })
- const gamePost = await Post.findOne({
- where: { id: parent.id },
- include: [
- {
- model: User,
- as: 'Creator',
- attributes: ['id', 'name', 'handle', 'email', 'emailsDisabled'],
- },
- { model: GlassBeadGame },
- {
- model: User,
- as: 'Players',
- attributes: ['id', 'name', 'handle', 'email', 'emailsDisabled'],
- through: { where: { type: 'glass-bead-game' }, attributes: ['index'] },
- },
- {
- model: Post,
- as: 'Beads',
- required: false,
- through: { where: { state: 'active' }, attributes: ['index'] },
- include: {
+ const gamePost = await Post.findOne({
+ where: { id: parent.id },
+ include: [
+ {
model: User,
as: 'Creator',
attributes: ['id', 'name', 'handle', 'email', 'emailsDisabled'],
},
- },
- ],
- })
+ { model: GlassBeadGame },
+ {
+ model: User,
+ as: 'Players',
+ attributes: ['id', 'name', 'handle', 'email', 'emailsDisabled'],
+ through: { where: { type: 'glass-bead-game' }, attributes: ['index'] },
+ },
+ {
+ model: Post,
+ as: 'Beads',
+ required: false,
+ through: { where: { state: 'active' }, attributes: ['index'] },
+ include: {
+ model: User,
+ as: 'Creator',
+ attributes: ['id', 'name', 'handle', 'email', 'emailsDisabled'],
+ },
+ },
+ ],
+ })
- const createLink = await Link.create({
- creatorId: accountId,
- itemAId: parent.id,
- itemAType: 'post',
- itemBId: newBead.id,
- itemBType: 'bead',
- index: gamePost.GlassBeadGame.totalBeads,
- relationship: 'parent',
- state: 'active',
- totalLikes: 0,
- totalComments: 0,
- totalRatings: 0,
- })
+ let newDeadline = 0;
- const { synchronous, multiplayer, moveTimeWindow } = gamePost.GlassBeadGame
- const notifyPlayers =
- !synchronous && multiplayer
- ? await new Promise(async (resolve) => {
- // find other players to notify
- const otherPlayers = []
- if (gamePost.Players.length) {
- // if restricted game, use linked Players
- otherPlayers.push(...gamePost.Players.filter((p) => p.id !== accountId))
- } else {
- // if open game, use linked Bead Creators
- gamePost.Beads.forEach((bead) => {
- // filter out game creator and existing records
- if (
- bead.Creator.id !== accountId &&
- !otherPlayers.find((p) => p.id === bead.Creator.id)
- )
- otherPlayers.push(bead.Creator)
- })
- }
- // notify players
- const sendNotifications = await Promise.all(
- otherPlayers.map(
- (p) =>
- new Promise(async (resolve2) => {
- const notifyPlayer = await Notification.create({
- type: 'gbg-move-from-other-player',
- ownerId: p.id,
- postId: parent.id,
- userId: accountId,
- seen: false,
- })
- const emailPlayer = p.emailsDisabled
- ? null
- : await sgMail.send({
- to: p.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ if (gamePost.GlassBeadGame) {
+ await Link.create({
+ creatorId: accountId,
+ itemAId: parent.id,
+ itemAType: 'post',
+ itemBId: newBead.id,
+ itemBType: 'bead',
+ index: gamePost.GlassBeadGame.totalBeads,
+ relationship: 'parent',
+ state: 'active',
+ totalLikes: 0,
+ totalComments: 0,
+ totalRatings: 0,
+ })
+
+
+ const { synchronous, multiplayer, moveTimeWindow } = gamePost.GlassBeadGame
+ if (!synchronous && multiplayer) {
+ await new Promise(async (resolve) => {
+ // find other players to notify
+ const otherPlayers = []
+ if (gamePost.Players.length) {
+ // if restricted game, use linked Players
+ otherPlayers.push(...gamePost.Players.filter((p) => p.id !== accountId))
+ } else {
+ // if open game, use linked Bead Creators
+ gamePost.Beads.forEach((bead) => {
+ // filter out game creator and existing records
+ if (
+ bead.Creator.id !== accountId &&
+ !otherPlayers.find((p) => p.id === bead.Creator.id)
+ )
+ otherPlayers.push(bead.Creator)
+ })
+ }
+ // notify players
+ const sendNotifications = await Promise.all(
+ otherPlayers.map(
+ (p) =>
+ new Promise(async (resolve2) => {
+ const notifyPlayer = await Notification.create({
+ type: 'gbg-move-from-other-player',
+ ownerId: p.id,
+ postId: parent.id,
+ userId: accountId,
+ seen: false,
+ })
+ const emailPlayer = p.emailsDisabled
+ ? null
+ : await sgMail.send({
+ to: p.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${p.name}, ${creator.name} just added a new bead.
https://${appURL}/p/${parent.id}
`,
- html: `
+ html: `
Hi ${p.name},
@@ -1754,40 +1780,53 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
bead.
`,
- })
- Promise.all([notifyPlayer, emailPlayer])
- .then(() => resolve2())
- .catch((error) => resolve2(error))
- })
- )
- )
- // schedule next deadline
- const scheduleNewDeadline = moveTimeWindow
- ? await scheduleNextBeadDeadline(
- parent.id,
- gamePost.GlassBeadGame,
- gamePost.Players
+ })
+ Promise.all([notifyPlayer, emailPlayer])
+ .then(() => resolve2())
+ .catch((error) => resolve2(error))
+ })
+ )
)
- : null
+ // schedule next deadline
+ if (moveTimeWindow) {
+ newDeadline = await scheduleNextBeadDeadline(
+ parent.id,
+ gamePost.GlassBeadGame,
+ gamePost.Players
+ )
+ }
+ })
+ }
- Promise.all([sendNotifications, scheduleNewDeadline])
- .then((data) => resolve(data[1]))
- .catch((error) => resolve(error))
+ await GlassBeadGame.increment('totalBeads', {
+ where: { postId: parent.id },
})
- : null
-
- const incrementTotalBeads = await GlassBeadGame.increment('totalBeads', {
- where: { postId: parent.id },
- })
+ } else {
+ await Link.create({
+ creatorId: accountId,
+ itemAId: parent.id,
+ itemAType: 'post',
+ itemBId: newBead.id,
+ itemBType: 'bead',
+ index: 1,
+ relationship: 'submission',
+ state: 'active',
+ totalLikes: 0,
+ totalComments: 0,
+ totalRatings: 0,
+ })
+ }
- const updateLastPostActivity = await Post.update(
- { lastActivity: new Date() },
- { where: { id: parent.id }, silent: true }
- )
+ await Post.update(
+ { lastActivity: new Date() },
+ { where: { id: parent.id }, silent: true }
+ )
- Promise.all([createLink, notifyPlayers, incrementTotalBeads, updateLastPostActivity])
- .then((data) => res.status(200).json({ newBead, newDeadline: data[1] }))
- .catch((error) => res.status(500).json({ message: 'Error', error }))
+ res.status(200).json({ newBead, newDeadline })
+ }
+ } catch (error) {
+ console.error(error)
+ res.status(500).json({ message: 'Error', error })
}
})
From 4771341a76a8d4074d7f034f63d2c39a6c990d15 Mon Sep 17 00:00:00 2001
From: jhweir
Date: Sun, 21 Apr 2024 15:44:46 +0100
Subject: [PATCH 18/21] Indentation updates
---
Helpers.js | 358 ++++++++++----------
ScheduledTasks.js | 34 +-
routes/Post.js | 837 +++++++++++++++++++++++-----------------------
3 files changed, 623 insertions(+), 606 deletions(-)
diff --git a/Helpers.js b/Helpers.js
index 353655d..e87ad47 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -302,14 +302,14 @@ function notifyMention(creator, user, postId, type) {
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: user.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: user.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${user.name}, ${creator.name} just mentioned you in a ${type} on weco:
http://${appURL}/p/${postId}
`,
- html: `
+ html: `
Hi ${user.name},
@@ -319,7 +319,7 @@ function notifyMention(creator, user, postId, type) {
on weco
`,
- })
+ })
Promise.all([sendNotification, sendEmail])
.then(() => resolve())
@@ -863,7 +863,7 @@ function totalSpaceResults(filters) {
const endDate = createSQLDate(new Date())
return depth === 'Deep'
? [
- literal(`(
+ literal(`(
SELECT COUNT(*)
FROM Spaces s
WHERE s.id != Space.id
@@ -881,10 +881,10 @@ function totalSpaceResults(filters) {
OR s.description LIKE '%${search}%'
) AND s.createdAt BETWEEN '${startDate}' AND '${endDate}'
)`),
- 'totalResults',
- ]
+ 'totalResults',
+ ]
: [
- literal(`(
+ literal(`(
SELECT COUNT(*)
FROM Spaces s
WHERE s.state = 'active'
@@ -900,8 +900,8 @@ function totalSpaceResults(filters) {
OR s.description LIKE '%${search}%'
) AND s.createdAt BETWEEN '${startDate}' AND '${endDate}'
)`),
- 'totalResults',
- ]
+ 'totalResults',
+ ]
}
}
@@ -967,7 +967,7 @@ const fullPostAttributes = [
'totalRatings',
'totalLinks',
'game',
- 'move'
+ 'move',
]
// todo: replace all use cases with const fullPostAttributes above
@@ -1155,8 +1155,8 @@ function findPostInclude(accountId) {
include: {
model: Post,
as: 'Parent',
- attributes: ['id', 'title', 'game', 'state']
- }
+ attributes: ['id', 'title', 'game', 'state'],
+ },
},
{
model: Link,
@@ -1166,8 +1166,8 @@ function findPostInclude(accountId) {
order: [['index', 'ASC']],
include: {
model: Post,
- attributes: ['id', 'title', 'game', 'state']
- }
+ attributes: ['id', 'title', 'game', 'state'],
+ },
},
{
model: Reaction,
@@ -1632,14 +1632,14 @@ function sendGBGInvite(player, postId, creator, settings) {
const sendEmail = player.emailsDisabled
? null
: await sgMail.send({
- to: player.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: player.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${player.name}, ${creator.name} just invited you to join a game on weco: https://${appURL}/p/${postId}
Log in and go to your notifications to accept or reject the invitation.
`,
- html: `
+ html: `
Hi ${player.name},
@@ -1657,17 +1657,19 @@ function sendGBGInvite(player, postId, creator, settings) {
Allowed bead types: ${allowedBeadTypes.join(',')}
- Time window for moves: ${moveTimeWindow ? `${moveTimeWindow} minutes` : 'Off'
- }
+ Time window for moves: ${
+ moveTimeWindow ? `${moveTimeWindow} minutes` : 'Off'
+ }
- Character limit: ${characterLimit ? `${characterLimit} characters` : 'Off'
- }
+ Character limit: ${
+ characterLimit ? `${characterLimit} characters` : 'Off'
+ }
Audio time limit: ${moveDuration ? `${moveDuration} seconds` : 'Off'}
`,
- })
+ })
Promise.all([createNotification, sendEmail])
.then(() => resolve())
@@ -1721,7 +1723,7 @@ async function addRemixes(accountId, game, postId) {
}
}
findOriginals(game.steps)
- originals = uniq(originals);
+ originals = uniq(originals)
const links = await Link.findAll({
attributes: ['itemAId'],
where: {
@@ -1730,11 +1732,11 @@ async function addRemixes(accountId, game, postId) {
itemBType: 'post',
itemAId: originals,
itemBId: postId,
- relationship: 'remix'
- }
+ relationship: 'remix',
+ },
})
for (const originalId of originals) {
- if (links?.some(link => link.itemAId === originalId)) {
+ if (links?.some((link) => link.itemAId === originalId)) {
continue
}
await Link.create({
@@ -1747,7 +1749,7 @@ async function addRemixes(accountId, game, postId) {
relationship: 'remix',
totalLikes: 0,
totalComments: 0,
- totalRatings: 0
+ totalRatings: 0,
})
}
}
@@ -1803,14 +1805,14 @@ function createPost(data, files, accountId) {
// todo: add the correct notification type
const notifyMentions = mentions?.length
? await new Promise(async (resolve) => {
- const users = await User.findAll({
- where: { id: mentions, state: 'active' },
- attributes: ['id', 'name', 'email', 'emailsDisabled'],
- })
- Promise.all(users.map((user) => notifyMention(creator, user, post.id, type)))
- .then(() => resolve())
- .catch((error) => resolve(data, error))
- })
+ const users = await User.findAll({
+ where: { id: mentions, state: 'active' },
+ attributes: ['id', 'name', 'email', 'emailsDisabled'],
+ })
+ Promise.all(users.map((user) => notifyMention(creator, user, post.id, type)))
+ .then(() => resolve())
+ .catch((error) => resolve(data, error))
+ })
: null
const createUrls = urls
@@ -1819,158 +1821,158 @@ function createPost(data, files, accountId) {
const createImages = images
? await Promise.all(
- images.map((image, i) => createImage(accountId, post.id, type, image, i, files))
- )
+ images.map((image, i) => createImage(accountId, post.id, type, image, i, files))
+ )
: null
const createAudios = audios
? await Promise.all(
- audios.map((audio, i) => createAudio(accountId, post.id, type, audio, i, files))
- )
+ audios.map((audio, i) => createAudio(accountId, post.id, type, audio, i, files))
+ )
: null
const createEvent = event
? await Event.create({
- postId: post.id,
- state: 'active',
- startTime: event.startTime,
- endTime: event.endTime,
- })
+ postId: post.id,
+ state: 'active',
+ startTime: event.startTime,
+ endTime: event.endTime,
+ })
: null
const createPoll = poll
? await new Promise(async (resolve) => {
- const { type, answers, locked, governance, action, threshold } = poll
- const createPoll = await Poll.create({
- postId: post.id,
- type,
- answersLocked: locked,
- spaceId: governance ? spaceIds[0] : null,
- action: action || null,
- threshold: threshold || null,
- })
- const creatAnswers = await Promise.all(
- answers.map((a) => createPollAnswer(a, accountId, post.id, files))
- )
- Promise.all([createPoll, creatAnswers])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const { type, answers, locked, governance, action, threshold } = poll
+ const createPoll = await Poll.create({
+ postId: post.id,
+ type,
+ answersLocked: locked,
+ spaceId: governance ? spaceIds[0] : null,
+ action: action || null,
+ threshold: threshold || null,
+ })
+ const creatAnswers = await Promise.all(
+ answers.map((a) => createPollAnswer(a, accountId, post.id, files))
+ )
+ Promise.all([createPoll, creatAnswers])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
const createGBG = glassBeadGame
? await new Promise(async (resolve) => {
- const { settings, topicImage, topicGroup, beads, sourcePostId } = glassBeadGame
- const imageFile = files.find((file) => file.originalname === topicImage.id)
- const { players } = settings
- const createGame = await GlassBeadGame.create({
- postId: post.id,
- state: 'active',
- locked: false,
- topicGroup,
- topicImage: imageFile ? imageFile.url : topicImage.Image.url || null,
- synchronous: settings.synchronous,
- multiplayer: settings.multiplayer,
- allowedBeadTypes: settings.allowedBeadTypes.join(',').toLowerCase(),
- playerOrder: players.length ? players.map((p) => p.id).join(',') : null,
- totalMoves: settings.totalMoves || null,
- movesPerPlayer: settings.movesPerPlayer || null,
- moveDuration: settings.moveDuration || null,
- moveTimeWindow: settings.moveTimeWindow || null,
- characterLimit: settings.characterLimit || null,
- introDuration: settings.introDuration || null,
- outroDuration: settings.outroDuration || null,
- intervalDuration: settings.intervalDuration || null,
- nextMoveDeadline: settings.nextMoveDeadline || null,
- totalBeads: beads.length + (sourcePostId ? 1 : 0),
- })
-
- // const linkSourceBead = sourcePostId
- // ? await Link.create({
- // state: 'active',
- // // type: 'gbg-post',
- // index: 0,
- // relationship: 'source',
- // creatorId: accountId,
- // itemAId: post.id,
- // itemBId: sourcePostId,
- // totalLikes: 0,
- // totalComments: 0,
- // totalRatings: 0,
- // })
- // : null
-
- // const notifySourceCreator =
- // sourcePostId && sourceCreatorId !== accountId
- // ? await new Promise(async (Resolve) => {
- // const sourceCreator = await User.findOne({
- // where: { id: sourceCreatorId },
- // attributes: ['name', 'email', 'emailsDisabled'],
- // })
- // const notifyCreator = await Notification.create({
- // type: 'new-gbg-from-your-post',
- // ownerId: sourceCreatorId,
- // userId: accountId,
- // postId: post.id,
- // seen: false,
- // })
- // const skipEmail =
- // sourceCreator.emailsDisabled ||
- // (await accountMuted(accountId, sourceCreator))
- // const emailCreator = skipEmail
- // ? null
- // : await sgMail.send({
- // to: sourceCreator.email,
- // from: {
- // email: 'admin@weco.io',
- // name: 'we { collective }',
- // },
- // subject: 'New notification',
- // text: `
- // Hi ${sourceCreator.name}, ${creatorName} just created a new glass bead game from your post on weco: https://${appURL}/p/${post.id}
- // `,
- // html: `
- //
- // Hi ${sourceCreator.name},
- //
- // ${creatorName}
- // just created a new glass bead game from your post on weco.
- //
- // `,
- // })
- // Promise.all([notifyCreator, emailCreator])
- // .then(() => Resolve())
- // .catch((error) => Resolve(error))
- // })
- // : null
-
- const createBeads = await Promise.all(
- beads.map((bead, index) => createBead(bead, index, accountId, post.id, files))
- )
-
- const addPlayers =
- settings.multiplayer && !!players.length
- ? await addGBGPlayers(post.id, creator, settings)
- : null
-
- Promise.all([
- createGame,
- // linkSourceBead,
- // notifySourceCreator,
- createBeads,
- addPlayers,
- ])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const { settings, topicImage, topicGroup, beads, sourcePostId } = glassBeadGame
+ const imageFile = files.find((file) => file.originalname === topicImage.id)
+ const { players } = settings
+ const createGame = await GlassBeadGame.create({
+ postId: post.id,
+ state: 'active',
+ locked: false,
+ topicGroup,
+ topicImage: imageFile ? imageFile.url : topicImage.Image.url || null,
+ synchronous: settings.synchronous,
+ multiplayer: settings.multiplayer,
+ allowedBeadTypes: settings.allowedBeadTypes.join(',').toLowerCase(),
+ playerOrder: players.length ? players.map((p) => p.id).join(',') : null,
+ totalMoves: settings.totalMoves || null,
+ movesPerPlayer: settings.movesPerPlayer || null,
+ moveDuration: settings.moveDuration || null,
+ moveTimeWindow: settings.moveTimeWindow || null,
+ characterLimit: settings.characterLimit || null,
+ introDuration: settings.introDuration || null,
+ outroDuration: settings.outroDuration || null,
+ intervalDuration: settings.intervalDuration || null,
+ nextMoveDeadline: settings.nextMoveDeadline || null,
+ totalBeads: beads.length + (sourcePostId ? 1 : 0),
+ })
+
+ // const linkSourceBead = sourcePostId
+ // ? await Link.create({
+ // state: 'active',
+ // // type: 'gbg-post',
+ // index: 0,
+ // relationship: 'source',
+ // creatorId: accountId,
+ // itemAId: post.id,
+ // itemBId: sourcePostId,
+ // totalLikes: 0,
+ // totalComments: 0,
+ // totalRatings: 0,
+ // })
+ // : null
+
+ // const notifySourceCreator =
+ // sourcePostId && sourceCreatorId !== accountId
+ // ? await new Promise(async (Resolve) => {
+ // const sourceCreator = await User.findOne({
+ // where: { id: sourceCreatorId },
+ // attributes: ['name', 'email', 'emailsDisabled'],
+ // })
+ // const notifyCreator = await Notification.create({
+ // type: 'new-gbg-from-your-post',
+ // ownerId: sourceCreatorId,
+ // userId: accountId,
+ // postId: post.id,
+ // seen: false,
+ // })
+ // const skipEmail =
+ // sourceCreator.emailsDisabled ||
+ // (await accountMuted(accountId, sourceCreator))
+ // const emailCreator = skipEmail
+ // ? null
+ // : await sgMail.send({
+ // to: sourceCreator.email,
+ // from: {
+ // email: 'admin@weco.io',
+ // name: 'we { collective }',
+ // },
+ // subject: 'New notification',
+ // text: `
+ // Hi ${sourceCreator.name}, ${creatorName} just created a new glass bead game from your post on weco: https://${appURL}/p/${post.id}
+ // `,
+ // html: `
+ //
+ // Hi ${sourceCreator.name},
+ //
+ // ${creatorName}
+ // just created a new glass bead game from your post on weco.
+ //
+ // `,
+ // })
+ // Promise.all([notifyCreator, emailCreator])
+ // .then(() => Resolve())
+ // .catch((error) => Resolve(error))
+ // })
+ // : null
+
+ const createBeads = await Promise.all(
+ beads.map((bead, index) => createBead(bead, index, accountId, post.id, files))
+ )
+
+ const addPlayers =
+ settings.multiplayer && !!players.length
+ ? await addGBGPlayers(post.id, creator, settings)
+ : null
+
+ Promise.all([
+ createGame,
+ // linkSourceBead,
+ // notifySourceCreator,
+ createBeads,
+ addPlayers,
+ ])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
const createCard = card
? await Promise.all(
- [card.front, card.back].map((cardFace, index) =>
- createCardFace(cardFace, index, accountId, post.id, files)
- )
- )
+ [card.front, card.back].map((cardFace, index) =>
+ createCardFace(cardFace, index, accountId, post.id, files)
+ )
+ )
: null
Promise.all([
@@ -2140,14 +2142,14 @@ function scheduleNextBeadDeadline(postId, settings, players) {
const sendMoveEmail = nextPlayer.emailsDisabled
? null
: await sgMail.send({
- to: nextPlayer.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: nextPlayer.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${nextPlayer.name}, it's your move!
Add a new bead to the glass bead game: https://${appURL}/p/${postId}
`,
- html: `
+ html: `
Hi ${nextPlayer.name},
@@ -2156,7 +2158,7 @@ function scheduleNextBeadDeadline(postId, settings, players) {
Add a new bead to the glass bead game.
`,
- })
+ })
const scheduleReminders = await scheduleGBGMoveJobs(
postId,
nextPlayer,
diff --git a/ScheduledTasks.js b/ScheduledTasks.js
index a8aa667..04f3135 100644
--- a/ScheduledTasks.js
+++ b/ScheduledTasks.js
@@ -189,33 +189,37 @@ async function scheduleGBGMoveJobs(postId, player, moveNumber, deadline) {
const sendEmail = p.emailsDisabled
? null
: await sgMail.send({
- to: p.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
- Hi ${p.name}, ${you ? 'You' : player.name
- } failed to make ${you ? 'your' : 'their'
- } move in time on this glass bead game:
+ to: p.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
+ Hi ${p.name}, ${
+ you ? 'You' : player.name
+ } failed to make ${
+ you ? 'your' : 'their'
+ } move in time on this glass bead game:
http://${config.appURL}/p/${postId}
The game has now ended!
`,
- html: `
+ html: `
Hi ${p.name},
- ${you ? 'You' : player.name} failed to make ${you ? 'your' : 'their'
- } move in time on this glass bead game.
+ ${you ? 'You' : player.name} failed to make ${
+ you ? 'your' : 'their'
+ } move in time on this glass bead game.
The game has now ended!
`,
- })
+ })
Promise.all([createNotification, sendEmail])
.then(() => resolve())
.catch((error) => resolve(error))
diff --git a/routes/Post.js b/routes/Post.js
index 68752f1..653b5cb 100644
--- a/routes/Post.js
+++ b/routes/Post.js
@@ -376,7 +376,10 @@ router.get('/search', authenticateToken, async (req, res) => {
if (userId) where.creatorId = userId
if (mediaType) {
if (mediaType === 'game') {
- where[Op.and] = [{ mediaTypes: { [Op.like]: `%${mediaType}%` } }, { [Op.not]: { mediaTypes: { [Op.like]: `%glass-bead-game%` } } }]
+ where[Op.and] = [
+ { mediaTypes: { [Op.like]: `%${mediaType}%` } },
+ { [Op.not]: { mediaTypes: { [Op.like]: `%glass-bead-game%` } } },
+ ]
}
} else {
where.mediaTypes = { [Op.like]: `%${mediaType}%` }
@@ -386,7 +389,9 @@ router.get('/search', authenticateToken, async (req, res) => {
where,
limit: 10,
// TODO: no idea why this fails
- include: findPostInclude(accountId).filter(include => !['UrlBlocks', 'ImageBlocks', 'AudioBlocks'].includes(include.as)),
+ include: findPostInclude(accountId).filter(
+ (include) => !['UrlBlocks', 'ImageBlocks', 'AudioBlocks'].includes(include.as)
+ ),
})
res.status(200).json(matchingPosts)
})
@@ -770,7 +775,7 @@ router.get('/post-comments', async (req, res) => {
router.get('/post-children', async (req, res) => {
const accountId = req.user ? req.user.id : null
- const { postId, limit, offset, childrenIds } = req.query;
+ const { postId, limit, offset, childrenIds } = req.query
const query = {
order: [['createdAt', 'DESC']],
@@ -798,7 +803,13 @@ router.get('/post-children', async (req, res) => {
{
model: User,
as: 'Creator',
- attributes: ['id', 'handle', 'name', 'flagImagePath', 'coverImagePath'],
+ attributes: [
+ 'id',
+ 'handle',
+ 'name',
+ 'flagImagePath',
+ 'coverImagePath',
+ ],
},
{
model: Link,
@@ -822,29 +833,29 @@ router.get('/post-children', async (req, res) => {
},
},
},
- ]
- }
- }
- ]
- }
+ ],
+ },
+ },
+ ],
+ },
],
where: {
state: 'active',
relationship: 'parent',
itemAType: 'post',
itemAId: postId,
- }
+ },
}
if (childrenIds) {
query.where.itemBId = childrenIds.split(',')
} else {
- query.offset = +offset;
- query.limit = +limit;
+ query.offset = +offset
+ query.limit = +limit
}
const links = await Link.findAll(query)
- res.status(200).json({ children: links.map(link => link.Post) })
+ res.status(200).json({ children: links.map((link) => link.Post) })
})
router.get('/post-indirect-spaces', async (req, res) => {
@@ -1356,15 +1367,15 @@ router.get('/card-faces', async (req, res) => {
})
const linkToImage = linkToImageBlock
? await Link.findOne({
- where: {
- itemAType: 'image-block',
- itemAId: linkToImageBlock.itemBId,
- itemBType: 'image',
- state: 'active',
- },
- attributes: [],
- include: { model: Image, attributes: ['url'] },
- })
+ where: {
+ itemAType: 'image-block',
+ itemAId: linkToImageBlock.itemBId,
+ itemBType: 'image',
+ state: 'active',
+ },
+ attributes: [],
+ include: { model: Image, attributes: ['url'] },
+ })
: null
blocks.push({
...link.Post.dataValues,
@@ -1393,71 +1404,71 @@ router.post('/create-post', authenticateToken, async (req, res) => {
// add spaces and increment space stats
const addSpaces = spaceIds
? await new Promise(async (resolve) => {
- const addDirectSpaces = await Promise.all(
- spaceIds.map((spaceId) =>
- createSpacePost(accountId, spaceId, post.id, 'post', 'direct')
- )
- )
- // gather direct spaces ancestor ids
- const spaces = await Space.findAll({
- where: { id: spaceIds, state: 'active' },
- attributes: ['id'],
- include: {
- model: Space,
- as: 'SpaceAncestors',
- attributes: ['id'],
- through: { where: { state: 'open' }, attributes: [] },
- },
- })
- let ancestorIds = []
- spaces.forEach((space) =>
- ancestorIds.push(...space.SpaceAncestors.map((space) => space.id))
- )
- // remove duplicates and direct spaces
- ancestorIds = [...new Set(ancestorIds)].filter((id) => !spaceIds.includes(id))
- // store ancestor ids for response
- allSpaceIds.push(...ancestorIds)
- const addIndirectSpaces = await Promise.all(
- ancestorIds.map((spaceId) =>
- createSpacePost(accountId, spaceId, post.id, 'post', 'indirect')
- )
- )
- // increment space stats
- const incrementSpaceStats = await Space.increment('totalPosts', {
- where: { id: allSpaceIds },
- silent: true,
- })
- Promise.all([addDirectSpaces, addIndirectSpaces, incrementSpaceStats])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const addDirectSpaces = await Promise.all(
+ spaceIds.map((spaceId) =>
+ createSpacePost(accountId, spaceId, post.id, 'post', 'direct')
+ )
+ )
+ // gather direct spaces ancestor ids
+ const spaces = await Space.findAll({
+ where: { id: spaceIds, state: 'active' },
+ attributes: ['id'],
+ include: {
+ model: Space,
+ as: 'SpaceAncestors',
+ attributes: ['id'],
+ through: { where: { state: 'open' }, attributes: [] },
+ },
+ })
+ let ancestorIds = []
+ spaces.forEach((space) =>
+ ancestorIds.push(...space.SpaceAncestors.map((space) => space.id))
+ )
+ // remove duplicates and direct spaces
+ ancestorIds = [...new Set(ancestorIds)].filter((id) => !spaceIds.includes(id))
+ // store ancestor ids for response
+ allSpaceIds.push(...ancestorIds)
+ const addIndirectSpaces = await Promise.all(
+ ancestorIds.map((spaceId) =>
+ createSpacePost(accountId, spaceId, post.id, 'post', 'indirect')
+ )
+ )
+ // increment space stats
+ const incrementSpaceStats = await Space.increment('totalPosts', {
+ where: { id: allSpaceIds },
+ silent: true,
+ })
+ Promise.all([addDirectSpaces, addIndirectSpaces, incrementSpaceStats])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
// todo: notify source creator
const addLink = source
? await new Promise(async (resolve) => {
- const createNewLink = await Link.create({
- state: 'active',
- creatorId: accountId,
- relationship: source.relationship ?? 'link',
- itemAType: source.type,
- itemBType: 'post',
- itemAId: source.id,
- itemBId: post.id,
- description: source.linkDescription,
- totalLikes: 0,
- totalComments: 0,
- totalRatings: 0,
- })
- const updateSourceLinks = await Post.increment('totalLinks', {
- where: { id: source.id },
- silent: true,
- })
- const updateTargetLinks = await post.update({ totalLinks: 1 }, { silent: true })
- Promise.all([createNewLink, updateSourceLinks, updateTargetLinks])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const createNewLink = await Link.create({
+ state: 'active',
+ creatorId: accountId,
+ relationship: source.relationship ?? 'link',
+ itemAType: source.type,
+ itemBType: 'post',
+ itemAId: source.id,
+ itemBId: post.id,
+ description: source.linkDescription,
+ totalLikes: 0,
+ totalComments: 0,
+ totalRatings: 0,
+ })
+ const updateSourceLinks = await Post.increment('totalLinks', {
+ where: { id: source.id },
+ silent: true,
+ })
+ const updateTargetLinks = await post.update({ totalLinks: 1 }, { silent: true })
+ Promise.all([createNewLink, updateSourceLinks, updateTargetLinks])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
Promise.all([addSpaces, addLink])
@@ -1493,14 +1504,14 @@ router.post('/create-comment', authenticateToken, async (req, res) => {
const createNotification = isOwnPost
? null
: await Notification.create({
- ownerId: parentPost.Creator.id,
- type: parentPost.type === 'comment' ? 'comment-reply' : 'post-comment',
- seen: false,
- spaceAId: postData.originSpaceId,
- userId: accountId,
- postId: parent.id,
- commentId: post.id,
- })
+ ownerId: parentPost.Creator.id,
+ type: parentPost.type === 'comment' ? 'comment-reply' : 'post-comment',
+ seen: false,
+ spaceAId: postData.originSpaceId,
+ userId: accountId,
+ postId: parent.id,
+ commentId: post.id,
+ })
const muted = await accountMuted(accountId, parentPost.Creator)
const skipEmail = isOwnPost || muted || parentPost.Creator.emailsDisabled
const messageText =
@@ -1508,14 +1519,14 @@ router.post('/create-comment', authenticateToken, async (req, res) => {
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: parentPost.Creator.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: parentPost.Creator.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${parentPost.Creator.name}, ${account.name} just ${messageText} ${parentPost.type} on weco:
http://${appURL}/p/${post.id}
`,
- html: `
+ html: `
Hi ${parentPost.Creator.name},
@@ -1525,7 +1536,7 @@ router.post('/create-comment', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
if (parentPost.game) {
const io = req.app.get('socketio')
@@ -1563,44 +1574,44 @@ router.post('/create-chat-message', authenticateToken, async (req, res) => {
// add spaces and increment space stats
const addSpaces = spaceIds
? await new Promise(async (resolve) => {
- const addDirectSpaces = await Promise.all(
- spaceIds.map((spaceId) =>
- createSpacePost(accountId, spaceId, post.id, 'post', 'direct')
- )
- )
- // gather direct spaces ancestor ids
- const spaces = await Space.findAll({
- where: { id: spaceIds, state: 'active' },
- attributes: ['id'],
- include: {
- model: Space,
- as: 'SpaceAncestors',
- attributes: ['id'],
- through: { where: { state: 'open' }, attributes: [] },
- },
- })
- let ancestorIds = []
- spaces.forEach((space) =>
- ancestorIds.push(...space.SpaceAncestors.map((space) => space.id))
- )
- // remove duplicates and direct spaces
- ancestorIds = [...new Set(ancestorIds)].filter((id) => !spaceIds.includes(id))
- // store ancestor ids for response
- allSpaceIds.push(...ancestorIds)
- const addIndirectSpaces = await Promise.all(
- ancestorIds.map((spaceId) =>
- createSpacePost(accountId, spaceId, post.id, 'post', 'indirect')
- )
- )
- // increment space stats
- const incrementSpaceStats = await Space.increment('totalPosts', {
- where: { id: allSpaceIds },
- silent: true,
- })
- Promise.all([addDirectSpaces, addIndirectSpaces, incrementSpaceStats])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const addDirectSpaces = await Promise.all(
+ spaceIds.map((spaceId) =>
+ createSpacePost(accountId, spaceId, post.id, 'post', 'direct')
+ )
+ )
+ // gather direct spaces ancestor ids
+ const spaces = await Space.findAll({
+ where: { id: spaceIds, state: 'active' },
+ attributes: ['id'],
+ include: {
+ model: Space,
+ as: 'SpaceAncestors',
+ attributes: ['id'],
+ through: { where: { state: 'open' }, attributes: [] },
+ },
+ })
+ let ancestorIds = []
+ spaces.forEach((space) =>
+ ancestorIds.push(...space.SpaceAncestors.map((space) => space.id))
+ )
+ // remove duplicates and direct spaces
+ ancestorIds = [...new Set(ancestorIds)].filter((id) => !spaceIds.includes(id))
+ // store ancestor ids for response
+ allSpaceIds.push(...ancestorIds)
+ const addIndirectSpaces = await Promise.all(
+ ancestorIds.map((spaceId) =>
+ createSpacePost(accountId, spaceId, post.id, 'post', 'indirect')
+ )
+ )
+ // increment space stats
+ const incrementSpaceStats = await Space.increment('totalPosts', {
+ where: { id: allSpaceIds },
+ silent: true,
+ })
+ Promise.all([addDirectSpaces, addIndirectSpaces, incrementSpaceStats])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
: null
// attach parent comment
const linkComment = parent ? await attachComment(post, parent, accountId) : null
@@ -1709,7 +1720,7 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
],
})
- let newDeadline = 0;
+ let newDeadline = 0
if (gamePost.GlassBeadGame) {
await Link.create({
@@ -1726,7 +1737,6 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
totalRatings: 0,
})
-
const { synchronous, multiplayer, moveTimeWindow } = gamePost.GlassBeadGame
if (!synchronous && multiplayer) {
await new Promise(async (resolve) => {
@@ -1761,17 +1771,17 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
const emailPlayer = p.emailsDisabled
? null
: await sgMail.send({
- to: p.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ to: p.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${p.name}, ${creator.name} just added a new bead.
https://${appURL}/p/${parent.id}
`,
- html: `
+ html: `
Hi ${p.name},
@@ -1780,7 +1790,7 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
bead.
`,
- })
+ })
Promise.all([notifyPlayer, emailPlayer])
.then(() => resolve2())
.catch((error) => resolve2(error))
@@ -1833,7 +1843,7 @@ router.post('/create-bead', authenticateToken, async (req, res) => {
// test
router.post('/update-post', authenticateToken, async (req, res) => {
const accountId = req.user ? req.user.id : null
- const id = req.body.id;
+ const id = req.body.id
const post = await Post.findOne({
where: { id, creatorId: accountId },
attributes: ['id', 'type', 'mediaTypes'],
@@ -1845,23 +1855,20 @@ router.post('/update-post', authenticateToken, async (req, res) => {
})
if (!post) res.status(401).json({ message: 'Unauthorized' })
else {
- const toUpdate = {};
+ const toUpdate = {}
for (const key of ['mediaTypes', 'title', 'text', 'searchableText', 'game', 'move']) {
if (key in req.body) {
toUpdate[key] = req.body[key]
}
}
const promises = []
- const updatePost = await Post.update(
- toUpdate,
- { where: { id, creatorId: accountId } }
- )
+ const updatePost = await Post.update(toUpdate, { where: { id, creatorId: accountId } })
promises.push(updatePost)
if ('game' in req.body) {
await addRemixes(accountId, req.body.game, id)
}
if ('urls' in req.body) {
- const newUrls = req.body.urls;
+ const newUrls = req.body.urls
// update urls
const oldUrlBlockLinks = await Link.findAll({
where: {
@@ -2002,17 +2009,17 @@ router.post('/update-post', authenticateToken, async (req, res) => {
const sendEmail = user.emailsDisabled
? null
: await sgMail.send({
- to: user.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ to: user.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${user.name}, ${post.Creator.name} just mentioned you in a ${post.type} on weco:
http://${appURL}/p/${id}
`,
- html: `
+ html: `
Hi ${user.name},
@@ -2022,7 +2029,7 @@ router.post('/update-post', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
Promise.all([sendNotification, sendEmail])
.then(() => resolve())
.catch((error) => resolve(error))
@@ -2073,28 +2080,28 @@ router.post('/repost-post', authenticateToken, async (req, res) => {
const sendNotification = skipNotification
? null
: await Notification.create({
- ownerId: post.Creator.id,
- type: 'post-repost',
- seen: false,
- spaceAId: spaceId,
- userId: accountId,
- postId,
- })
+ ownerId: post.Creator.id,
+ type: 'post-repost',
+ seen: false,
+ spaceAId: spaceId,
+ userId: accountId,
+ postId,
+ })
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: post.Creator.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ to: post.Creator.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${post.Creator.name}, ${accountName} just reposted your post on weco:
http://${appURL}/p/${postId}
`,
- html: `
+ html: `
Hi ${post.Creator.name},
@@ -2104,7 +2111,7 @@ router.post('/repost-post', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
const createReactions = await Promise.all(
spaceIds.map((id) =>
@@ -2150,14 +2157,14 @@ router.post('/repost-post', authenticateToken, async (req, res) => {
})
const updateSpaceUserStat = spaceUserStat
? await spaceUserStat.increment('totalPostLikes', {
- by: post.totalLikes,
- })
+ by: post.totalLikes,
+ })
: await SpaceUserStat.create({
- spaceId: id,
- userId: post.Creator.id,
- totalPostLikes: post.totalLikes,
- totalUnseenMessages: 0,
- })
+ spaceId: id,
+ userId: post.Creator.id,
+ totalPostLikes: post.totalLikes,
+ totalUnseenMessages: 0,
+ })
Promise.all([
createSpacePost,
incrementTotalPostLikes,
@@ -2227,14 +2234,14 @@ router.post('/repost-post', authenticateToken, async (req, res) => {
})
const updateSpaceUserStat = spaceUserStat
? await spaceUserStat.increment('totalPostLikes', {
- by: post.totalLikes,
- })
+ by: post.totalLikes,
+ })
: await SpaceUserStat.create({
- spaceId: id,
- userId: post.Creator.id,
- totalPostLikes: post.totalLikes,
- totalUnseenMessages: 0,
- })
+ spaceId: id,
+ userId: post.Creator.id,
+ totalPostLikes: post.totalLikes,
+ totalUnseenMessages: 0,
+ })
Promise.all([
createSpacePost,
updateSpaceStats,
@@ -2298,30 +2305,30 @@ router.post('/add-like', authenticateToken, async (req, res) => {
const updateSpaceStats =
type !== 'link'
? Promise.all(
- item.AllPostSpaces.map(
- (space) =>
- new Promise(async (resolve) => {
- const updateSpaceStat = await space.increment('totalPostLikes', {
- silent: true,
- })
- const spaceUserStat = await SpaceUserStat.findOne({
- where: { spaceId: space.id, userId: item.Creator.id },
- attributes: ['id'],
- })
- const updateSpaceUserStat = spaceUserStat
- ? await spaceUserStat.increment('totalPostLikes')
- : await SpaceUserStat.create({
- spaceId: space.id,
- userId: item.Creator.id,
- totalPostLikes: 1,
- totalUnseenMessages: 0,
- })
- Promise.all([updateSpaceStat, updateSpaceUserStat])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
- )
- )
+ item.AllPostSpaces.map(
+ (space) =>
+ new Promise(async (resolve) => {
+ const updateSpaceStat = await space.increment('totalPostLikes', {
+ silent: true,
+ })
+ const spaceUserStat = await SpaceUserStat.findOne({
+ where: { spaceId: space.id, userId: item.Creator.id },
+ attributes: ['id'],
+ })
+ const updateSpaceUserStat = spaceUserStat
+ ? await spaceUserStat.increment('totalPostLikes')
+ : await SpaceUserStat.create({
+ spaceId: space.id,
+ userId: item.Creator.id,
+ totalPostLikes: 1,
+ totalUnseenMessages: 0,
+ })
+ Promise.all([updateSpaceStat, updateSpaceUserStat])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
+ )
+ )
: null
const createReaction = await Reaction.create({
@@ -2353,14 +2360,14 @@ router.post('/add-like', authenticateToken, async (req, res) => {
const createNotification = skipNotification
? null
: await Notification.create({
- ownerId: item.Creator.id,
- type: `${type}-like`,
- seen: false,
- userId: accountId,
- spaceAId,
- postId,
- commentId,
- })
+ ownerId: item.Creator.id,
+ type: `${type}-like`,
+ seen: false,
+ userId: accountId,
+ spaceAId,
+ postId,
+ commentId,
+ })
const { handle, name, flagImagePath } = await User.findOne({
where: { id: accountId },
@@ -2382,14 +2389,14 @@ router.post('/add-like', authenticateToken, async (req, res) => {
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: item.Creator.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: item.Creator.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${item.Creator.name}, ${name} just liked your ${type} on weco:
http://${itemUrl}
`,
- html: `
+ html: `
Hi ${item.Creator.name},
@@ -2399,7 +2406,7 @@ router.post('/add-like', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
Promise.all([
updateTotalLikes,
@@ -2438,22 +2445,22 @@ router.post('/remove-like', authenticateToken, async (req, res) => {
const updateSpaceStats =
type === 'post'
? Promise.all(
- item.AllPostSpaces.map(
- (space) =>
- new Promise(async (resolve) => {
- const updateSpaceStat = await space.decrement('totalPostLikes', {
- silent: true,
- })
- const updateSpaceUserStat = await SpaceUserStat.decrement(
- 'totalPostLikes',
- { where: { spaceId: space.id, userId: item.Creator.id } }
- )
- Promise.all([updateSpaceStat, updateSpaceUserStat])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
- )
- )
+ item.AllPostSpaces.map(
+ (space) =>
+ new Promise(async (resolve) => {
+ const updateSpaceStat = await space.decrement('totalPostLikes', {
+ silent: true,
+ })
+ const updateSpaceUserStat = await SpaceUserStat.decrement(
+ 'totalPostLikes',
+ { where: { spaceId: space.id, userId: item.Creator.id } }
+ )
+ Promise.all([updateSpaceStat, updateSpaceUserStat])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
+ )
+ )
: null
const removeReaction = await Reaction.update(
@@ -2517,27 +2524,27 @@ router.post('/add-rating', authenticateToken, async (req, res) => {
const sendNotification = skipNotification
? null
: await Notification.create({
- ownerId: item.Creator.id,
- type: `${type}-rating`,
- seen: false,
- spaceAId: spaceId,
- userId: accountId,
- postId,
- commentId,
- })
+ ownerId: item.Creator.id,
+ type: `${type}-rating`,
+ seen: false,
+ spaceAId: spaceId,
+ userId: accountId,
+ postId,
+ commentId,
+ })
const itemUrl = `${appURL}/p/${id}`
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: item.Creator.email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
+ to: item.Creator.email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
Hi ${item.Creator.name}, ${accountName} just rated your ${type} on weco:
http://${itemUrl}
`,
- html: `
+ html: `
Hi ${item.Creator.name},
@@ -2547,7 +2554,7 @@ router.post('/add-rating', authenticateToken, async (req, res) => {
on weco
`,
- })
+ })
Promise.all([updateTotalRatings, createReaction, sendNotification, sendEmail])
.then(() => res.status(200).json({ message: 'Success' }))
@@ -2705,29 +2712,33 @@ router.post('/add-link', authenticateToken, async (req, res) => {
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: email,
- from: { email: 'admin@weco.io', name: 'we { collective }' },
- subject: 'New notification',
- text: `
- Hi ${name}, ${accountName} just linked ${type === 'user' ? 'you' : `your ${type}`
- } to another ${location === 'source' ? sourceType : targetType
- } on weco:
+ to: email,
+ from: { email: 'admin@weco.io', name: 'we { collective }' },
+ subject: 'New notification',
+ text: `
+ Hi ${name}, ${accountName} just linked ${
+ type === 'user' ? 'you' : `your ${type}`
+ } to another ${
+ location === 'source' ? sourceType : targetType
+ } on weco:
http://${url}
`,
- html: `
+ html: `
Hi ${name},
${accountName}
- just linked ${type === 'user'
- ? `you`
- : `your ${type}`
+ just linked ${
+ type === 'user'
+ ? `you`
+ : `your ${type}`
}
- to another ${location === 'source' ? sourceType : targetType
+ to another ${
+ location === 'source' ? sourceType : targetType
} on weco
`,
- })
+ })
Promise.all([createNotification, sendEmail])
.then(() => resolve())
.catch((error) => resolve(error))
@@ -2801,34 +2812,34 @@ router.post('/respond-to-event', authenticateToken, async (req, res) => {
const updateStatus = previousResponse
? UserEvent.update({ state: 'removed' }, { where: { id: previousResponse.id } })
: new Promise(async (resolve) => {
- const removeOtherResponseTypes = await UserEvent.update(
- { state: 'removed' },
- { where: { userId: accountId, eventId, state: 'active' } }
- )
-
- const newResponse = await UserEvent.create({
- userId: accountId,
- eventId,
- relationship: response,
- state: 'active',
- })
-
- const scheduleReminder = await scheduleEventNotification({
- type: response,
- postId,
- eventId,
- userEventId: newResponse.id,
- startTime,
- userId: accountId,
- userName: user.name,
- userEmail: user.email,
- emailsDisabled: user.emailsDisabled,
- })
-
- Promise.all([removeOtherResponseTypes, scheduleReminder])
- .then(() => resolve())
- .catch((error) => resolve(error))
- })
+ const removeOtherResponseTypes = await UserEvent.update(
+ { state: 'removed' },
+ { where: { userId: accountId, eventId, state: 'active' } }
+ )
+
+ const newResponse = await UserEvent.create({
+ userId: accountId,
+ eventId,
+ relationship: response,
+ state: 'active',
+ })
+
+ const scheduleReminder = await scheduleEventNotification({
+ type: response,
+ postId,
+ eventId,
+ userEventId: newResponse.id,
+ startTime,
+ userId: accountId,
+ userName: user.name,
+ userEmail: user.email,
+ emailsDisabled: user.emailsDisabled,
+ })
+
+ Promise.all([removeOtherResponseTypes, scheduleReminder])
+ .then(() => resolve())
+ .catch((error) => resolve(error))
+ })
updateStatus
.then(() => res.status(200).json({ message: 'Success' }))
@@ -2881,89 +2892,89 @@ router.post('/vote-on-poll', authenticateToken, async (req, res) => {
const { type, action, threshold } = post.Poll
const executeAction = action
? Promise.all(
- voteData.map(
- (answer) =>
- new Promise(async (resolve1) => {
- // find poll answer
- const pollAnswer = await Post.findOne({
- where: { id: answer.id },
- attributes: ['id', 'text'],
- include: {
- model: Reaction,
- where: { type: 'vote', state: 'active' },
- required: false,
- attributes: ['value'],
- },
- })
- const answerLink = await Link.findOne({
- where: {
- itemAId: postId,
- itemAType: 'post',
- itemBId: answer.id,
- itemBType: 'poll-answer',
- },
- attributes: ['id', 'state'],
- })
- const { text, Reactions } = pollAnswer
- let totalVotes
- if (type === 'weighted-choice')
- totalVotes =
- Reactions.map((r) => +r.value).reduce((a, b) => a + b, 0) /
- 100
- else totalVotes = Reactions.length
- const createSpace =
- action === 'Create spaces' &&
- answerLink.state !== 'done' &&
- totalVotes >= threshold
- ? new Promise(async (resolve2) => {
- const markAnswerDone = await answerLink.update({
- state: 'done',
- })
- const newSpace = await Space.create({
- creatorId: post.Creator.id,
- handle: uuidv4().substring(0, 15),
- name: text,
- description: null,
- state: 'active',
- privacy: 'public',
- totalPostLikes: 0,
- totalPosts: 0,
- totalComments: 0,
- totalFollowers: 1,
- })
- const createModRelationship = SpaceUser.create({
- relationship: 'moderator',
- state: 'active',
- spaceId: newSpace.id,
- userId: post.Creator.id,
- })
- const createFollowerRelationship = SpaceUser.create({
- relationship: 'follower',
- state: 'active',
- spaceId: newSpace.id,
- userId: post.Creator.id,
+ voteData.map(
+ (answer) =>
+ new Promise(async (resolve1) => {
+ // find poll answer
+ const pollAnswer = await Post.findOne({
+ where: { id: answer.id },
+ attributes: ['id', 'text'],
+ include: {
+ model: Reaction,
+ where: { type: 'vote', state: 'active' },
+ required: false,
+ attributes: ['value'],
+ },
+ })
+ const answerLink = await Link.findOne({
+ where: {
+ itemAId: postId,
+ itemAType: 'post',
+ itemBId: answer.id,
+ itemBType: 'poll-answer',
+ },
+ attributes: ['id', 'state'],
+ })
+ const { text, Reactions } = pollAnswer
+ let totalVotes
+ if (type === 'weighted-choice')
+ totalVotes =
+ Reactions.map((r) => +r.value).reduce((a, b) => a + b, 0) /
+ 100
+ else totalVotes = Reactions.length
+ const createSpace =
+ action === 'Create spaces' &&
+ answerLink.state !== 'done' &&
+ totalVotes >= threshold
+ ? new Promise(async (resolve2) => {
+ const markAnswerDone = await answerLink.update({
+ state: 'done',
+ })
+ const newSpace = await Space.create({
+ creatorId: post.Creator.id,
+ handle: uuidv4().substring(0, 15),
+ name: text,
+ description: null,
+ state: 'active',
+ privacy: 'public',
+ totalPostLikes: 0,
+ totalPosts: 0,
+ totalComments: 0,
+ totalFollowers: 1,
+ })
+ const createModRelationship = SpaceUser.create({
+ relationship: 'moderator',
+ state: 'active',
+ spaceId: newSpace.id,
+ userId: post.Creator.id,
+ })
+ const createFollowerRelationship = SpaceUser.create({
+ relationship: 'follower',
+ state: 'active',
+ spaceId: newSpace.id,
+ userId: post.Creator.id,
+ })
+ const attachToParent = await attachParentSpace(
+ newSpace.id,
+ post.Poll.spaceId
+ )
+ Promise.all([
+ markAnswerDone,
+ createModRelationship,
+ createFollowerRelationship,
+ attachToParent,
+ ])
+ .then(() => resolve2())
+ .catch((error) => resolve2(error))
})
- const attachToParent = await attachParentSpace(
- newSpace.id,
- post.Poll.spaceId
- )
- Promise.all([
- markAnswerDone,
- createModRelationship,
- createFollowerRelationship,
- attachToParent,
- ])
- .then(() => resolve2())
- .catch((error) => resolve2(error))
- })
- : null
-
- Promise.all([createSpace])
- .then(() => resolve1())
- .catch((error) => resolve1(error))
- })
- )
- )
+ : null
+
+ Promise.all([createSpace])
+ .then(() => resolve1())
+ .catch((error) => resolve1(error))
+ })
+ )
+ )
: null
const skipNotification = post.Creator.id === accountId
@@ -2972,27 +2983,27 @@ router.post('/vote-on-poll', authenticateToken, async (req, res) => {
const createNotification = skipNotification
? null
: await Notification.create({
- ownerId: post.Creator.id,
- type: 'poll-vote',
- seen: false,
- userId: accountId,
- postId,
- })
+ ownerId: post.Creator.id,
+ type: 'poll-vote',
+ seen: false,
+ userId: accountId,
+ postId,
+ })
const sendEmail = skipEmail
? null
: await sgMail.send({
- to: post.Creator.email,
- from: {
- email: 'admin@weco.io',
- name: 'we { collective }',
- },
- subject: 'New notification',
- text: `
+ to: post.Creator.email,
+ from: {
+ email: 'admin@weco.io',
+ name: 'we { collective }',
+ },
+ subject: 'New notification',
+ text: `
Hi ${post.Creator.name}, ${userName} just voted on your Poll:
http://${appURL}/p/${postId}
`,
- html: `
+ html: `
Hi ${post.Creator.name},
@@ -3001,7 +3012,7 @@ router.post('/vote-on-poll', authenticateToken, async (req, res) => {
Poll
`,
- })
+ })
const updateLastPostActivity = await Post.update(
{ lastActivity: new Date() },
@@ -3174,23 +3185,23 @@ router.post('/delete-post', authenticateToken, async (req, res) => {
)
await Link.update(
- { state: 'deleted', },
+ { state: 'deleted' },
{
where: {
state: 'active',
[Op.or]: [
{
itemAType: 'post',
- itemAId: postId
+ itemAId: postId,
},
{
itemBType: 'post',
- itemBId: postId
- }
- ]
- }
+ itemBId: postId,
+ },
+ ],
+ },
}
- );
+ )
const updateSpaceStats = await Promise.all(
post.AllPostSpaces.map(
@@ -3210,8 +3221,8 @@ router.post('/delete-post', authenticateToken, async (req, res) => {
})
const updateSpaceUserStat = spaceUserStat
? await spaceUserStat.update({
- totalPostLikes: spaceUserStat.totalPostLikes - post.totalLikes,
- })
+ totalPostLikes: spaceUserStat.totalPostLikes - post.totalLikes,
+ })
: null
Promise.all([updateSpace, updateSpaceUserStat])
.then(() => resolve())
@@ -3242,21 +3253,21 @@ router.post('/delete-comment', authenticateToken, async (req, res) => {
{ where: { id: postId, creatorId: accountId } }
)
await Link.update(
- { state: 'deleted', },
+ { state: 'deleted' },
{
where: {
state: 'active',
[Op.or]: [
{
- itemAId: postId
+ itemAId: postId,
},
{
- itemBId: postId
- }
- ]
- }
+ itemBId: postId,
+ },
+ ],
+ },
}
- );
+ )
// get links & root post for tally updates
const rootLink = await Link.findOne({
where: { itemBId: postId, itemBType: 'comment', relationship: 'root' },
From a7ef559477642a06814394c7e866e968e59799e1 Mon Sep 17 00:00:00 2001
From: jhweir
Date: Sun, 21 Apr 2024 17:18:08 +0100
Subject: [PATCH 19/21] script syntax update to work on windows
---
docs/notes.md | 4 ++
package-lock.json | 151 +++++++++++++++++++++++++++++-----------------
package.json | 2 +-
3 files changed, 100 insertions(+), 57 deletions(-)
diff --git a/docs/notes.md b/docs/notes.md
index 6293140..d35bfd4 100644
--- a/docs/notes.md
+++ b/docs/notes.md
@@ -30,3 +30,7 @@ sudo du -x -h / | sort -h | tail -40
# flush pm2 logs
pm2 flush
+
+# deployment notes
+
+Change dev script "concurrently -n node,ts \"nodemon Server.js\" \"tsc --watch\"" to "concurrently -n node,ts 'nodemon Server.js' 'tsc --watch'" to work on linux server
diff --git a/package-lock.json b/package-lock.json
index 8e00e5a..b7570be 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2992,12 +2992,12 @@
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/body-parser": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
- "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dependencies": {
"bytes": "3.1.2",
- "content-type": "~1.0.4",
+ "content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -3005,7 +3005,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
- "raw-body": "2.5.1",
+ "raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -3405,9 +3405,9 @@
}
},
"node_modules/cookie": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
@@ -3932,13 +3932,14 @@
}
},
"node_modules/es5-ext": {
- "version": "0.10.62",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
- "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
+ "version": "0.10.64",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
+ "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
+ "esniff": "^2.0.1",
"next-tick": "^1.1.0"
},
"engines": {
@@ -4410,6 +4411,25 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esniff": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
+ "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
+ "dependencies": {
+ "d": "^1.0.1",
+ "es5-ext": "^0.10.62",
+ "event-emitter": "^0.3.5",
+ "type": "^2.7.2"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esniff/node_modules/type": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
+ "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
+ },
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@@ -4495,16 +4515,16 @@
}
},
"node_modules/express": {
- "version": "4.18.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
- "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.1",
+ "body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.5.0",
+ "cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -4800,9 +4820,9 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
- "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
@@ -6419,9 +6439,9 @@
}
},
"node_modules/mysql2": {
- "version": "3.8.0",
- "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.8.0.tgz",
- "integrity": "sha512-rC9J/Wy9TCaoQWhk/p4J0Jd+WCDYghniuawi7pheDqhQOEJyDfiWGiWOR3iPgTFJaOK3GezC7dmCki7cP1HFkQ==",
+ "version": "3.9.7",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.7.tgz",
+ "integrity": "sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==",
"dependencies": {
"denque": "^2.1.0",
"generate-function": "^2.3.1",
@@ -7236,9 +7256,9 @@
}
},
"node_modules/raw-body": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
- "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
@@ -8157,9 +8177,9 @@
}
},
"node_modules/tar": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
- "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
+ "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
@@ -11281,12 +11301,12 @@
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"body-parser": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
- "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"requires": {
"bytes": "3.1.2",
- "content-type": "~1.0.4",
+ "content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -11294,7 +11314,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
- "raw-body": "2.5.1",
+ "raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -11597,9 +11617,9 @@
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
},
"cookie": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
},
"cookie-signature": {
"version": "1.0.6",
@@ -11999,12 +12019,13 @@
}
},
"es5-ext": {
- "version": "0.10.62",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
- "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
+ "version": "0.10.64",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
+ "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"requires": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
+ "esniff": "^2.0.1",
"next-tick": "^1.1.0"
}
},
@@ -12358,6 +12379,24 @@
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true
},
+ "esniff": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
+ "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
+ "requires": {
+ "d": "^1.0.1",
+ "es5-ext": "^0.10.62",
+ "event-emitter": "^0.3.5",
+ "type": "^2.7.2"
+ },
+ "dependencies": {
+ "type": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
+ "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
+ }
+ }
+ },
"espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@@ -12419,16 +12458,16 @@
"integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="
},
"express": {
- "version": "4.18.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
- "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.1",
+ "body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.5.0",
+ "cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -12677,9 +12716,9 @@
}
},
"follow-redirects": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
- "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw=="
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
},
"for-each": {
"version": "0.3.3",
@@ -13859,9 +13898,9 @@
}
},
"mysql2": {
- "version": "3.8.0",
- "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.8.0.tgz",
- "integrity": "sha512-rC9J/Wy9TCaoQWhk/p4J0Jd+WCDYghniuawi7pheDqhQOEJyDfiWGiWOR3iPgTFJaOK3GezC7dmCki7cP1HFkQ==",
+ "version": "3.9.7",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.7.tgz",
+ "integrity": "sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==",
"requires": {
"denque": "^2.1.0",
"generate-function": "^2.3.1",
@@ -14440,9 +14479,9 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
- "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"requires": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
@@ -15119,9 +15158,9 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"tar": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
- "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
+ "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
diff --git a/package.json b/package.json
index 400c664..b4e3015 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"deps": "docker-compose up --build mysql",
"migrate": "sequelize db:migrate",
"seed": "sequelize db:seed:all",
- "dev": "concurrently -n node,ts 'nodemon Server.js' 'tsc --watch'",
+ "dev": "concurrently -n node,ts \"nodemon Server.js\" \"tsc --watch\"",
"start": "node Server.js"
},
"keywords": [],
From 650db8c706cc4219f18ef06e8e2d9d28cfba8b3f Mon Sep 17 00:00:00 2001
From: jhweir
Date: Sun, 28 Apr 2024 20:18:08 +0100
Subject: [PATCH 20/21] 'move' value added to create post migration
---
migrations/create-post.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/migrations/create-post.js b/migrations/create-post.js
index 9172eee..0b960b2 100644
--- a/migrations/create-post.js
+++ b/migrations/create-post.js
@@ -62,6 +62,9 @@ module.exports = {
game: {
type: Sequelize.JSON,
},
+ move: {
+ type: Sequelize.JSON,
+ },
lastActivity: {
type: Sequelize.DATE,
},
From 447019fa758444151280cab88ef0ac94c70de12e Mon Sep 17 00:00:00 2001
From: jhweir
Date: Mon, 29 Apr 2024 19:38:54 +0100
Subject: [PATCH 21/21] Upload limit changes
---
Helpers.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Helpers.js b/Helpers.js
index e87ad47..38a3b39 100644
--- a/Helpers.js
+++ b/Helpers.js
@@ -42,8 +42,8 @@ const sgMail = require('@sendgrid/mail')
const { uniq } = require('lodash')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
-const imageMBLimit = 10
-const audioMBLimit = 30
+const imageMBLimit = 20
+const audioMBLimit = 100
const defaultPostValues = {
state: 'active',
watermark: false,