Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
0001271
Add SSR mode files
rogeraabbccdd Apr 5, 2021
1a0915e
Update scripts to SSR mode
rogeraabbccdd Apr 7, 2021
014acef
Configure boot file for SSR mode
rogeraabbccdd Apr 7, 2021
85d23ff
Configure router for SSR mode
rogeraabbccdd Apr 7, 2021
3de3544
Add new env HOST_URL
rogeraabbccdd Apr 7, 2021
e8a5978
Remove meta tags in index.template.html
rogeraabbccdd Apr 7, 2021
a62985a
Meta tags for Index page
rogeraabbccdd Apr 7, 2021
eddca05
Meta tags for Patterns page
rogeraabbccdd Apr 7, 2021
177c525
Meta tags for Pattern page
rogeraabbccdd Apr 7, 2021
252b1ff
Change YouTube thumbnail to hqdefault
rogeraabbccdd Apr 7, 2021
73ee2e8
Meta tags for Changelog page
rogeraabbccdd Apr 7, 2021
af323c0
Meta tags for 404 page
rogeraabbccdd Apr 7, 2021
688f01c
Meta tags for My page
rogeraabbccdd Apr 7, 2021
8eced00
Fix vuex persisted state in SSR mode
rogeraabbccdd Apr 8, 2021
aa5210c
Add database schema
rogeraabbccdd Apr 8, 2021
7d1c03a
JWT auth middleware for backend API
rogeraabbccdd Apr 8, 2021
db47e53
Add new dependencies for backend API
rogeraabbccdd Apr 8, 2021
5ce6f27
Update env sample
rogeraabbccdd Apr 8, 2021
8bccf0e
Add express API base
rogeraabbccdd Apr 8, 2021
cb78239
Add users API router
rogeraabbccdd Apr 8, 2021
bdad46c
API backend: login
rogeraabbccdd Apr 8, 2021
f3c514e
API frontend: login
rogeraabbccdd Apr 8, 2021
2682a21
Fix vuex warning
rogeraabbccdd Apr 8, 2021
bff4c19
API backend: refresh client tokens
rogeraabbccdd Apr 8, 2021
6d16ac4
API frontend: refresh client tokens
rogeraabbccdd Apr 8, 2021
292b8ec
API backend: logout
rogeraabbccdd Apr 8, 2021
a69d49e
API frontend: logout
rogeraabbccdd Apr 8, 2021
e5a9177
Fix meta tags url
rogeraabbccdd Apr 8, 2021
fcca47b
Add heroku-postbuild script in package.json
rogeraabbccdd Apr 8, 2021
21125da
Fix logout not delete accessinfo in database
rogeraabbccdd Apr 9, 2021
4d43225
Add patterns API router
rogeraabbccdd Apr 9, 2021
4090c71
Fix typo in patterns schema
rogeraabbccdd Apr 9, 2021
4e67e5d
Type of pattern difficulty level should be Number
rogeraabbccdd Apr 9, 2021
24914c8
API backend: submit new pattern
rogeraabbccdd Apr 9, 2021
9709ce6
Remove unused variable in vue mixin
rogeraabbccdd Apr 9, 2021
ba26683
API frontend: submit new pattern
rogeraabbccdd Apr 9, 2021
cac045c
Fix new pattern discord embed message URL
rogeraabbccdd Apr 9, 2021
beba3fe
Type of pattern submitter should be mongoose id
rogeraabbccdd Apr 9, 2021
a8f065c
Fix keysounded value always false
rogeraabbccdd Apr 9, 2021
f918c3d
Fix wrong status code in API
rogeraabbccdd Apr 9, 2021
ad55b29
API frontend: patterns
rogeraabbccdd Apr 9, 2021
92a5b26
API backend: pattern page
rogeraabbccdd Apr 9, 2021
f1cae55
API frontend: pattern page
rogeraabbccdd Apr 9, 2021
cd4a5d6
Fix keysounded filter in patterns page
rogeraabbccdd Apr 9, 2021
4dc069b
Remove required in users name schema
rogeraabbccdd Apr 9, 2021
58783d0
Also send user ID to front end when login
rogeraabbccdd Apr 9, 2021
6614760
Force logout when fetch user discord data failed
rogeraabbccdd Apr 9, 2021
fd9ee85
API frontend: my page
rogeraabbccdd Apr 9, 2021
24cc042
Add missing data in API user token extend
rogeraabbccdd Apr 9, 2021
2c25d4e
API backend: delete pattern
rogeraabbccdd Apr 9, 2021
7d89bca
API backend: update pattern
rogeraabbccdd Apr 9, 2021
05df959
API frontend: delete and update pattern
rogeraabbccdd Apr 9, 2021
6aa852d
Change manual URL to techmania-team docs
rogeraabbccdd Apr 9, 2021
ecab974
Fix duplicate div id in index page
rogeraabbccdd Apr 9, 2021
d3ad7c7
Add submit date to pattern schema
rogeraabbccdd Apr 9, 2021
5ab09a9
Add last updated date to pattern schema
rogeraabbccdd Apr 9, 2021
71c2f26
Add dates to pattern card
rogeraabbccdd Apr 9, 2021
ed4a247
Also show pattern dates in my page
rogeraabbccdd Apr 9, 2021
bab59a1
Add start and limit query to get patterns API
rogeraabbccdd Apr 9, 2021
665617c
API frontend: index patterns
rogeraabbccdd Apr 9, 2021
b61d3e0
API backend: index videos
rogeraabbccdd Apr 9, 2021
fe05aa6
API frontend: index videos
rogeraabbccdd Apr 9, 2021
638d3b3
Add day.js
rogeraabbccdd Apr 9, 2021
55f6b0e
Update date format in pattern card
rogeraabbccdd Apr 9, 2021
bc2f4af
Remove GitHub actions
rogeraabbccdd Apr 9, 2021
a885067
Remove version keys in db schema
rogeraabbccdd Apr 9, 2021
aec7f34
Update engines in package.json
rogeraabbccdd Apr 9, 2021
1541fae
Add start script for heroku
rogeraabbccdd Apr 9, 2021
368728e
Fix discord login URL
rogeraabbccdd Apr 9, 2021
e16fcc9
Fix keysounded text in pattern page
rogeraabbccdd Apr 9, 2021
21f847f
Prefetch patterns
rogeraabbccdd Apr 9, 2021
eee975c
Add default background for pattern page
rogeraabbccdd Apr 9, 2021
2acbe7d
Add meta API, and redirect facebook and twitter crawler request
rogeraabbccdd Apr 9, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
DISCORD_CLIENT=``
DISCORD_SECRET=``
BACK_URL=``
# Discord client, place your value before 'abc', idk why the fuck dotenv keep parsing wrong value
DISCORD_CLIENT="12345678abc"
# Discord secret
DISCORD_SECRET="abcdefg"
# Discord webhook url
DISCORD_WEBHOOK="https://discord.com/api/webhooks/..."
# Discord guild
DISCORD_GUILD= "799491033917816842"
# Where does the site host on
HOST_URL="http://localhost:8080"
# MongoDB connect string
DB_URL="mongodb://127.0.0.1:27017/techmania"
# Your secret for JWT
JWT_SECRET="abcdefg"
22 changes: 0 additions & 22 deletions .github/workflows/deploy.yml

