Remove google, add avif support
This commit is contained in:
parent
85d31ded75
commit
44bcbe2647
21 changed files with 178 additions and 194 deletions
|
@ -27,7 +27,7 @@ jobs:
|
|||
name: Push to docker
|
||||
command: |
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
docker push ${di}
|
||||
docker push ${di} --all-tags
|
||||
- deploy:
|
||||
name: Deploy to production
|
||||
command: |
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import googleauth from 'google-auth-library'
|
||||
import config from '../config.mjs'
|
||||
|
||||
const oauth2Client = new googleauth.OAuth2Client(config.get('googleid'))
|
||||
|
||||
// This is hard to have always running as it requires a
|
||||
// test access token which always expire.
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function getProfile(token) {
|
||||
return oauth2Client.getTokenInfo(token)
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import Staff from '../staff/model.mjs'
|
||||
import Jwt from '../jwt.mjs'
|
||||
import * as google from './google.mjs'
|
||||
import * as security from './security.mjs'
|
||||
import AuthHelper from './helper.mjs'
|
||||
|
||||
|
@ -10,37 +9,10 @@ export default class AuthRoutes {
|
|||
helper: opts.helper || new AuthHelper(),
|
||||
Staff: opts.Staff || Staff,
|
||||
jwt: opts.jwt || new Jwt(),
|
||||
google: opts.google || google,
|
||||
security: opts.security || security,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* POST /api/login - Authenticate a user using social login
|
||||
*
|
||||
* @body {string} token - The google token to authenticate
|
||||
* @returns
|
||||
*
|
||||
* { token: 'Authentication token' }
|
||||
*/
|
||||
async login(ctx) {
|
||||
let output = await google.getProfile(ctx.request.body.token)
|
||||
|
||||
if (output.email_verified !== 'true') ctx.throw(422, 'Email was not verified with google')
|
||||
if (!output.email) ctx.throw(422, 'Email was missing from google response')
|
||||
|
||||
let level = 1
|
||||
let staff = await this.Staff
|
||||
.query({ where: { email: output.email }})
|
||||
.fetch({ require: false })
|
||||
|
||||
if (staff && staff.id && staff.get('level')) {
|
||||
level = staff.get('level')
|
||||
}
|
||||
|
||||
ctx.body = { token: this.jwt.createToken(staff.id, output.email, level) }
|
||||
}
|
||||
|
||||
/*
|
||||
* POST /api/login/user - Authenticate a user using password login
|
||||
*
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash'
|
||||
import knex from 'knex'
|
||||
import knex from 'knex-core'
|
||||
import bookshelf from 'bookshelf'
|
||||
|
||||
import config from './config.mjs'
|
||||
|
@ -178,7 +178,7 @@ function getConfig(index = 0, addEvents = true) {
|
|||
afterCreate: addEvents && afterCreate || null,
|
||||
min: 2,
|
||||
max: 10,
|
||||
beforeDestroy: addEvents && beforeDestroy || null,
|
||||
// beforeDestroy: addEvents && beforeDestroy || null,
|
||||
},
|
||||
acquireConnectionTimeout: 10000,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash'
|
||||
import nconf from 'nconf'
|
||||
import nconf from 'nconf-lite'
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
// Helper method for global usage.
|
||||
|
|
|
@ -39,6 +39,21 @@ const Media = bookshelf.createModel({
|
|||
return `${Media.baseUrl}${this.get('large_image')}`
|
||||
},
|
||||
|
||||
small_url_avif() {
|
||||
if (!this.get('small_image_avif')) return null
|
||||
return `${Media.baseUrl}${this.get('small_image_avif')}`
|
||||
},
|
||||
|
||||
medium_url_avif() {
|
||||
if (!this.get('small_image_avif')) return null
|
||||
return `${Media.baseUrl}${this.get('medium_image_avif')}`
|
||||
},
|
||||
|
||||
large_url_avif() {
|
||||
if (!this.get('small_image_avif')) return null
|
||||
return `${Media.baseUrl}${this.get('large_image_avif')}`
|
||||
},
|
||||
|
||||
link() {
|
||||
return `${Media.baseUrl}${this.get('org_image')}`
|
||||
},
|
||||
|
@ -54,7 +69,7 @@ const Media = bookshelf.createModel({
|
|||
}, {
|
||||
baseUrl: config.get('upload:baseurl'),
|
||||
|
||||
getSubUrl(input, size) {
|
||||
getSubUrl(input, size, type = 'jpg') {
|
||||
if (!input) return input
|
||||
|
||||
let output = input
|
||||
|
@ -62,7 +77,7 @@ const Media = bookshelf.createModel({
|
|||
let ext = path.extname(input).toLowerCase()
|
||||
output = input.slice(0, -ext.length)
|
||||
}
|
||||
return `${output}.${size}.jpg`
|
||||
return `${output}.${size}.${type}`
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -9,49 +9,85 @@ export default class Resizer {
|
|||
})
|
||||
}
|
||||
|
||||
createSmall(input) {
|
||||
let output = this.Media.getSubUrl(input, 'small')
|
||||
createSmall(input, format = 'jpg') {
|
||||
let output = this.Media.getSubUrl(input, 'small', format === 'avif' ? 'avif' : 'jpg')
|
||||
|
||||
return this.sharp(input)
|
||||
.resize(360, 360, {
|
||||
let sized = this.sharp(input)
|
||||
.resize(500, 500, {
|
||||
fit: sharp.fit.inside,
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
|
||||
if (format === 'avif') {
|
||||
return sized
|
||||
.avif({
|
||||
quality: 80,
|
||||
speed: 5,
|
||||
})
|
||||
.toFile(output)
|
||||
.then(() => output)
|
||||
} else {
|
||||
return sized
|
||||
.jpeg({
|
||||
quality: 90,
|
||||
quality: 93,
|
||||
})
|
||||
.toFile(output)
|
||||
.then(() => output)
|
||||
}
|
||||
}
|
||||
|
||||
createMedium(input, height) {
|
||||
let output = this.Media.getSubUrl(input, 'medium')
|
||||
createMedium(input, height, format = 'jpg') {
|
||||
let output = this.Media.getSubUrl(input, 'medium', format === 'avif' ? 'avif' : 'jpg')
|
||||
|
||||
return this.sharp(input)
|
||||
.resize(700, height || 700, {
|
||||
let sized = this.sharp(input)
|
||||
.resize(800, height || 800, {
|
||||
fit: height && sharp.fit.cover || sharp.fit.inside,
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
|
||||
if (format === 'avif') {
|
||||
return sized
|
||||
.avif({
|
||||
quality: 80,
|
||||
speed: 5,
|
||||
})
|
||||
.toFile(output)
|
||||
.then(() => output)
|
||||
} else {
|
||||
return sized
|
||||
.jpeg({
|
||||
quality: 90,
|
||||
quality: 93,
|
||||
})
|
||||
.toFile(output)
|
||||
.then(() => output)
|
||||
}
|
||||
}
|
||||
|
||||
createLarge(input) {
|
||||
let output = this.Media.getSubUrl(input, 'large')
|
||||
createLarge(input, format = 'jpg') {
|
||||
let output = this.Media.getSubUrl(input, 'large', format === 'avif' ? 'avif' : 'jpg')
|
||||
|
||||
return this.sharp(input)
|
||||
let sized = this.sharp(input)
|
||||
.resize(1280, 1280, {
|
||||
fit: sharp.fit.inside,
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.jpeg({
|
||||
quality: 90,
|
||||
|
||||
if (format === 'avif') {
|
||||
return sized
|
||||
.avif({
|
||||
quality: 85,
|
||||
speed: 5,
|
||||
})
|
||||
.toFile(output)
|
||||
.then(() => output)
|
||||
} else {
|
||||
return sized
|
||||
.jpeg({
|
||||
quality: 93,
|
||||
})
|
||||
.toFile(output)
|
||||
.then(() => output)
|
||||
}
|
||||
}
|
||||
|
||||
autoRotate(input) {
|
||||
|
|
|
@ -27,14 +27,20 @@ export default class MediaRoutes {
|
|||
let smallPath = await this.resize.createSmall(result.path)
|
||||
let mediumPath = await this.resize.createMedium(result.path, height)
|
||||
let largePath = await this.resize.createLarge(result.path)
|
||||
let smallPathAvif = await this.resize.createSmall(result.path, 'avif')
|
||||
let mediumPathAvif = await this.resize.createMedium(result.path, height, 'avif')
|
||||
let largePathAvif = await this.resize.createLarge(result.path, 'avif')
|
||||
|
||||
let token = this.jwt.signDirect({ site: config.get('upload:name') }, config.get('upload:secret'))
|
||||
|
||||
let [org, small, medium, large] = await Promise.all([
|
||||
let [org, small, medium, large, smallAvif, mediumAvif, largeAvif] = await Promise.all([
|
||||
this.uploadFile(token, result.path),
|
||||
this.uploadFile(token, smallPath),
|
||||
this.uploadFile(token, mediumPath),
|
||||
this.uploadFile(token, largePath),
|
||||
this.uploadFile(token, smallPathAvif),
|
||||
this.uploadFile(token, mediumPathAvif),
|
||||
this.uploadFile(token, largePathAvif),
|
||||
])
|
||||
|
||||
ctx.body = await this.Media.create({
|
||||
|
@ -43,6 +49,9 @@ export default class MediaRoutes {
|
|||
small_image: small.path,
|
||||
medium_image: medium.path,
|
||||
large_image: large.path,
|
||||
small_image_avif: smallAvif.path,
|
||||
medium_image_avif: mediumAvif.path,
|
||||
large_image_avif: largeAvif.path,
|
||||
org_image: org.path,
|
||||
size: result.size,
|
||||
staff_id: ctx.state.user.id,
|
||||
|
|
|
@ -14,7 +14,6 @@ const router = new Router()
|
|||
|
||||
// API Authentication
|
||||
const authentication = new AuthRoutes()
|
||||
router.post('/api/login', authentication.login.bind(authentication))
|
||||
router.post('/api/login/user', authentication.loginUser.bind(authentication))
|
||||
|
||||
// API Media
|
||||
|
|
|
@ -24,13 +24,19 @@ function mapArticle(trim = false, x, includeBanner = false, includeFiles = true)
|
|||
media: x.media && ({
|
||||
link: !trim && x.media.link || null,
|
||||
large_url: x.media.large_url,
|
||||
large_url_avif: x.media.large_url_avif,
|
||||
medium_url: x.media.medium_url,
|
||||
medium_url_avif: x.media.medium_url_avif,
|
||||
small_url: x.media.small_url,
|
||||
small_url_avif: x.media.small_url_avif,
|
||||
}) || null,
|
||||
banner: x.banner && includeBanner && ({
|
||||
large_url: x.banner.large_url,
|
||||
large_url_avif: x.banner.large_url_avif,
|
||||
medium_url: x.banner.medium_url,
|
||||
medium_url_avif: x.banner.medium_url_avif,
|
||||
small_url: x.banner.small_url,
|
||||
small_url_avif: x.banner.small_url_avif,
|
||||
}) || null,
|
||||
parent: x.parent && ({
|
||||
id: x.parent.id,
|
||||
|
@ -67,8 +73,11 @@ function mapPage(x) {
|
|||
media: x.media && ({
|
||||
link: x.media.link,
|
||||
large_url: x.media.large_url,
|
||||
large_url_avif: x.media.large_url_avif,
|
||||
medium_url: x.media.medium_url,
|
||||
medium_url_avif: x.media.medium_url_avif,
|
||||
small_url: x.media.small_url,
|
||||
small_url_avif: x.media.small_url_avif,
|
||||
}) || null,
|
||||
parent: x.parent && ({
|
||||
id: x.parent.id,
|
||||
|
@ -77,8 +86,11 @@ function mapPage(x) {
|
|||
}),
|
||||
banner: x.banner && ({
|
||||
large_url: x.banner.large_url,
|
||||
large_url_avif: x.banner.large_url_avif,
|
||||
medium_url: x.banner.medium_url,
|
||||
medium_url_avif: x.banner.medium_url_avif,
|
||||
small_url: x.banner.small_url,
|
||||
small_url_avif: x.banner.small_url_avif,
|
||||
}) || null,
|
||||
children: x.children && x.children.map(f => ({
|
||||
id: f.id,
|
||||
|
@ -94,7 +106,8 @@ export async function serveIndex(ctx, path) {
|
|||
let links = null
|
||||
let featured = null
|
||||
let url = frontend + ctx.request.url
|
||||
let image = frontend + '/assets/img/heart.jpg'
|
||||
let image = frontend + '/assets/img/heart.png'
|
||||
let image_avif = frontend + '/assets/img/heart.png'
|
||||
let title = 'NFP Moe - Anime/Manga translation group'
|
||||
let description = 'Small fansubbing and scanlation group translating and encoding our favourite shows from Japan.'
|
||||
try {
|
||||
|
@ -147,8 +160,10 @@ export async function serveIndex(ctx, path) {
|
|||
if (found) {
|
||||
if (found.media) {
|
||||
image = found.media.large_url
|
||||
image_avif = found.media.large_url_avifl
|
||||
} else if (found.banner) {
|
||||
image = found.banner.large_url
|
||||
image_avif = found.banner.large_url_avifl
|
||||
}
|
||||
if (found.description) {
|
||||
description = striptags(found.description)
|
||||
|
@ -174,6 +189,7 @@ export async function serveIndex(ctx, path) {
|
|||
featured: JSON.stringify(featured),
|
||||
url: url,
|
||||
image: image,
|
||||
image_avif: image_avif,
|
||||
title: title,
|
||||
description: description,
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash'
|
|||
|
||||
import config from './config.mjs'
|
||||
import log from './log.mjs'
|
||||
import knex from 'knex'
|
||||
import knex from 'knex-core'
|
||||
|
||||
// This is important for setup to run cleanly.
|
||||
let knexConfig = _.cloneDeep(config.get('knex'))
|
||||
|
|
|
@ -3,8 +3,6 @@ const storageName = 'logintoken'
|
|||
const Authentication = {
|
||||
currentUser: null,
|
||||
isAdmin: false,
|
||||
loadedGoogle: false,
|
||||
loadingGoogle: false,
|
||||
loadingListeners: [],
|
||||
authListeners: [],
|
||||
|
||||
|
@ -32,38 +30,11 @@ const Authentication = {
|
|||
Authentication.isAdmin = item
|
||||
},
|
||||
|
||||
createGoogleScript: function() {
|
||||
if (Authentication.loadedGoogle) return Promise.resolve()
|
||||
return new Promise(function (res) {
|
||||
if (Authentication.loadedGoogle) return res()
|
||||
Authentication.loadingListeners.push(res)
|
||||
|
||||
if (Authentication.loadingGoogle) return
|
||||
Authentication.loadingGoogle = true
|
||||
|
||||
let gscript = document.createElement('script')
|
||||
gscript.type = 'text/javascript'
|
||||
gscript.async = true
|
||||
gscript.defer = true
|
||||
gscript.src = 'https://apis.google.com/js/platform.js?onload=googleLoaded'
|
||||
document.body.appendChild(gscript)
|
||||
})
|
||||
},
|
||||
|
||||
getToken: function() {
|
||||
return localStorage.getItem(storageName)
|
||||
},
|
||||
}
|
||||
|
||||
if (!window.googleLoaded) {
|
||||
window.googleLoaded = function() {
|
||||
Authentication.loadedGoogle = true
|
||||
while (Authentication.loadingListeners.length) {
|
||||
Authentication.loadingListeners.pop()()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Authentication.updateToken(localStorage.getItem(storageName))
|
||||
|
||||
module.exports = Authentication
|
||||
|
|
|
@ -90,12 +90,18 @@ const Frontpage = {
|
|||
if (this.featured && this.featured.banner) {
|
||||
var pixelRatio = window.devicePixelRatio || 1
|
||||
if (deviceWidth < 400 && pixelRatio <= 1) {
|
||||
bannerPath = this.featured.banner.small_url
|
||||
bannerPath = window.supportsavif
|
||||
&& this.featured.banner.small_url_avif
|
||||
|| this.featured.banner.small_url
|
||||
} else if ((deviceWidth < 800 && pixelRatio <= 1)
|
||||
|| (deviceWidth < 600 && pixelRatio > 1)) {
|
||||
bannerPath = this.featured.banner.medium_url
|
||||
bannerPath = window.supportsavif
|
||||
&& this.featured.banner.medium_url_avif
|
||||
|| this.featured.banner.medium_url
|
||||
} else {
|
||||
bannerPath = this.featured.banner.large_url
|
||||
bannerPath = window.supportsavif
|
||||
&& this.featured.banner.large_url_avif
|
||||
|| this.featured.banner.large_url
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,15 @@ require('./polyfill')
|
|||
const m = require('mithril')
|
||||
window.m = m
|
||||
|
||||
/*
|
||||
* imgsupport.js from leechy/imgsupport
|
||||
*/
|
||||
const AVIF = new Image();
|
||||
AVIF.onload = AVIF.onerror = function () {
|
||||
window.supportsavif = (AVIF.height === 2)
|
||||
}
|
||||
AVIF.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';
|
||||
|
||||
m.route.setOrig = m.route.set
|
||||
m.route.set = function(path, data, options){
|
||||
m.route.setOrig(path, data, options)
|
||||
|
|
|
@ -3,54 +3,10 @@ const Authentication = require('../authentication')
|
|||
const Api = require('../api/common')
|
||||
|
||||
const Login = {
|
||||
loadedGoogle: false,
|
||||
loading: false,
|
||||
redirect: '',
|
||||
error: '',
|
||||
|
||||
initGoogleButton: function() {
|
||||
gapi.signin2.render('googlesignin', {
|
||||
'scope': 'email',
|
||||
'width': 240,
|
||||
'height': 50,
|
||||
'longtitle': true,
|
||||
'theme': 'dark',
|
||||
'onsuccess': Login.onGoogleSuccess,
|
||||
'onfailure': Login.onGoogleFailure,
|
||||
})
|
||||
},
|
||||
|
||||
onGoogleSuccess: function(googleUser) {
|
||||
Login.loading = true
|
||||
m.redraw()
|
||||
|
||||
m.request({
|
||||
method: 'POST',
|
||||
url: '/api/login',
|
||||
body: { token: googleUser.Zi.access_token },
|
||||
})
|
||||
.then(function(result) {
|
||||
Authentication.updateToken(result.token)
|
||||
m.route.set(Login.redirect || '/')
|
||||
})
|
||||
.catch(function(error) {
|
||||
Login.error = 'Error while logging into NFP! ' + error.status + ': ' + error.message
|
||||
let auth2 = gapi.auth2.getAuthInstance()
|
||||
return auth2.signOut()
|
||||
})
|
||||
.then(function () {
|
||||
Login.loading = false
|
||||
m.redraw()
|
||||
})
|
||||
},
|
||||
|
||||
onGoogleFailure: function(error) {
|
||||
if (error.error !== 'popup_closed_by_user' && error.error !== 'popup_blocked_by_browser') {
|
||||
Login.error = 'Error while logging into Google: ' + error.error
|
||||
m.redraw()
|
||||
}
|
||||
},
|
||||
|
||||
oninit: function(vnode) {
|
||||
Login.redirect = vnode.attrs.redirect || ''
|
||||
if (Authentication.currentUser) return m.route.set('/')
|
||||
|
@ -62,10 +18,6 @@ const Login = {
|
|||
|
||||
oncreate: function() {
|
||||
if (Authentication.currentUser) return
|
||||
Authentication.createGoogleScript()
|
||||
.then(function() {
|
||||
Login.initGoogleButton()
|
||||
})
|
||||
},
|
||||
|
||||
loginuser: function(vnode, e) {
|
||||
|
@ -136,8 +88,6 @@ const Login = {
|
|||
value: 'Login',
|
||||
}),
|
||||
]),
|
||||
m('h5', { hidden: Login.loading }, 'Alternative login'),
|
||||
m('div#googlesignin', { hidden: Login.loading }, m('div.loading-spinner')),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
|
|
@ -3,23 +3,8 @@ const Authentication = require('../authentication')
|
|||
|
||||
const Logout = {
|
||||
oninit: function() {
|
||||
Authentication.createGoogleScript()
|
||||
.then(function() {
|
||||
return new Promise(function (res) {
|
||||
gapi.load('auth2', res)
|
||||
})
|
||||
})
|
||||
.then(function() { return gapi.auth2.init() })
|
||||
.then(function() {
|
||||
let auth2 = gapi.auth2.getAuthInstance()
|
||||
return auth2.signOut()
|
||||
})
|
||||
.then(function() {
|
||||
Authentication.clearToken()
|
||||
m.route.set('/')
|
||||
}, function(err) {
|
||||
console.log('unable to logout:', err)
|
||||
})
|
||||
},
|
||||
|
||||
view: function() {
|
||||
|
|
|
@ -3,6 +3,8 @@ const Fileinfo = require('./fileinfo')
|
|||
const Newsitem = {
|
||||
view: function(vnode) {
|
||||
var pixelRatio = window.devicePixelRatio || 1
|
||||
var jpegImage = pixelRatio > 1 ? vnode.attrs.media.medium_url : vnode.attrs.media.small_url
|
||||
var avifImage = pixelRatio > 1 ? vnode.attrs.media.medium_url_avif : vnode.attrs.media.small_url_avif
|
||||
return m('newsitem', [
|
||||
m(m.route.Link,
|
||||
{ href: '/article/' + vnode.attrs.path, class: 'title' },
|
||||
|
@ -12,7 +14,14 @@ const Newsitem = {
|
|||
vnode.attrs.media
|
||||
? m('a.cover', {
|
||||
href: '/article/' + vnode.attrs.path,
|
||||
}, m('img', { alt: 'Image for news item ' + vnode.attrs.name, src: pixelRatio > 1 ? vnode.attrs.media.medium_url : vnode.attrs.media.small_url }))
|
||||
},
|
||||
m('picture', [
|
||||
avifImage ? m('source', {
|
||||
srcset: avifImage,
|
||||
type: 'image/avif',
|
||||
}) : null,
|
||||
m('img', { alt: 'Image for news item ' + vnode.attrs.name, src: jpegImage })
|
||||
]))
|
||||
: null,
|
||||
m('div.entrycontent', {
|
||||
class: vnode.attrs.media ? '' : 'extrapadding',
|
||||
|
|
20
migrations/20210104150910_media.js
Normal file
20
migrations/20210104150910_media.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* eslint-disable */
|
||||
exports.up = function(knex) {
|
||||
return Promise.all([
|
||||
knex.schema.table('media', function(table) {
|
||||
table.text('small_image_avif')
|
||||
table.text('medium_image_avif')
|
||||
table.text('large_image_avif')
|
||||
})
|
||||
])
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return Promise.all([
|
||||
knex.schema.table('media', function(table) {
|
||||
table.dropColumn('small_image_avif')
|
||||
table.dropColumn('medium_image_avif')
|
||||
table.dropColumn('large_image_avif')
|
||||
})
|
||||
])
|
||||
};
|
11
package.json
11
package.json
|
@ -46,27 +46,26 @@
|
|||
"homepage": "https://github.com/nfp-projects/nfp_moe",
|
||||
"dependencies": {
|
||||
"@koa/cors": "^2.2.3",
|
||||
"bcrypt": "^3.0.0",
|
||||
"bcrypt": "^3.0.8",
|
||||
"bookshelf": "^0.15.1",
|
||||
"bunyan-lite": "^1.0.1",
|
||||
"dot": "^1.1.2",
|
||||
"format-link-header": "^2.1.0",
|
||||
"googleapis": "^42.0.0",
|
||||
"http-errors": "^1.7.2",
|
||||
"json-mask": "^0.3.8",
|
||||
"jsonwebtoken": "^8.4.0",
|
||||
"knex": "^0.16.3",
|
||||
"koa": "^2.7.0",
|
||||
"knex-core": "^0.19.5",
|
||||
"koa-bodyparser": "^4.2.1",
|
||||
"koa-jwt": "^3.5.1",
|
||||
"koa-lite": "^2.10.1",
|
||||
"koa-router": "^7.4.0",
|
||||
"koa-send": "^5.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"multer": "^1.4.1",
|
||||
"nconf": "^0.10.0",
|
||||
"nconf-lite": "^1.0.1",
|
||||
"parse-torrent": "^7.0.1",
|
||||
"pg": "^7.8.0",
|
||||
"sharp": "^0.22.1",
|
||||
"sharp": "^0.27.0",
|
||||
"striptags": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
BIN
public/assets/img/heart.png
Normal file
BIN
public/assets/img/heart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 436 KiB |
|
@ -1,4 +1,4 @@
|
|||
import Koa from 'koa'
|
||||
import Koa from 'koa-lite'
|
||||
import bodyParser from 'koa-bodyparser'
|
||||
import cors from '@koa/cors'
|
||||
|
||||
|
|
Loading…
Reference in a new issue