This file was deleted.

19 changes: 13 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@
"private": true,
"scripts": {
"lint": "eslint --ext .js,.vue ./",
"dev": "quasar dev",
"build": "quasar build",
"icons": "icongenie g -i ./public/assets/notes/basic.png"
"dev": "quasar dev -m ssr",
"build": "quasar build -m ssr",
"heroku-postbuild": "yarn && yarn build",
"icons": "icongenie g -i ./public/assets/notes/basic.png",
"start": "cd dist/ssr && yarn start"
},
"dependencies": {
"@quasar/extras": "^1.0.0",
"axios": "^0.18.1",
"body-parser": "^1.19.0",
"core-js": "^3.6.5",
"dotenv": "^8.2.0",
"form-data": "^4.0.0",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.12.3",
"quasar": "^1.0.0",
"vue-dayjs-plugin": "^1.0.0",
"vue-i18n": "^8.0.0",
"vuex-persistedstate": "3.0.1"
},
Expand Down Expand Up @@ -49,8 +56,8 @@
"last 5 Opera versions"
],
"engines": {
"node": ">= 10.18.1",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
"node": ">= 12.18.3",
"npm": ">= 6.14.11",
"yarn": ">= 1.22.5"
}
}
17 changes: 12 additions & 5 deletions quasar.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = function (/* ctx */) {
supportTS: false,

// https://quasar.dev/quasar-cli/prefetch-feature
// preFetch: true,
preFetch: true,

// app boot file (/src/boot)
// --> boot files are part of "main.js"
Expand All @@ -22,7 +22,9 @@ module.exports = function (/* ctx */) {
'i18n',
'axios',
'mixin',
'analytics'
{ path: 'analytics', server: false },
{ path: 'persist', server: false },
{ path: 'day', server: false }
],

// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
Expand All @@ -48,9 +50,14 @@ module.exports = function (/* ctx */) {
build: {
vueRouterMode: 'hash', // available values: 'hash', 'history'
env: {
BACK_URL: process.env.BACK_URL,
// I don't know why the fuck dotenv always parse wrong value, this trick should fix it
DISCORD_CLIENT: process.env.DISCORD_CLIENT,
DISCORD_SECRET: process.env.DISCORD_SECRET
DISCORD_SECRET: process.env.DISCORD_SECRET,
DISCORD_WEBHOOK: process.env.DISCORD_WEBHOOK,
DISCORD_GUILD: process.env.DISCORD_GUILD,
HOST_URL: process.env.HOST_URL,
DB_URL: process.env.DB_URL,
JWT_SECRET: process.env.JWT_SECRET
},
// transpile: false,

Expand Down Expand Up @@ -117,7 +124,7 @@ module.exports = function (/* ctx */) {
// directives: [],

// Quasar plugins
plugins: ['Notify', 'SessionStorage'],
plugins: ['Notify', 'LocalStorage', 'Meta'],
cssAddon: true
},

Expand Down
30 changes: 30 additions & 0 deletions src-ssr/controllers/meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const patterns = require('../models/patterns.js')

module.exports = {
async pattern (req, res) {
try {
const result = await patterns.findById(req.params.id).populate('submitter', 'name').lean()
if (result === null) {
res.status(404).send({ success: false, message: 'Not found' })
} else {
const img = result.previews.length > 0 ? `http://i3.ytimg.com/vi/${result.previews[0].ytid}/hqdefault.jpg` : 'https://raw.githubusercontent.com/techmania-team/techmania-team.github.io/master/public/assets/Logo_black.png'
res.send(
`
<meta name="title" content="${result.name} | TECHMANIA">
<meta name="description" content="TECHMANIA >> Patterns >> ${result.name}">
<meta name="og:type" content="website" data-dynamic="true" data-qmeta="ogType">
<meta name="og:url" content="${new URL('/patterns/' + result._id, process.env.HOST_URL).toString()}">
<meta name="og:title" content="${result.name} | TECHMANIA">
<meta name="og:description" content="TECHMANIA >> Patterns >> ${result.name}">
<meta name="og:image" content="${img}">
<meta name="twitter:title" content="${result.name} | TECHMANIA">
<meta name="twitter:url" content="${new URL('/patterns/' + result._id, process.env.HOST_URL).toString()}">
<meta name="twitter:description" content="TECHMANIA >> Patterns >> ${result.name}">
`
)
}
} catch (error) {
res.status(500).send({ success: false, message: 'Server Error' })
}
}
}
161 changes: 161 additions & 0 deletions src-ssr/controllers/patterns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
const axios = require('axios')
const mongoose = require('mongoose')
const patterns = require('../models/patterns.js')

module.exports = {
async create (req, res) {
try {
const infoidx = req.user.accessInfo.findIndex(info => info.jwt === req.token)
// check in discord guild or not
const response = await axios.get('https://discord.com/api/users/@me/guilds', {
headers: { Authorization: `Bearer ${req.user.accessInfo[infoidx].discord}` }
})
const inGuild = response.data.find(guild => guild.id.toString() === process.env.DISCORD_GUILD.toString())
if (!inGuild) {
res.status(403).send({ success: false, message: 'Not in guild' })
return
} else {
const result = await patterns.create({
submitter: req.user._id,
name: req.body.name,
composer: req.body.composer,
keysounded: req.body.keysounded,
difficulties: req.body.difficulties,
link: req.body.link,
previews: req.body.previews,
description: req.body.description
})
let strPreveiw = ''
for (const preview of req.body.previews) {
strPreveiw += `https://www.youtube.com/watch?v=${preview.ytid}\n`
}
const controls = ['Touch', 'Key', 'KM']
let strDifficulty = ''
for (const difficulty of req.body.difficulties) {
strDifficulty += `${controls[difficulty.control]} - ${difficulty.name}: lv.${difficulty.level}\n`
}
await axios.post(process.env.DISCORD_WEBHOOK, {
username: 'TECHMANIA',
avatar_url: 'https://avatars.githubusercontent.com/u/77661148?s=200&v=4',
content: `New pattern submitted by <@${req.user.discord}>`,
embeds: [{
url: new URL(`/patterns/${result._id}`, process.env.HOST_URL).toString(),
image: { url: `http://i3.ytimg.com/vi/${req.body.previews[0].ytid}/hqdefault.jpg` },
title: req.body.name,
color: '15158332',
fields: [
{ name: 'Composer', value: req.body.composer, inline: true },
{ name: 'Keysounded', value: req.body.keysounded === true ? 'Yes' : 'No', inline: true },
{ name: 'Previews', value: strPreveiw, inline: false },
{ name: 'Difficulties', value: strDifficulty, inline: false },
{ name: 'Download', value: req.body.link, inline: false },
{ name: 'Description', value: req.body.description, inline: false }
]
}]
})
res.status(200).send({ success: true, message: '' })
}
} catch (error) {
console.log(error)
res.status(500).send({ success: false, message: 'Server Error' })
}
},
async search (req, res) {
try {
const query = {}
let skip = 0
let limit = 0
if (req.query.submitter) {
query.submitter = mongoose.Types.ObjectId(req.query.submitter)
}
if (req.query.start) {
skip = parseInt(req.query.start)
skip = isNaN(skip) ? 0 : skip
}
if (req.query.limit) {
limit = parseInt(req.query.limit)
limit = isNaN(limit) ? 0 : limit
}
const result = await patterns.find(query, {}, { skip, limit }).sort('-submitDate').populate('submitter', 'name').lean()
res.status(200).send({ success: true, message: '', result })
} catch (error) {
console.log(error)
res.status(500).send({ success: false, message: 'Server Error' })
}
},
async searchID (req, res) {
try {
const result = await patterns.findById(req.params.id).populate('submitter', 'name').lean()
if (result === null) {
res.status(404).send({ success: false, message: 'Not found' })
} else {
res.status(200).send({ success: true, message: '', result })
}
} catch (error) {
res.status(500).send({ success: false, message: 'Server Error' })
}
},
async del (req, res) {
try {
const infoidx = req.user.accessInfo.findIndex(info => info.jwt === req.token)
// check in discord guild or not
const response = await axios.get('https://discord.com/api/users/@me/guilds', {
headers: { Authorization: `Bearer ${req.user.accessInfo[infoidx].discord}` }
})
const inGuild = response.data.find(guild => guild.id.toString() === process.env.DISCORD_GUILD.toString())
if (!inGuild) {
res.status(403).send({ success: false, message: 'Not in guild' })
return
} else {
await patterns.findOneAndDelete({ _id: mongoose.Types.ObjectId(req.params.id), submitter: req.user._id })
res.status(200).send({ success: true, message: '' })
}
} catch (error) {
console.log(error)
res.status(500).send({ success: false, message: 'Server Error' })
}
},
async update (req, res) {
try {
const infoidx = req.user.accessInfo.findIndex(info => info.jwt === req.token)
// check in discord guild or not
const response = await axios.get('https://discord.com/api/users/@me/guilds', {
headers: { Authorization: `Bearer ${req.user.accessInfo[infoidx].discord}` }
})
const inGuild = response.data.find(guild => guild.id.toString() === process.env.DISCORD_GUILD.toString())
if (!inGuild) {
res.status(403).send({ success: false, message: 'Not in guild' })
return
} else {
await patterns.findOneAndUpdate({
_id: mongoose.Types.ObjectId(req.params.id),
submitter: req.user._id,
name: req.body.name,
composer: req.body.composer,
keysounded: req.body.keysounded,
difficulties: req.body.difficulties,
link: req.body.link,
previews: req.body.previews,
description: req.body.description,
updateDate: Date.now()
})
res.status(200).send({ success: true, message: '' })
}
} catch (error) {
console.log(error)
res.status(500).send({ success: false, message: 'Server Error' })
}
},
async indexvideo (req, res) {
try {
const result = await patterns.aggregate([
{ $unwind: '$previews' },
{ $project: { 'previews.ytid': 1 } },
{ $sample: { size: 7 } }
])
res.status(200).send({ success: true, message: '', result })
} catch (error) {

}
}
}
Loading