1
0
Fork 0

chore: More linting

This commit is contained in:
Joost De Cock 2022-09-15 13:49:55 +02:00
parent 8e187a947a
commit a416b8b860
134 changed files with 1041 additions and 1055 deletions

View file

@ -1,3 +1,5 @@
.git
coverage
node_modules
yarn.lock
package.json

18
.eslintrc.yml Normal file
View file

@ -0,0 +1,18 @@
env:
browser: true
es2021: true
extends: eslint:recommended
overrides:
- files: ["*.yaml", "*.yml"]
plugins: ["yaml"]
extends: ["plugin:yaml/recommended"]
parserOptions:
ecmaVersion: latest
sourceType: module
rules: {}
globals:
it: readonly
describe: readonly
process: readonly
__dirname: readonly

4
.husky/pre-commit Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn lint-staged

View file

@ -3,4 +3,8 @@
# So let's not
*.yml
*.md
yarn.lock
.husky/pre-commit
.prettierignore
.gitignore
.eslintignore

View file

@ -3,16 +3,16 @@ _:
clean: 'rimraf dist'
mbuild: 'NO_MINIFY=1 node build.mjs'
symlink: 'mkdir -p ./node_modules/@freesewing && cd ./node_modules/@freesewing && ln -s -f ../../../* . && cd -'
test: &notests 'echo "{{name}}: No tests configured. Perhaps you''d like to do this?" && exit 0'
test: 'echo "{{name}}: No tests configured. Perhaps you could write some?" && exit 0'
vbuild: 'VERBOSE=1 node build.mjs'
lab: "cd ../../sites/lab && yarn start"
tips: "node ../../scripts/help.mjs"
lab: 'cd ../../sites/lab && yarn start'
tips: 'node ../../scripts/help.mjs'
lint: "npx eslint 'src/**' 'tests/*.mjs'"
_types:
design:
prettier: "npx prettier --write 'src/*.mjs' 'tests/*.mjs'"
test: &test 'npx mocha tests/*.test.mjs'
testci: &testci "npx mocha tests/*.test.mjs --reporter ../../tests/reporters/terse.js"
testci: &testci 'npx mocha tests/*.test.mjs --reporter ../../tests/reporters/terse.js'
plugin:
prettier: "npx prettier --write 'src/*.mjs' 'tests/*.mjs'"
test: *test
@ -20,17 +20,15 @@ _types:
core:
report: 'c8 report'
test: 'c8 mocha tests/*.test.mjs'
testci: "mocha tests/*.test.mjs"
testci: 'mocha tests/*.test.mjs'
prettier: "npx prettier --write 'src/*.mjs' 'tests/*.mjs'"
lint: "npx eslint 'src/*.mjs' 'tests/*.mjs'"
i18n:
prebuild: 'node scripts/prebuilder.mjs'
test: *test
testci: *testci
models:
test: "npx mocha tests/*.test.mjs"
test: 'npx mocha tests/*.test.mjs'
new-design:
build: "SITE=new-design/shared node ../../sites/shared/prebuild/i18n-only.mjs && cp ../../scripts/banner.mjs ./lib && node build.mjs"
build: 'SITE=new-design/shared node ../../sites/shared/prebuild/i18n-only.mjs && cp ../../scripts/banner.mjs ./lib && node build.mjs'
lint: "npx eslint 'lib/*.mjs'"
mbuild: '!'
test: '!'
@ -41,5 +39,4 @@ rehype-jargon:
snapseries:
lint: "npx eslint 'src/*.mjs'"
backend:
lint: "!"
lint: '!'

View file

@ -184,5 +184,5 @@ export const back = {
}
return part
}
},
}

View file

@ -13,7 +13,7 @@ export const base = {
'shoulderSlope',
'waistToHips',
],
optionalMeasurements: [ 'highBust' ],
optionalMeasurements: ['highBust'],
options: {
// Static
brianFitSleeve: true,
@ -37,7 +37,7 @@ export const base = {
frontArmholeDeeper: { pct: 0.2, min: 0, max: 0.5, menu: 'advanced' },
shoulderSlopeReduction: { pct: 0, min: 0, max: 80, menu: 'advanced' },
},
plugins: [ pluginBundle, bustPlugin ],
plugins: [pluginBundle, bustPlugin],
draft: ({
measurements,
options,
@ -53,7 +53,6 @@ export const base = {
macro,
part,
}) => {
store.set('shoulderEase', (measurements.shoulderToShoulder * options.shoulderEase) / 2)
// Center back (cb) vertical axis
@ -63,7 +62,10 @@ export const base = {
points.cbHips = new Point(0, points.cbWaist.y + measurements.waistToHips)
// Shoulder line
points.neck = new Point((measurements.neck * (1 + options.collarEase)) / options.collarFactor, 0)
points.neck = new Point(
(measurements.neck * (1 + options.collarEase)) / options.collarFactor,
0
)
points.hps = points.neck.clone() // We started using HPS in many measurements
// Shoulder point using shoulderSlope degree measurement
points.shoulder = utils.beamsIntersect(
@ -76,7 +78,8 @@ export const base = {
points.cbShoulder = new Point(0, points.shoulder.y)
points.cbArmhole = new Point(
0,
points.shoulder.y + measurements.biceps * (1 + options.bicepsEase) * options.armholeDepthFactor
points.shoulder.y +
measurements.biceps * (1 + options.bicepsEase) * options.armholeDepthFactor
)
// Now take shoulder slope reduction into account
@ -87,7 +90,10 @@ export const base = {
points.cbHem = new Point(0, points.cbHips.y * (1 + options.lengthBonus))
// Side back (cb) vertical axis
points.armhole = new Point((measurements.chest * (1 + options.chestEase)) / 4, points.cbArmhole.y)
points.armhole = new Point(
(measurements.chest * (1 + options.chestEase)) / 4,
points.cbArmhole.y
)
points.waist = new Point(points.armhole.x, points.cbWaist.y)
points.hips = new Point(points.armhole.x, points.cbHips.y)
points.hem = new Point(points.armhole.x, points.cbHem.y)
@ -170,10 +176,10 @@ export const base = {
points.gridAnchor = points.cbHem
/*
* People would like to have the option to shift the shoulder seam
* See https://github.com/freesewing/freesewing/issues/642
* So let's make the people happy
*/
* People would like to have the option to shift the shoulder seam
* See https://github.com/freesewing/freesewing/issues/642
* So let's make the people happy
*/
// Front armhole is a bit deeper, add those points
let deeper = measurements.chest * options.frontArmholeDeeper
for (const p of ['', 'Cp1', 'Cp2']) {
@ -210,6 +216,5 @@ export const base = {
}
return part
}
},
}

View file

@ -20,7 +20,6 @@ export const front = {
utils,
part,
}) => {
// Re-use points for deeper armhole at the front
points.armholePitchCp1 = points.frontArmholePitchCp1
points.armholePitch = points.frontArmholePitch
@ -62,7 +61,9 @@ export const front = {
.curve_(points.mirroredNeckCp2, points.mirroredCbNeck)
.split(points.s3CollarSplit)[0]
.reverse()
.join(new Path().move(points.hps).curve(points.neckCp2Front, points.cfNeckCp1, points.cfNeck))
.join(
new Path().move(points.hps).curve(points.neckCp2Front, points.cfNeckCp1, points.cfNeck)
)
.setRender(false)
}
if (options.s3Armhole < 0.1 && options.s3Armhole > -0.1) {
@ -194,5 +195,5 @@ export const front = {
}
return part
}
},
}

View file

@ -10,7 +10,7 @@ import { sleevecap } from './sleevecap.mjs'
// Setup our new design
const Brian = new Design({
data,
parts: [ back, front, sleeve ]
parts: [back, front, sleeve],
})
// Named exports

View file

@ -4,12 +4,9 @@ export const sleeve = {
from: sleevecap,
name: 'brian.sleeve',
options: {
sleeveLengthBonus: { pct: 0, min: -40, max: 10, menu: 'style' }
sleeveLengthBonus: { pct: 0, min: -40, max: 10, menu: 'style' },
},
measurements: [
'shoulderToWrist',
'wrist',
],
measurements: ['shoulderToWrist', 'wrist'],
draft: ({
store,
sa,
@ -26,7 +23,6 @@ export const sleeve = {
macro,
part,
}) => {
// Remove things inherited
macro('cutonfold', false)
macro('rmad')
@ -40,7 +36,10 @@ export const sleeve = {
// Wrist
points.centerWrist = points.sleeveTop.shift(-90, store.get('sleeveLength'))
points.wristRight = points.centerWrist.shift(0, (measurements.wrist * (1 + options.cuffEase)) / 2)
points.wristRight = points.centerWrist.shift(
0,
(measurements.wrist * (1 + options.cuffEase)) / 2
)
points.wristLeft = points.wristRight.rotate(180, points.centerWrist)
// Paths
@ -70,7 +69,9 @@ export const sleeve = {
macro('scalebox', { at: points.scalebox })
points.frontNotch = paths.sleevecap.shiftAlong(store.get('frontArmholeToArmholePitch'))
points.backNotch = paths.sleevecap.reverse().shiftAlong(store.get('backArmholeToArmholePitch'))
points.backNotch = paths.sleevecap
.reverse()
.shiftAlong(store.get('backArmholeToArmholePitch'))
snippets.frontNotch = new Snippet('notch', points.frontNotch)
snippets.backNotch = new Snippet('bnotch', points.backNotch)
if (sa) paths.sa = paths.seam.offset(sa).attr('class', 'fabric sa')
@ -104,5 +105,5 @@ export const sleeve = {
})
}
return part
}
},
}

View file

@ -166,7 +166,6 @@ export const sleevecap = {
sleeveWidthGuarantee: { pct: 90, min: 25, max: 100, menu: 'advanced' },
},
draft: ({ store, units, options, Point, points, paths, log, snippets, macro, part }) => {
// Clean up from fron
for (const path in paths) delete paths[path]
delete snippets.logo
@ -190,5 +189,5 @@ export const sleevecap = {
points.gridAnchor = new Point(0, 0)
return part
}
},
}

View file

@ -359,6 +359,5 @@ export {
// Docs
docs_coords,
docs_overview,
Examples,
}

View file

@ -69,7 +69,10 @@ export const stacks_leftEye = {
stack,
after: stacks_top,
draft: ({ store, Point, points, part }) => {
points.leftEye = new Point(store.get('x') + store.get('size') * 0.35, store.get('y') + store.get('size') * 0.4)
points.leftEye = new Point(
store.get('x') + store.get('size') * 0.35,
store.get('y') + store.get('size') * 0.4
)
.attr('data-circle', store.get('size') * 0.1)
.attr('data-circle-class', 'stroke-6xl')
@ -82,7 +85,10 @@ export const stacks_rightEye = {
stack,
after: stacks_top,
draft: ({ store, Point, points, part }) => {
points.rightEye = new Point(store.get('x') + store.get('size') * 0.65, store.get('y') + store.get('size') * 0.4)
points.rightEye = new Point(
store.get('x') + store.get('size') * 0.65,
store.get('y') + store.get('size') * 0.4
)
.attr('data-circle', store.get('size') * 0.08)
.attr('data-circle-class', 'stroke-7xl')
@ -95,10 +101,22 @@ export const stacks_mouth = {
stack,
after: stacks_top,
draft: ({ store, Point, points, paths, Path, part }) => {
points.left = new Point(store.get('x') + store.get('size') * 0.15, store.get('y') + store.get('size') * 0.5)
points.right = new Point(store.get('x') + store.get('size') * 0.85, store.get('y') + store.get('size') * 0.5)
points.leftCp = new Point(store.get('x') + store.get('size') * 0.35, store.get('y') + store.get('size') * 0.8)
points.rightCp = new Point(store.get('x') + store.get('size') * 0.65, store.get('y') + store.get('size') * 0.8)
points.left = new Point(
store.get('x') + store.get('size') * 0.15,
store.get('y') + store.get('size') * 0.5
)
points.right = new Point(
store.get('x') + store.get('size') * 0.85,
store.get('y') + store.get('size') * 0.5
)
points.leftCp = new Point(
store.get('x') + store.get('size') * 0.35,
store.get('y') + store.get('size') * 0.8
)
points.rightCp = new Point(
store.get('x') + store.get('size') * 0.65,
store.get('y') + store.get('size') * 0.8
)
paths.mouth = new Path()
.move(points.left)

View file

@ -30,7 +30,6 @@ function hugoFront({
macro,
part,
}) {
// Remove clutter
for (const key in paths) {
if (key !== 'seam') delete paths[key]

View file

@ -1,7 +1,18 @@
import { front } from './front.mjs'
function hugoPocket({ utils, store, sa, points, Path, paths, complete, paperless, macro, snippets, part }) {
function hugoPocket({
utils,
store,
sa,
points,
Path,
paths,
complete,
paperless,
macro,
snippets,
part,
}) {
// Remove clutter
for (const key in paths) {
if (key !== 'pocket') delete paths[key]

View file

@ -1,7 +1,6 @@
import { pocket } from './pocket.mjs'
function hugoPocketFacing({ sa, points, Path, paths, complete, paperless, macro, store, part }) {
// Remove clutter
for (const key in paths) {
if (key !== 'facing') delete paths[key]

View file

@ -19,7 +19,6 @@ function svenWaistband(params) {
})
}
return part
}

View file

@ -17,7 +17,6 @@ export const bib = {
paperless,
part,
}) => {
// Construct the neck opening
let tweak = 1
let target = (measurements.head * options.neckRatio) / 4
@ -230,5 +229,5 @@ export const bib = {
}
return part
}
},
}

View file

@ -5,7 +5,7 @@ import { pluginBundle } from '@freesewing/plugin-bundle'
export const configpart = {
name: 'tutorial.configpart',
measurements: ['head'],
plugins: [ pluginBundle ],
plugins: [pluginBundle],
options: {
size: { pct: 50, min: 10, max: 100 },
neckRatio: { pct: 80, min: 70, max: 90 },
@ -13,6 +13,5 @@ export const configpart = {
lengthRatio: { pct: 75, min: 55, max: 85 },
},
hide: true,
draft: ({part}) => part
draft: ({ part }) => part,
}

View file

@ -8,20 +8,7 @@ import { bib } from './bib.mjs'
// Setup our new design
const Tutorial = new Design({
data,
parts: [
step1,
step2,
step3,
step4,
step5,
step6,
step7,
step8,
step9,
step10,
step11,
bib,
]
parts: [step1, step2, step3, step4, step5, step6, step7, step8, step9, step10, step11, bib],
})
// Named exports
@ -38,6 +25,5 @@ export {
step10,
step11,
bib,
Tutorial
Tutorial,
}

View file

@ -17,7 +17,6 @@ export const step1 = {
macro,
part,
}) => {
let w = 500 * options.size
points.topLeft = new Point(0, 0)
points.topRight = new Point(w, 0)
@ -62,46 +61,31 @@ export const step1 = {
}
return part
}
},
}
export const step2 = {
name: 'tutorial.step2',
after: configpart,
draft: ({
Point,
points,
Path,
paths,
measurements,
part,
}) => {
draft: ({ Point, points, Path, paths, measurements, part }) => {
points.right = new Point(measurements.head / 10, 0)
points.bottom = new Point(0, measurements.head / 12)
points.rightCp1 = points.right.shift(90, points.bottom.dy(points.right) / 2)
points.bottomCp2 = points.bottom.shift(0, points.bottom.dx(points.right) / 2)
paths.neck = new Path().move(points.right).curve(points.rightCp1, points.bottomCp2, points.bottom)
paths.neck = new Path()
.move(points.right)
.curve(points.rightCp1, points.bottomCp2, points.bottom)
return part
}
},
}
export const step3 = {
name: 'tutorial.step3',
after: configpart,
draft: ({
Point,
points,
Path,
paths,
measurements,
options,
part,
}) => {
draft: ({ Point, points, Path, paths, measurements, options, part }) => {
let tweak = 1
let target = (measurements.head * options.neckRatio) / 4
let delta
@ -122,19 +106,13 @@ export const step3 = {
} while (Math.abs(delta) > 1)
return part
}
},
}
export const step4 = {
name: 'tutorial.step4',
from: step3,
draft: ({
points,
Path,
paths,
part,
}) => {
draft: ({ points, Path, paths, part }) => {
points.rightCp2 = points.rightCp1.flipY()
points.bottomCp1 = points.bottomCp2.flipX()
@ -155,6 +133,5 @@ export const step4 = {
.close()
return part
}
},
}

View file

@ -3,16 +3,7 @@ import { step4 } from './step1-4.mjs'
export const step5 = {
name: 'tutorial.step5',
from: step4,
draft: ({
Point,
points,
Path,
paths,
measurements,
options,
part,
}) => {
draft: ({ Point, points, Path, paths, measurements, options, part }) => {
const width = measurements.head * options.widthRatio
const length = measurements.head * options.lengthRatio
@ -30,20 +21,13 @@ export const step5 = {
.close()
return part
}
},
}
export const step6 = {
name: 'tutorial.step6',
from: step5,
draft: ({
Point,
points,
Path,
paths,
part,
}) => {
draft: ({ Point, points, Path, paths, part }) => {
points.edgeLeft = new Point(points.topLeft.x, points.left.y)
points.edgeRight = new Point(points.topRight.x, points.right.y)
points.edgeTop = new Point(0, points.topLeft.y)
@ -63,19 +47,13 @@ export const step6 = {
.close()
return part
}
},
}
export const step7 = {
name: 'tutorial.step7',
from: step6,
draft: ({
Point,
points,
macro,
part,
}) => {
draft: ({ Point, points, macro, part }) => {
const strap = points.edgeTop.dy(points.top)
points.tipRight = points.edgeTop.translate(strap / 2, strap / 2)
@ -98,20 +76,13 @@ export const step7 = {
})
return part
}
},
}
export const step8 = {
name: 'tutorial.step8',
from: step7,
draft: ({
points,
Path,
paths,
macro,
part,
}) => {
draft: ({ points, Path, paths, macro, part }) => {
const rotateThese = [
'edgeTopLeftCp',
'edgeTop',
@ -160,6 +131,5 @@ export const step8 = {
.close()
return part
}
},
}

View file

@ -3,13 +3,7 @@ import { step8 } from './step5-8.mjs'
export const step9 = {
name: 'tutorial.step9',
from: step8,
draft: ({
points,
Path,
paths,
part,
}) => {
draft: ({ points, Path, paths, part }) => {
points.edgeTopRightCp = points.edgeTopLeftCp.flipX()
points.topCp1 = points.topCp2.flipX()
points.tipLeftTopStart = points.tipRightTopStart.flipX()
@ -44,20 +38,13 @@ export const step9 = {
delete paths.rect
return part
}
},
}
export const step10 = {
name: 'tutorial.step10',
from: step9,
draft: ({
points,
Path,
paths,
macro,
part,
}) => {
draft: ({ points, Path, paths, macro, part }) => {
macro('round', {
from: points.topLeft,
to: points.bottomRight,
@ -95,23 +82,13 @@ export const step10 = {
.close()
return part
}
},
}
export const step11 = {
name: 'tutorial.step11',
from: step10,
draft: ({
Point,
points,
paths,
macro,
complete,
snippets,
Snippet,
part,
}) => {
draft: ({ Point, points, paths, macro, complete, snippets, Snippet, part }) => {
// Complete?
if (complete) {
snippets.snapStud = new Snippet('snap-stud', points.snapLeft)
@ -138,6 +115,5 @@ export const step11 = {
}
return part
}
},
}

View file

@ -17,7 +17,6 @@ function ursulaBack({
macro,
part,
}) {
// Design pattern here
// Create points

View file

@ -12,7 +12,6 @@ function ursulaElastic({
macro,
part,
}) {
// Stretch utility method
store.set('elasticScale', utils.stretchToScale(options.elasticStretch))

View file

@ -17,7 +17,6 @@ function ursulaFront({
macro,
part,
}) {
// Stretch utility method
store.set('xScale', utils.stretchToScale(options.fabricStretch))
@ -217,6 +216,6 @@ export const front = {
frontDip: { pct: 5.0, min: -5, max: 15, menu: 'style' },
taperToGusset: { pct: 70, min: 5, max: 100, menu: 'style' },
},
plugins: [ pluginBundle ],
plugins: [pluginBundle],
draft: ursulaFront,
}

View file

@ -14,7 +14,6 @@ function ursulaGusset({
macro,
part,
}) {
// Create points
points.frontGussetLeft = new Point(store.get('frontGussetLeft').x, 0)
points.backGussetLeft = new Point(

View file

@ -16,7 +16,6 @@ function wahidBack({
snippets,
part,
}) {
// Cleanup from Brian
for (let i of Object.keys(paths)) delete paths[i]
delete snippets.armholePitchNotch

View file

@ -35,7 +35,6 @@ function wahidFront({
store,
part,
}) {
// Cleanup from Brian
for (let i of Object.keys(paths)) delete paths[i]
delete snippets.armholePitchNotch

View file

@ -13,7 +13,6 @@ function wahidFrontFacing({
sa,
part,
}) {
// Cleanup from front part
for (let i of Object.keys(paths).filter((name) => name !== 'grainline')) delete paths[i]
for (let i of Object.keys(snippets)) delete snippets[i]

View file

@ -13,7 +13,6 @@ function wahidFrontLining({
sa,
part,
}) {
// Cleanup from Brian
for (let i of Object.keys(paths)) delete paths[i]
for (let i of Object.keys(snippets)) delete snippets[i]

View file

@ -13,7 +13,6 @@ function wahidPocketbag({
store,
part,
}) {
let pw = measurements.hips * options.pocketWidth // Pocket width
let ph = store.get('pocketBagLength') // Pocket height
points.topLeft = new Point(0, 0)

View file

@ -13,7 +13,6 @@ function wahidPocketFacing({
store,
part,
}) {
const pw = measurements.hips * options.pocketWidth // Pocket width
const pwh = pw * options.weltHeight // Pocket welt height
const ph = store.get('pocketBagLength') + pwh // Pocket height

View file

@ -12,7 +12,6 @@ function wahidPocketInterfacing({
paperless,
part,
}) {
const pw = measurements.hips * options.pocketWidth // Pocket width
const pwh = pw * options.weltHeight // Pocket welt height
points.topLeft = new Point(0, 0)

View file

@ -12,7 +12,6 @@ function wahidPocketWelt({
paperless,
part,
}) {
const pw = measurements.hips * options.pocketWidth // Pocket width
const pwh = pw * options.weltHeight // Pocket welt height
points.topLeft = new Point(0, 0)

View file

@ -1,14 +1,6 @@
import { base } from './base.mjs'
function walburgaBack({
points,
macro,
complete,
snippets,
Snippet,
part,
}) {
function walburgaBack({ points, macro, complete, snippets, Snippet, part }) {
// Complete?
if (complete) {
// logo & title

View file

@ -17,7 +17,6 @@ function walburgaBase({
utils,
part,
}) {
// define some variables
const hem_pos =
options.length === 'toKnee'

View file

@ -16,7 +16,6 @@ function walburgaFront({
utils,
part,
}) {
const head = store.get('hhead') * 2
const goldenRatio = store.get('goldenRatio')
const ratio = goldenRatio * options.neckoRatio

View file

@ -15,7 +15,6 @@ function waraleeBackPocket({
sa,
part,
}) {
if (false == options.backPocket) {
return part
}

View file

@ -13,7 +13,6 @@ function waraleeCutout({
macro,
part,
}) {
let separateWaistband = options.separateWaistband
if ('waistband' == options.frontPocketStyle) {
separateWaistband = true

View file

@ -15,7 +15,6 @@ function waraleeFacings({
sa,
part,
}) {
let frontPocketSize =
options.frontPocketSize * measurements.crotchDepth /*- measurements.waistToHips*/
let backPocketSize =

View file

@ -4,18 +4,7 @@ import { pantsProto } from './pantsproto.mjs'
// To keep you from printing it completely, you could print this part in paperless mode
// and only have a single sheet with all the dimensions on it.
function waraleeMini({
options,
Path,
points,
paths,
complete,
sa,
macro,
store,
part,
}) {
function waraleeMini({ options, Path, points, paths, complete, sa, macro, store, part }) {
let mini = options.minimizer
let separateWaistband = options.separateWaistband
if ('waistband' == options.frontPocketStyle) {

View file

@ -14,7 +14,6 @@ function waraleePants({
store,
part,
}) {
let separateWaistband = options.separateWaistband
if ('waistband' == options.frontPocketStyle) {
separateWaistband = true

View file

@ -1,17 +1,7 @@
import { pluginBundle } from '@freesewing/plugin-bundle'
import * as options from './options.mjs'
function waraleePantsProto({
options,
measurements,
Point,
Path,
points,
paths,
store,
part,
}) {
function waraleePantsProto({ options, measurements, Point, Path, points, paths, store, part }) {
let seatDepth =
measurements.crotchDepth /* - measurements.waistToHips */ *
(1 + options.waistRaise) *

View file

@ -15,7 +15,6 @@ function waraleePocket({
macro,
part,
}) {
if (false == options.frontPocket) {
return part
}

View file

@ -1,22 +1,24 @@
import { pantsProto } from './pantsproto.mjs'
function waraleeWaistband(type, {
options,
measurements,
Point,
Path,
points,
paths,
Snippet,
snippets,
complete,
paperless,
macro,
sa,
store,
part,
}) {
function waraleeWaistband(
type,
{
options,
measurements,
Point,
Path,
points,
paths,
Snippet,
snippets,
complete,
paperless,
macro,
sa,
store,
part,
}
) {
const WidthReduction = 6
let waistBand = store.get('waistBand')
let waistBandLengthFront = points.fWaistSideHem.dist(points.fWaistFrontOverlapHem)

View file

@ -15,7 +15,6 @@ function yuriBack({
measurements,
part,
}) {
// Clear paths from Brian
for (const i in paths) {
if (['backArmhole', 'backCollar'].indexOf(i) === -1) delete paths[i]

View file

@ -17,7 +17,6 @@ function yuriFront({
Snippet,
part,
}) {
// Clear paths from Brian
for (const i in paths) {
if (['frontArmhole', 'frontCollar'].indexOf(i) === -1) delete paths[i]

View file

@ -14,7 +14,6 @@ function yuriGusset({
store,
part,
}) {
const w = store.get('gussetLength')
points.top = new Point(0, 0)
points.bottom = new Point(0, w)

View file

@ -13,7 +13,6 @@ function yuriHoodCenter({
units,
part,
}) {
const width = store.get('hoodCenterWidth')
const length = complete ? width * 2.5 : store.get('hoodCenterLength')
points.topLeft = new Point(0, 0)

View file

@ -16,7 +16,6 @@ function yuriHoodSide({
macro,
part,
}) {
const neckOpening = store.get('frontNeckSeamLength') + store.get('backNeckSeamLength')
const hoodOpening = measurements.head
const neckCutoutDelta = store.get('neckCutoutFront') - store.get('neckCutoutBack')

View file

@ -1,17 +1,6 @@
import { sleeve as brianSleeve } from '@freesewing/brian'
function yuriSleeve({
Point,
Path,
points,
paths,
complete,
sa,
paperless,
macro,
part,
}) {
function yuriSleeve({ Point, Path, points, paths, complete, sa, paperless, macro, part }) {
// Clear paths from Brian, but keep sleevecap
for (let p of Object.keys(paths)) {
if (p !== 'sleevecap') delete paths[p]

View file

@ -15,7 +15,7 @@
"?": "node scripts/help.mjs",
"tips": "node scripts/help.mjs",
"lab": "cd sites/lab && yarn start",
"kickstart": "npx lerna bootstrap && yarn buildall && yarn tips",
"kickstart": "npx lerna bootstrap && yarn buildall && yarn prepare && yarn tips",
"cleanall": "lerna run clean",
"test": "lerna run test",
"prettier": "npx prettier --write 'packages/**/src/*.mjs' 'packages/**/src/*.js' 'packages/i18n/src/locales/**/*.*' 'packages/**/tests/*.mjs'",
@ -24,7 +24,10 @@
"buildall": "lerna run cibuild_step0 && lerna run cibuild_step1 && lerna run cibuild_step2 && lerna run cibuild_step3 && lerna run cibuild_step4 && lerna run cibuild_step5 && lerna run cibuild_step6 && lerna run cibuild_step7",
"build": "yarn buildall",
"testall": "node scripts/testall.js",
"lint": "lerna run lint",
"lint": "lerna run lint -- ",
"qa": "yarn qa:prettier && yarn qa:lint",
"qa:prettier": "npx prettier",
"qa:lint": "npx eslint",
"release": "lerna exec --no-bail -- npm publish",
"postrelease": "git add . && git commit -m ':bookmark: v$npm_package_version' && git tag -a v$npm_package_version -m ':bookmark: FreeSewing v$npm_package_version'",
"ship": "lerna exec --no-bail -- npm publish",
@ -35,7 +38,8 @@
"famgen": "all-contributors generate",
"checkdocs": "remark markdown --quiet --frail",
"strapi:translate": "node scripts/strapi-en-to-other.mjs",
"fixdocs": "remark markdown --quiet --frail --output"
"fixdocs": "remark markdown --quiet --frail --output",
"prepare": "husky install"
},
"repository": {
"type": "git",
@ -45,10 +49,11 @@
"url": "https://github.com/freesewing/freesewing/issues"
},
"prettier": "@freesewing/prettier-config",
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
"lint-staged": {
"*": [
"npx prettier --write",
"npx eslint"
]
},
"devDependencies": {
"@commitlint/cli": "^17.0.2",
@ -67,16 +72,18 @@
"cross-env": "^7.0.2",
"esbuild": "^0.15.3",
"esbuild-plugin-yaml": "^0.0.1",
"eslint": "^8.23.1",
"eslint-plugin-yaml": "^0.5.0",
"esm": "^3.2.25",
"handlebars": "^4.7.7",
"husky": "^8.0.1",
"js-yaml": "^4.0.0",
"lerna": "^5.1.4",
"lint-staged": "^13.0.3",
"mocha": "^10.0.0",
"mustache": "^4.0.1",
"nyc": "^15.1.0",
"prettier": "^2.3.0",
"prettier": "^2.7.1",
"pretty-quick": "^3.0.0",
"prop-types": "^15.7.2",
"react": "^17.0.2",

View file

@ -5,7 +5,6 @@ const expect = chai.expect
describe('Pattern', () => {
describe('Pattern.draft()', () => {
it('Pattern.draft() should draft according to settings', () => {
let count = 0
const back = {

View file

@ -31,13 +31,12 @@
"clean": "rimraf dist",
"mbuild": "NO_MINIFY=1 node build.mjs",
"symlink": "mkdir -p ./node_modules/@freesewing && cd ./node_modules/@freesewing && ln -s -f ../../../* . && cd -",
"test": "npx mocha tests/*.test.mjs",
"test": "echo \"i18n: No tests configured. Perhaps you'd like to do this?\" && exit 0",
"vbuild": "VERBOSE=1 node build.mjs",
"lab": "cd ../../sites/lab && yarn start",
"tips": "node ../../scripts/help.mjs",
"lint": "npx eslint 'src/**' 'tests/*.mjs'",
"prebuild": "node scripts/prebuilder.mjs",
"testci": "npx mocha tests/*.test.mjs --reporter ../../tests/reporters/terse.js",
"precibuild_step7": "node scripts/prebuilder.mjs",
"cibuild_step7": "node build.mjs"
},

View file

@ -1,5 +1,5 @@
import chai from 'chai'
import { strings as i18n } from './dist/index.mjs'
import * as i18n from '../prebuild/strings.js'
const expect = chai.expect
@ -27,13 +27,12 @@ const languages = [
]
function checkTranslations(from, to) {
const originals = Object.keys(from.strings)
const translated = to.strings
for (let string of originals) {
for (const string in from.strings) {
if (typeof translated[string] === 'undefined') {
console.log(`String ${string} in ${from.name} is not available in ${to.name}`)
expect(typeof translated[string]).to.equal('string')
}
expect(typeof translated[string]).to.equal('string')
}
}

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { bannerPlugin } from './dist/index.mjs'
import { bannerPlugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,15 +1,15 @@
import { bannerPlugin } from '../../plugin-banner/src/index.mjs'
import { bartackPlugin } from '../../plugin-bartack/src/index.mjs'
import { buttonsPlugin } from '../../plugin-buttons/src/index.mjs'
import { bannerPlugin } from '../../plugin-banner/src/index.mjs'
import { bartackPlugin } from '../../plugin-bartack/src/index.mjs'
import { buttonsPlugin } from '../../plugin-buttons/src/index.mjs'
import { cutonfoldPlugin } from '../../plugin-cutonfold/src/index.mjs'
import { dimensionPlugin } from '../../plugin-dimension/src/index.mjs'
import { grainlinePlugin } from '../../plugin-grainline/src/index.mjs'
import { logoPlugin } from '../../plugin-logo/src/index.mjs'
import { mirrorPlugin } from '../../plugin-mirror/src/index.mjs'
import { notchesPlugin } from '../../plugin-notches/src/index.mjs'
import { titlePlugin } from '../../plugin-title/src/index.mjs'
import { scaleboxPlugin } from '../../plugin-scalebox/src/index.mjs'
import { roundPlugin } from '../../plugin-round/src/index.mjs'
import { logoPlugin } from '../../plugin-logo/src/index.mjs'
import { mirrorPlugin } from '../../plugin-mirror/src/index.mjs'
import { notchesPlugin } from '../../plugin-notches/src/index.mjs'
import { titlePlugin } from '../../plugin-title/src/index.mjs'
import { scaleboxPlugin } from '../../plugin-scalebox/src/index.mjs'
import { roundPlugin } from '../../plugin-round/src/index.mjs'
import { sprinklePlugin } from '../../plugin-sprinkle/src/index.mjs'
import { measurementsPlugin } from '../../plugin-measurements/src/index.mjs'
import { name, version } from '../data.mjs'
@ -66,4 +66,3 @@ export const plugin = {
// More specifically named exports
export const bundlePlugin = plugin
export const pluginBundle = plugin

View file

@ -3,7 +3,7 @@ import chai from 'chai'
const expect = chai.expect
describe('Round Plugin Tests', () => {
it("FIXME: No plugin tests configured", () => {
it('FIXME: No plugin tests configured', () => {
expect(1).to.equal(1)
})
})

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from './dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from './dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from '../dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design, round } from '@freesewing/core'
import { plugin } from './dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design, round } from '@freesewing/core'
import { plugin } from './dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from './dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from '../dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from './dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from '../dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from '../dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -41,4 +41,3 @@ export const plugin = {
// More specifically named exports
export const roundPlugin = plugin
export const pluginRound = plugin

View file

@ -3,7 +3,7 @@ import chai from 'chai'
const expect = chai.expect
describe('Round Plugin Tests', () => {
it("FIXME: No plugin tests configured", () => {
it('FIXME: No plugin tests configured', () => {
expect(1).to.equal(1)
})
})

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design, round } from '@freesewing/core'
import { plugin } from '../dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,6 +1,6 @@
import chai from 'chai'
//import freesewing from '@freesewing/core'
//import plugin from '../dist/index.mjs'
//import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -15,4 +15,3 @@ export const svgAttrPlugin = plugin
export const svgattrPlugin = plugin
export const pluginSvgAttr = plugin
export const pluginSvgattr = plugin

View file

@ -1,9 +1,9 @@
import chai from 'chai'
const expect = chai.expect;
const expect = chai.expect
describe('SVG Attributed Plugin Tests', () => {
it("FIXME: No plugin tests defined", () => {
it('FIXME: No plugin tests defined', () => {
expect(1).to.equal(1)
})
})

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from '../dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -15,4 +15,3 @@ export const plugin = {
// More specifically named exports
export const versionfreeSvgPlugin = plugin
export const pluginVersionfreeSvg = plugin

View file

@ -1,6 +1,6 @@
import chai from 'chai'
import { Design } from '@freesewing/core'
import { plugin } from '../dist/index.mjs'
import { plugin } from '../src/index.mjs'
const expect = chai.expect

View file

@ -1,4 +1,4 @@
import { withBreasts, withoutBreasts } from '@freesewing/models';
import { withBreasts, withoutBreasts } from '@freesewing/models'
export default {
users: [
@ -10,15 +10,15 @@ export default {
role: 'user',
settings: {
language: 'nl',
units: 'imperial'
units: 'imperial',
},
patron: 2,
consent: {
profile: true,
measurements: true,
openData: true
openData: true,
},
status: 'active'
status: 'active',
},
{
email: 'test@freesewing.org',
@ -28,15 +28,15 @@ export default {
role: 'user',
settings: {
language: 'nl',
units: 'imperial'
units: 'imperial',
},
patron: 4,
consent: {
profile: true,
measurements: true,
openData: true
openData: true,
},
status: 'active'
status: 'active',
},
{
email: 'admin@freesewing.org',
@ -52,16 +52,16 @@ export default {
patron: 8,
settings: {
language: 'en',
units: 'metric'
units: 'metric',
},
consent: {
profile: true,
measurements: true,
openData: true
openData: true,
},
newsletter: true,
status: 'active'
}
status: 'active',
},
],
people: [
{
@ -72,7 +72,7 @@ export default {
breasts: false,
units: 'metric',
notes: 'This is an example person',
measurements: withoutBreasts.size42
measurements: withoutBreasts.size42,
},
{
handle: 'persb',
@ -84,37 +84,37 @@ export default {
notes: 'This is an example person',
measurements: {
...withBreasts.size36,
doesNotExist: 234
}
doesNotExist: 234,
},
},
],
patterns: [
{
handle: "recip",
name: "Example pattern",
notes: "These are the pattern notes",
handle: 'recip',
name: 'Example pattern',
notes: 'These are the pattern notes',
data: {
settings: {
sa: 10,
complete: true,
paperless: false,
units: "imperial",
units: 'imperial',
measurements: {
biceps: 335,
hpsToWaist: 520,
chest: 1080,
waistToHips: 145,
neck: 420,
shoulderSlope: 13,
shoulderSlope: 13,
shoulderToShoulder: 465,
hips: 990
}
hips: 990,
},
},
design: "aaron",
design: 'aaron',
},
created: "2019-08-14T09:47:27.163Z",
created: '2019-08-14T09:47:27.163Z',
user: 'tuser',
person:"persa"
}
]
person: 'persa',
},
],
}

View file

@ -20,7 +20,7 @@ verifyConfig(config, chalk)
mongoose.Promise = global.Promise
mongoose
.connect(config.db.uri, {
useNewUrlParser: true
useNewUrlParser: true,
})
.then(() => {
console.log(chalk.green('Successfully connected to the database'))
@ -37,7 +37,7 @@ mongoose
}
})
})
.catch(err => {
.catch((err) => {
console.log(chalk.red('Could not connect to the database. Exiting now...'), err)
process.exit()
})

View file

@ -9,7 +9,11 @@ export const showHelp = () => {
console.log()
console.log(' ', chalk.bold.blue('npm run clear:users'), '👉 Truncate the users collection')
console.log(' ', chalk.bold.blue('npm run clear:people'), '👉 Truncate the people collection')
console.log(' ', chalk.bold.blue('npm run clear:patterns'), '👉 Truncate the patterns collection')
console.log(
' ',
chalk.bold.blue('npm run clear:patterns'),
'👉 Truncate the patterns collection'
)
console.log(
' ',
chalk.bold.blue('npm run clear:confirmations'),
@ -26,33 +30,33 @@ export const showHelp = () => {
}
export const clearUsers = async () => {
await User.deleteMany().then(result => {
if (result.ok) console.log('🔥 Users deleted')
else console.log('🚨 Could not remove users', result)
await User.deleteMany().then((result) => {
if (result.ok) console.log('🔥 Users deleted')
else console.log('🚨 Could not remove users', result)
})
}
export const clearPeople = async () => {
await Person.deleteMany().then(result => {
if (result.ok) console.log('🔥 People removed')
else console.log('🚨 Could not remove people', result)
await Person.deleteMany().then((result) => {
if (result.ok) console.log('🔥 People removed')
else console.log('🚨 Could not remove people', result)
})
}
export const clearPatterns = async () => {
await Pattern.deleteMany().then(result => {
if (result.ok) console.log('🔥 Patterns deleted')
else console.log('🚨 Could not remove patterns', result)
await Pattern.deleteMany().then((result) => {
if (result.ok) console.log('🔥 Patterns deleted')
else console.log('🚨 Could not remove patterns', result)
})
}
export const clearConfirmations = async () => {
await Confirmation.deleteMany().then(result => {
if (result.ok) console.log('🔥 Confirmations deleted')
else console.log('🚨 Could not remove confirmations', result)
await Confirmation.deleteMany().then((result) => {
if (result.ok) console.log('🔥 Confirmations deleted')
else console.log('🚨 Could not remove confirmations', result)
})
}
export const clearNewsletterSubscribers = async () => {
await Newsletter.deleteMany().then(result => {
if (result.ok) console.log('🔥 Newsletter subscriptions deleted')
else console.log('🚨 Could not remove newsletter subscriptions', result)
await Newsletter.deleteMany().then((result) => {
if (result.ok) console.log('🔥 Newsletter subscriptions deleted')
else console.log('🚨 Could not remove newsletter subscriptions', result)
})
}
@ -65,8 +69,8 @@ export const loadSampleData = async () => {
ehash: ehash(sample.email),
picture: sample.handle + '.svg',
time: {
created: new Date()
}
created: new Date(),
},
})
user.createAvatar()
promises.push(user.save())
@ -83,7 +87,7 @@ export const loadSampleData = async () => {
return Promise.all(promises)
}
export const runTasks = options => {
export const runTasks = (options) => {
let promises = []
if (options.clearAll || options.reboot || options.clearUsers) promises.push(clearUsers())
if (options.clearAll || options.reboot || options.clearPeople) promises.push(clearPeople())

View file

@ -1,34 +1,34 @@
export default [
{
name: 'clearUsers',
type: Boolean
type: Boolean,
},
{
name: 'clearModels',
type: Boolean
type: Boolean,
},
{
name: 'clearPatterns',
type: Boolean
type: Boolean,
},
{
name: 'clearConfirmations',
type: Boolean
type: Boolean,
},
{
name: 'clearNewsletterSubscribers',
type: Boolean
type: Boolean,
},
{
name: 'clearAll',
type: Boolean
type: Boolean,
},
{
name: 'reboot',
type: Boolean
type: Boolean,
},
{
name: 'help',
type: Boolean
}
type: Boolean,
},
]

View file

@ -5,8 +5,7 @@ import { ehash } from '../utils'
function AdminController() {}
AdminController.prototype.search = function(req, res) {
AdminController.prototype.search = function (req, res) {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, admin) => {
if (err || admin === null) return res.sendStatus(400)
@ -16,26 +15,26 @@ AdminController.prototype.search = function(req, res) {
{ handle: { $regex: `.*${req.body.query}.*` } },
{ username: { $regex: `.*${req.body.query}.*` } },
{ ehash: ehash(req.body.query) },
]
],
})
.sort('username')
.exec((err, users) => {
if (err) return res.sendStatus(400)
Person.find({ handle: { $regex: `.*${req.body.query}.*` } })
.sort('handle')
.exec((err, people) => {
.sort('username')
.exec((err, users) => {
if (err) return res.sendStatus(400)
if (users === null && people === null) return res.sendStatus(404)
return res.send({
users: users.map(user => user.adminProfile()),
people: people.map(person => person.info()),
})
Person.find({ handle: { $regex: `.*${req.body.query}.*` } })
.sort('handle')
.exec((err, people) => {
if (err) return res.sendStatus(400)
if (users === null && people === null) return res.sendStatus(404)
return res.send({
users: users.map((user) => user.adminProfile()),
people: people.map((person) => person.info()),
})
})
})
})
})
}
AdminController.prototype.setPatronStatus = function(req, res) {
AdminController.prototype.setPatronStatus = function (req, res) {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, admin) => {
if (err || admin === null) return res.sendStatus(400)
@ -49,7 +48,7 @@ AdminController.prototype.setPatronStatus = function(req, res) {
})
}
AdminController.prototype.setRole = function(req, res) {
AdminController.prototype.setRole = function (req, res) {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, admin) => {
if (err || admin === null) return res.sendStatus(400)
@ -63,7 +62,7 @@ AdminController.prototype.setRole = function(req, res) {
})
}
AdminController.prototype.unfreeze = function(req, res) {
AdminController.prototype.unfreeze = function (req, res) {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, admin) => {
if (err || admin === null) return res.sendStatus(400)
@ -77,7 +76,7 @@ AdminController.prototype.unfreeze = function(req, res) {
})
}
AdminController.prototype.impersonate = function(req, res) {
AdminController.prototype.impersonate = function (req, res) {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, admin) => {
if (err || admin === null) return res.sendStatus(400)
@ -95,16 +94,14 @@ AdminController.prototype.impersonate = function(req, res) {
Pattern.find({ user: user.handle }, (err, patternList) => {
if (err) return res.sendStatus(400)
for (let pattern of patternList) patterns[pattern.handle] = pattern
return user.updateLoginTime(() =>
res.send({ account, people, patterns, token })
)
return user.updateLoginTime(() => res.send({ account, people, patterns, token }))
})
})
})
})
}
AdminController.prototype.patronList = function(req, res) {
AdminController.prototype.patronList = function (req, res) {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, admin) => {
if (err || admin === null) return res.sendStatus(400)
@ -116,16 +113,16 @@ AdminController.prototype.patronList = function(req, res) {
})
}
AdminController.prototype.subscriberList = function(req, res) {
AdminController.prototype.subscriberList = function (req, res) {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, admin) => {
if (err || admin === null) return res.sendStatus(400)
if (admin.role !== 'admin') return res.sendStatus(403)
User.find({newsletter: true}, (err, subscribedUsers) => {
User.find({ newsletter: true }, (err, subscribedUsers) => {
if (err) return res.sendStatus(500)
let subscribers = subscribedUsers.map(user => ({
let subscribers = subscribedUsers.map((user) => ({
ehash: user.ehash,
email: user.email
email: user.email,
}))
Newsletter.find({}, (err, subs) => {
if (err) return res.sendStatus(500)
@ -135,12 +132,12 @@ AdminController.prototype.subscriberList = function(req, res) {
})
}
AdminController.prototype.stats = function(req, res) {
AdminController.prototype.stats = function (req, res) {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, admin) => {
if (err || admin === null) return res.sendStatus(400)
if (admin.role !== 'admin') return res.sendStatus(403)
User.find({ "consent.profile": true }, (err, users) => {
User.find({ 'consent.profile': true }, (err, users) => {
if (err) return res.sendStatus(500)
Person.find({}, (err, people) => {
if (err) return res.sendStatus(500)
@ -157,21 +154,21 @@ AdminController.prototype.stats = function(req, res) {
}
function saveAndReturnAccount(res, user) {
user.save(function(err, updatedUser) {
user.save(function (err, updatedUser) {
if (err) {
return res.sendStatus(500)
} else return res.send({ account: updatedUser.account() })
})
}
const getToken = account => {
const getToken = (account) => {
return jwt.sign(
{
_id: account._id,
handle: account.handle,
role: account.role,
aud: config.jwt.audience,
iss: config.jwt.issuer
iss: config.jwt.issuer,
},
config.jwt.secretOrKey
)

View file

@ -6,7 +6,7 @@ import {
getHandle,
createHandle,
imageType,
saveAvatarFromBase64
saveAvatarFromBase64,
} from '../utils'
import config from '../config'
import queryString from 'query-string'
@ -19,22 +19,22 @@ import axios from 'axios'
function AuthController() {}
AuthController.prototype.initOauth = function(req, res) {
AuthController.prototype.initOauth = function (req, res) {
if (!req.body) return res.sendStatus(400)
let confirmation = new Confirmation({
type: 'oauth',
data: {
language: req.body.language,
provider: req.body.provider
}
provider: req.body.provider,
},
})
confirmation.save(function(err) {
confirmation.save(function (err) {
if (err) return res.sendStatus(500)
return res.send({ state: confirmation._id })
})
}
AuthController.prototype.loginOauth = function(req, res) {
AuthController.prototype.loginOauth = function (req, res) {
if (!req.body) return res.sendStatus(400)
Confirmation.findById(req.body.confirmation, (err, confirmation) => {
if (err) return res.sendStatus(400)
@ -57,7 +57,7 @@ AuthController.prototype.loginOauth = function(req, res) {
Pattern.find({ user: user.handle }, (err, patternList) => {
if (err) return res.sendStatus(400)
for (let pattern of patternList) patterns[pattern.handle] = pattern
confirmation.remove(err => {
confirmation.remove((err) => {
if (err !== null) return res.sendStatus(500)
user.updateLoginTime(() => res.send({ account, people, token, signup }))
})
@ -67,7 +67,7 @@ AuthController.prototype.loginOauth = function(req, res) {
})
}
AuthController.prototype.providerCallback = function(req, res) {
AuthController.prototype.providerCallback = function (req, res) {
let language, token, email, avatarUri, username
let provider = req.params.provider
let conf = config.oauth[provider]
@ -89,19 +89,19 @@ AuthController.prototype.providerCallback = function(req, res) {
code: req.query.code,
accept: 'json',
grant_type: 'authorization_code',
redirect_uri: config.api + '/oauth/callback/from/' + provider
redirect_uri: config.api + '/oauth/callback/from/' + provider,
})
.then(result => {
.then((result) => {
if (result.status !== 200) return res.sendStatus(401)
if (provider === 'github') token = queryString.parse(result.data).access_token
else if (provider === 'google') token = result.data.access_token
// Contact API for user info
const headers = token => ({ headers: { Authorization: 'Bearer ' + token } })
const headers = (token) => ({ headers: { Authorization: 'Bearer ' + token } })
go.get(conf.dataUri, headers(token))
.then(async result => {
.then(async (result) => {
if (provider === 'github') {
email = await getGithubEmail(result.data.email, go, conf.emailUri, headers(token)),
avatarUri = result.data.avatar_url
;(email = await getGithubEmail(result.data.email, go, conf.emailUri, headers(token))),
(avatarUri = result.data.avatar_url)
username = result.data.login
} else if (provider === 'google') {
for (let address of result.data.emailAddresses) {
@ -120,7 +120,7 @@ AuthController.prototype.providerCallback = function(req, res) {
// New user: signup
signup = true
let handle = getHandle()
go.get(avatarUri, { responseType: 'arraybuffer' }).then(avatar => {
go.get(avatarUri, { responseType: 'arraybuffer' }).then((avatar) => {
let type = imageType(avatar.headers['content-type'])
saveAvatarFromBase64(
new Buffer(avatar.data, 'binary').toString('base64'),
@ -142,21 +142,21 @@ AuthController.prototype.providerCallback = function(req, res) {
},
time: {
created: new Date(),
login: new Date()
}
login: new Date(),
},
}
if (provider === 'github') {
userData.social.github = result.data.login
userData.bio = result.data.bio
}
let user = new User(userData)
user.save(function(err) {
user.save(function (err) {
if (err) return res.sendStatus(500)
let validation = createHandle(20)
confirmation.data.handle = user.handle
confirmation.data.validation = validation
confirmation.data.signup = true
confirmation.save(function(err) {
confirmation.save(function (err) {
if (err) return res.sendStatus(500)
return res.redirect(
createUrl(
@ -175,18 +175,20 @@ AuthController.prototype.providerCallback = function(req, res) {
if (user.bio === '') user.bio = result.data.bio
user.social.github = result.data.login
}
user.save(function(err) {
user.save(function (err) {
let validation = createHandle(20)
confirmation.data.handle = user.handle
confirmation.data.validation = validation
confirmation.data.signup = false
confirmation.save(function(err) {
confirmation.save(function (err) {
if (err) return res.sendStatus(500)
return res.redirect(
// Watch out for pending users
createUrl(language, (user.status === 'pending')
? '/confirm/signup/' + req.query.state + '/'
: '/login/callback/' + confirmation._id + '/' + validation
createUrl(
language,
user.status === 'pending'
? '/confirm/signup/' + req.query.state + '/'
: '/login/callback/' + confirmation._id + '/' + validation
)
)
})
@ -194,12 +196,12 @@ AuthController.prototype.providerCallback = function(req, res) {
}
})
})
.catch(err => {
.catch((err) => {
console.log('api token error', err)
res.sendStatus(401)
})
})
.catch(err => {
.catch((err) => {
console.log('post token error', err)
res.sendStatus(401)
})
@ -207,20 +209,17 @@ AuthController.prototype.providerCallback = function(req, res) {
}
/*
* Github does not always return the email address
* See https://github.com/freesewing/backend/issues/162
*/
* Github does not always return the email address
* See https://github.com/freesewing/backend/issues/162
*/
const getGithubEmail = async (email, client, uri, headers) => {
if (email === null) {
return client.get(uri, headers)
.then(result => {
for (let e of result.data) {
if (e.primary) return e.email
}
})
}
else return email
return client.get(uri, headers).then((result) => {
for (let e of result.data) {
if (e.primary) return e.email
}
})
} else return email
}
export default AuthController

View file

@ -4,95 +4,100 @@ import config from '../config'
function GithubController() {}
// Create a gist
GithubController.prototype.createGist = function(req, res) {
GithubController.prototype.createGist = function (req, res) {
if (!req.body.data) return res.sendStatus(400)
let client = GithubClient()
client.post('/gists', {
public: true,
description: `An open source sewing pattern from freesewing.org`,
files: {
'pattern.yaml': { content: req.body.data }
}
})
.then(result => {
let id = result.data.id
client.post(`/gists/${id}/comments`, {
body: `👉 https://freesewing.org/recreate/gist/${id} 👀`
client
.post('/gists', {
public: true,
description: `An open source sewing pattern from freesewing.org`,
files: {
'pattern.yaml': { content: req.body.data },
},
})
.then(result => res.send({id}))
.catch(err => res.sendStatus(500))
})
.catch(err => res.sendStatus(500))
.then((result) => {
let id = result.data.id
client
.post(`/gists/${id}/comments`, {
body: `👉 https://freesewing.org/recreate/gist/${id} 👀`,
})
.then((result) => res.send({ id }))
.catch((err) => res.sendStatus(500))
})
.catch((err) => res.sendStatus(500))
}
GithubController.prototype.createIssue = function(req, res) {
GithubController.prototype.createIssue = function (req, res) {
if (!req.body.data) return res.sendStatus(400)
if (!req.body.design) return res.sendStatus(400)
let client = GithubClient()
client.post('/gists', {
public: true,
description: `A FreeSewing crash report`,
files: {
'pattern.yaml': { content: req.body.data },
'settings.yaml': { content: req.body.patternProps.settings },
'events.yaml': { content: req.body.patternProps.events },
'errors.md': { content: req.body.traces },
'parts.json': { content: req.body.patternProps.parts },
}
})
.then(gist => {
client.post('/repos/freesewing/freesewing/issues', {
title: `Error while drafting ${req.body.design}`,
body: `An error occured while drafting ${req.body.design} and a [crash report](https://gist.github.com/${gist.data.id}) was generated.`,
labels: [
`:package: ${req.body.design}`,
':robot: robot'
]
client
.post('/gists', {
public: true,
description: `A FreeSewing crash report`,
files: {
'pattern.yaml': { content: req.body.data },
'settings.yaml': { content: req.body.patternProps.settings },
'events.yaml': { content: req.body.patternProps.events },
'errors.md': { content: req.body.traces },
'parts.json': { content: req.body.patternProps.parts },
},
})
.then(issue => {
let notify = (typeof config.github.notify.specific[req.body.design] === 'undefined')
? config.github.notify.dflt
: config.github.notify.specific[req.body.design]
let id = issue.data.number
let path = `/recreate/gist/${gist.data.id}`
let body = 'Ping '
for (const user of notify) body += `@${user} `
if (req.body.userGithub) body += `@${req.body.userGithub} `
body += " 👋 \nRecreate this:\n\n"
body += `- **Lab**: 👉 https://lab.freesewing.dev/v/next/` +
`${req.body.design}?from=github&preload=${gist.data.id}`
body += "\n\n"
body += `- **Production**: 👉 https://freesewing.org${path}`
body += "\n\n"
if (req.body.userHandle) body += `(user handle: ${req.body.userHandle})`
client.post(`/repos/freesewing/freesewing/issues/${id}/comments`, { body })
.then(result => res.send({id}))
.catch(err => {
console.log(err)
res.sendStatus(500)
})
.then((gist) => {
client
.post('/repos/freesewing/freesewing/issues', {
title: `Error while drafting ${req.body.design}`,
body: `An error occured while drafting ${req.body.design} and a [crash report](https://gist.github.com/${gist.data.id}) was generated.`,
labels: [`:package: ${req.body.design}`, ':robot: robot'],
})
.then((issue) => {
let notify =
typeof config.github.notify.specific[req.body.design] === 'undefined'
? config.github.notify.dflt
: config.github.notify.specific[req.body.design]
let id = issue.data.number
let path = `/recreate/gist/${gist.data.id}`
let body = 'Ping '
for (const user of notify) body += `@${user} `
if (req.body.userGithub) body += `@${req.body.userGithub} `
body += ' 👋 \nRecreate this:\n\n'
body +=
`- **Lab**: 👉 https://lab.freesewing.dev/v/next/` +
`${req.body.design}?from=github&preload=${gist.data.id}`
body += '\n\n'
body += `- **Production**: 👉 https://freesewing.org${path}`
body += '\n\n'
if (req.body.userHandle) body += `(user handle: ${req.body.userHandle})`
client
.post(`/repos/freesewing/freesewing/issues/${id}/comments`, { body })
.then((result) => res.send({ id }))
.catch((err) => {
console.log(err)
res.sendStatus(500)
})
})
.catch((err) => {
console.log(err)
res.sendStatus(500)
})
})
.catch(err => {
console.log(err)
.catch((err) => {
console.log(err)
res.sendStatus(500)
})
})
.catch(err => {
console.log(err)
res.sendStatus(500)
})
}
const GithubClient = () => axios.create({
baseURL: config.github.api,
timeout: 5000,
auth: {
username: config.github.bot.user,
password: config.github.token
},
headers: {
Accept: 'application/vnd.github.v3+json'
}
})
const GithubClient = () =>
axios.create({
baseURL: config.github.api,
timeout: 5000,
auth: {
username: config.github.bot.user,
password: config.github.token,
},
headers: {
Accept: 'application/vnd.github.v3+json',
},
})
export default GithubController

View file

@ -1,40 +1,37 @@
import { Newsletter, Confirmation, User } from '../models'
import {
log,
email,
ehash,
} from '../utils'
import { log, email, ehash } from '../utils'
import path from 'path'
const bail = (res, page='index') => res.sendFile(path.resolve(__dirname, '..', 'landing', `${page}.html`))
const bail = (res, page = 'index') =>
res.sendFile(path.resolve(__dirname, '..', 'landing', `${page}.html`))
function NewsletterController() {}
NewsletterController.prototype.subscribe = function(req, res, subscribe=true) {
NewsletterController.prototype.subscribe = function (req, res, subscribe = true) {
if (!req.body || !req.body.email) return res.sendStatus(400)
let confirmation = new Confirmation({
type: 'newsletter',
data: { email: req.body.email }
data: { email: req.body.email },
})
confirmation.save(function(err) {
confirmation.save(function (err) {
if (err) return res.sendStatus(500)
log.info('newsletterSubscription', {
email: req.body.email,
confirmation: confirmation._id
confirmation: confirmation._id,
})
email.subscribe(req.body.email, confirmation._id)
return res.send({status: 'subscribed'})
return res.send({ status: 'subscribed' })
})
}
NewsletterController.prototype.confirm = function(req, res, subscribe=true) {
NewsletterController.prototype.confirm = function (req, res, subscribe = true) {
if (!req.params.token) return bail(res, 'invalid')
Confirmation.findById(req.params.token, (err, confirmation) => {
if (err) return bail(res)
if (confirmation === null) return bail(res)
Newsletter.findOne(
{
ehash: ehash(confirmation.data.email)
ehash: ehash(confirmation.data.email),
},
(err, reader) => {
if (err) return bail(res)
@ -46,10 +43,10 @@ NewsletterController.prototype.confirm = function(req, res, subscribe=true) {
email: confirmation.data.email,
ehash: hash,
time: {
created: new Date()
}
created: new Date(),
},
})
sub.save(function(err) {
sub.save(function (err) {
if (err) {
log.error('newsletterSubscriptionFailed', sub)
console.log(err)
@ -61,34 +58,33 @@ NewsletterController.prototype.confirm = function(req, res, subscribe=true) {
return bail(res, 'subscribe')
}
})
})
}
)
})
}
NewsletterController.prototype.unsubscribe = function(req, res) {
NewsletterController.prototype.unsubscribe = function (req, res) {
if (!req.params.ehash) return bail(res, 'invalid')
Newsletter.findOne({ ehash: req.params.ehash }, (err, reader) => {
if (reader) {
Newsletter.deleteOne({id: reader.id}, (err, result) => {
Newsletter.deleteOne({ id: reader.id }, (err, result) => {
if (!err) {
console.log(`Unsubscribed ${reader.email} from the newsletter`)
return bail(res, 'unsubscribe')
}
else return bail(res, 'oops')
} else return bail(res, 'oops')
})
} else {
User.findOne({ ehash: req.params.ehash }, (err, user) => {
if (user) {
user.newsletter = false
user.save(function(err, updatedUser) {
user.save(function (err, updatedUser) {
if (err) {
log.error('accountUpdateFailed', err)
return res.sendStatus(500)
} else return bail(res, 'unsubscribe')
})
}
else return bail(res, 'oops')
} else return bail(res, 'oops')
})
}
})

View file

@ -1,8 +1,8 @@
import config from "../config";
import { capitalize } from "../utils";
import sharp from 'sharp';
import fs from "fs";
import path from "path";
import config from '../config'
import { capitalize } from '../utils'
import sharp from 'sharp'
import fs from 'fs'
import path from 'path'
import axios from 'axios'
import remark from 'remark'
import remarkParse from 'remark-parse'
@ -14,13 +14,10 @@ import yaml from 'yaml'
// Sites for which we generate images
const sites = ['dev', 'org']
// Langauges for which we generate images
const languages = ['en', 'fr', 'de', 'es', 'nl' ]
const languages = ['en', 'fr', 'de', 'es', 'nl']
// Load template once at startup
const template = fs.readFileSync(
path.resolve(...config.og.template),
'utf-8'
)
const template = fs.readFileSync(path.resolve(...config.og.template), 'utf-8')
/* Helper method to extract intro from strapi markdown */
const introFromStrapiMarkdown = async (md, slug) => {
@ -33,17 +30,15 @@ const introFromStrapiMarkdown = async (md, slug) => {
/* Helper method to extract title from markdown frontmatter */
const titleAndIntroFromLocalMarkdown = async (md, slug) => {
const tree = await remark()
.use(remarkParse)
.use(remarkFrontmatter, ['yaml'])
.parse(md)
const tree = await remark().use(remarkParse).use(remarkFrontmatter, ['yaml']).parse(md)
if (tree.children[0].type !== 'yaml')
console.log('Markdown does not start with frontmatter', slug)
else return {
title: titleAsLines(yaml.parse(tree.children[0].value).title),
intro: introAsLines(toString(tree.children.slice(1, 2)))
}
else
return {
title: titleAsLines(yaml.parse(tree.children[0].value).title),
intro: introAsLines(toString(tree.children.slice(1, 2))),
}
return false
}
@ -53,41 +48,40 @@ const loadDevBlogPost = async (slug) => {
const result = await axios.get(
`${config.strapi}/blogposts?_locale=en&dev_eq=true&slug_eq=${slug}`
)
if (result.data) return {
title: titleAsLines(result.data[0].title),
intro: introAsLines(await introFromStrapiMarkdown(result.data[0].body, slug)),
sub: [
result.data[0].author.displayname,
new Date(result.data[0].published_at).toString().split(' ').slice(0,4).join(' '),
],
lead: 'Developer Blog',
}
if (result.data)
return {
title: titleAsLines(result.data[0].title),
intro: introAsLines(await introFromStrapiMarkdown(result.data[0].body, slug)),
sub: [
result.data[0].author.displayname,
new Date(result.data[0].published_at).toString().split(' ').slice(0, 4).join(' '),
],
lead: 'Developer Blog',
}
return false
}
/* Helper method to load markdown file from disk */
const loadMarkdownFile = async (page, site, lang) => fs.promises.readFile(
path.resolve('..', '..', 'markdown', site, ...page.split('/'), `${lang}.md`),
'utf-8'
).then(async (md) => md
? {
...(await titleAndIntroFromLocalMarkdown(md, page)),
sub: [
'freesewing.dev/',
page
],
lead: capitalize(page.split('/').shift())
}
: false
)
const loadMarkdownFile = async (page, site, lang) =>
fs.promises
.readFile(path.resolve('..', '..', 'markdown', site, ...page.split('/'), `${lang}.md`), 'utf-8')
.then(async (md) =>
md
? {
...(await titleAndIntroFromLocalMarkdown(md, page)),
sub: ['freesewing.dev/', page],
lead: capitalize(page.split('/').shift()),
}
: false
)
/* Find longest possible place to split a string */
const splitLine = (line, chars) => {
const words = line.split(' ')
if (words[0].length > chars) {
// Force a word break
return [ line.slice(0, chars-1)+'-', line.slice(chars-1) ]
return [line.slice(0, chars - 1) + '-', line.slice(chars - 1)]
}
// Glue chunks together until it's too long
let firstLine = ''
@ -97,22 +91,22 @@ const splitLine = (line, chars) => {
else max = true
}
return [ firstLine, words.join(' ').slice(firstLine.length) ]
return [firstLine, words.join(' ').slice(firstLine.length)]
}
/* Divide title into lines to fit on image */
const titleAsLines = title => {
const titleAsLines = (title) => {
// Does it fit on one line?
if (title.length <= config.og.chars.title_1) return [title]
// Does it fit on two lines?
let lines = splitLine(title, config.og.chars.title_1)
if (lines[1].length <= config.og.chars.title_2) return lines
// Three lines it is
return [ lines[0], ...splitLine(lines[1], config.og.chars.title_2) ]
return [lines[0], ...splitLine(lines[1], config.og.chars.title_2)]
}
/* Divive intro into lines to fit on image */
const introAsLines = intro => {
const introAsLines = (intro) => {
// Does it fit on one line?
if (intro.length <= config.og.chars.intro) return [intro]
// Two lines it is
@ -124,19 +118,25 @@ const getMetaData = {
dev: async (page) => {
const chunks = page.split('/')
// Home page
if (chunks.length === 1 && chunks[0] === '') return {
title: ['FreeSewing.dev'],
intro: introAsLines('FreeSewing API documentation and tutorials for developers and contributors'),
sub: ['Also featuring', ' our developers blog'],
lead: '.dev',
}
if (chunks.length === 1 && chunks[0] === '')
return {
title: ['FreeSewing.dev'],
intro: introAsLines(
'FreeSewing API documentation and tutorials for developers and contributors'
),
sub: ['Also featuring', ' our developers blog'],
lead: '.dev',
}
// Blog index page
if (chunks.length === 1 && chunks[0] === 'blog') return {
title: titleAsLines('FreeSewing Developer Blog'),
intro: introAsLines("Contains no sewing news whatsover. Only posts for (aspiring) developers :)"),
sub: ['freesewing.dev', '/blog'],
lead: 'Developer Blog',
}
if (chunks.length === 1 && chunks[0] === 'blog')
return {
title: titleAsLines('FreeSewing Developer Blog'),
intro: introAsLines(
'Contains no sewing news whatsover. Only posts for (aspiring) developers :)'
),
sub: ['freesewing.dev', '/blog'],
lead: 'Developer Blog',
}
// Blog post
if (chunks.length === 2 && chunks[0] === 'blog') {
return await loadDevBlogPost(chunks[1])
@ -148,45 +148,43 @@ const getMetaData = {
return md
? md
: {
title: titleAsLines('FreeSewing.dev'),
intro: introAsLines('Documentation, guides, and howtos for contributors and developers alike'),
sub: ['https://freesewing.dev/', '&lt;== Check it out'],
lead: 'freesewing.dev'
}
title: titleAsLines('FreeSewing.dev'),
intro: introAsLines(
'Documentation, guides, and howtos for contributors and developers alike'
),
sub: ['https://freesewing.dev/', '&lt;== Check it out'],
lead: 'freesewing.dev',
}
},
org: async (page, site, lang) => ({})
org: async (page, site, lang) => ({}),
}
/* Hide unused placeholders */
const hidePlaceholders = list => {
const hidePlaceholders = (list) => {
let svg = template
for (const i of list) {
svg = svg
.replace(`${i}title_1`, '')
.replace(`${i}title_2`, '')
.replace(`${i}title_3`, '')
svg = svg.replace(`${i}title_1`, '').replace(`${i}title_2`, '').replace(`${i}title_3`, '')
}
return svg
}
/* Place text in SVG template */
const decorateSvg = data => {
const decorateSvg = (data) => {
let svg
// Single title line
if (data.title.length === 1) {
svg = hidePlaceholders([2,3])
.replace(`1title_1`, data.title[0])
svg = hidePlaceholders([2, 3]).replace(`1title_1`, data.title[0])
}
// Double title line
else if (data.title.length === 2) {
svg = hidePlaceholders([1,3])
svg = hidePlaceholders([1, 3])
.replace(`2title_1`, data.title[0])
.replace(`2title_2`, data.title[1])
}
// Triple title line
else if (data.title.length === 3) {
svg = hidePlaceholders([1,2])
svg = hidePlaceholders([1, 2])
.replace(`3title_1`, data.title[0])
.replace(`3title_2`, data.title[1])
.replace(`3title_3`, data.title[2])
@ -202,14 +200,14 @@ const decorateSvg = data => {
/* This generates open graph images */
function OgController() { }
function OgController() {}
OgController.prototype.image = async function (req, res) {
// Extract path parameters
const { lang='en', site='dev' } = req.params
const page = req.params["0"]
if (sites.indexOf(site) === -1) return res.send({error: 'sorry'})
if (languages.indexOf(lang) === -1) return res.send({error: 'sorry'})
const { lang = 'en', site = 'dev' } = req.params
const page = req.params['0']
if (sites.indexOf(site) === -1) return res.send({ error: 'sorry' })
if (languages.indexOf(lang) === -1) return res.send({ error: 'sorry' })
// Load meta data
const data = await getMetaData[site](page, site, lang)
@ -224,6 +222,4 @@ OgController.prototype.image = async function (req, res) {
})
}
export default OgController;
export default OgController

View file

@ -17,9 +17,9 @@ PatternController.prototype.create = (req, res) => {
name: req.body.name,
notes: req.body.notes,
data: req.body.data,
created: new Date()
created: new Date(),
})
pattern.save(function(err) {
pattern.save(function (err) {
if (err) {
log.error('patternCreationFailed', user)
console.log(err)
@ -56,7 +56,7 @@ PatternController.prototype.delete = (req, res) => {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, async (err, user) => {
if (err || user === null) return res.sendStatus(400)
Pattern.deleteOne({ handle: req.params.handle, user: user.handle }, err => {
Pattern.deleteOne({ handle: req.params.handle, user: user.handle }, (err) => {
if (err) return res.sendStatus(400)
else return res.sendStatus(204)
})
@ -64,7 +64,7 @@ PatternController.prototype.delete = (req, res) => {
}
// Delete multiple
PatternController.prototype.deleteMultiple = function(req, res) {
PatternController.prototype.deleteMultiple = function (req, res) {
if (!req.body) return res.sendStatus(400)
if (!req.body.patterns) return res.sendStatus(400)
if (!req.user._id) return res.sendStatus(400)
@ -77,9 +77,9 @@ PatternController.prototype.deleteMultiple = function(req, res) {
Pattern.deleteMany(
{
user: user.handle,
$or: handles
$or: handles,
},
err => {
(err) => {
if (err) return res.sendStatus(500)
const patterns = {}
Patterns.find({ user: user.handle }, (err, patternList) => {
@ -93,7 +93,7 @@ PatternController.prototype.deleteMultiple = function(req, res) {
}
function saveAndReturnPattern(res, pattern) {
pattern.save(function(err, updatedPattern) {
pattern.save(function (err, updatedPattern) {
if (err) {
log.error('patternUpdateFailed', updatedPattern)
return res.sendStatus(500)

View file

@ -4,7 +4,7 @@ import { log } from '../utils'
function PersonController() {}
// CRUD basics
PersonController.prototype.create = function(req, res) {
PersonController.prototype.create = function (req, res) {
if (!req.body) return res.sendStatus(400)
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, user) => {
@ -17,10 +17,10 @@ PersonController.prototype.create = function(req, res) {
units: req.body.units,
breasts: req.body.breasts,
picture: handle + '.svg',
created: new Date()
created: new Date(),
})
person.createAvatar()
person.save(function(err) {
person.save(function (err) {
if (err) return res.sendStatus(400)
log.info('personCreated', { handle: handle })
return res.send({ person: person.info() })
@ -28,7 +28,7 @@ PersonController.prototype.create = function(req, res) {
})
}
PersonController.prototype.read = function(req, res) {
PersonController.prototype.read = function (req, res) {
if (!req.body) return res.sendStatus(400)
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, user) => {
@ -56,7 +56,7 @@ PersonController.prototype.update = (req, res) => {
if (typeof data.measurements !== 'undefined')
person.measurements = {
...person.measurements,
...data.measurements
...data.measurements,
}
if (typeof data.picture !== 'undefined') person.saveAvatar(data.picture)
@ -69,7 +69,7 @@ PersonController.prototype.delete = (req, res) => {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, async (err, user) => {
if (err || user === null) return res.sendStatus(400)
Person.deleteOne({ handle: req.params.handle, user: user.handle }, err => {
Person.deleteOne({ handle: req.params.handle, user: user.handle }, (err) => {
if (err) return res.sendStatus(400)
else return res.sendStatus(204)
})
@ -77,10 +77,10 @@ PersonController.prototype.delete = (req, res) => {
}
// Clone
PersonController.prototype.clone = function(req, res) {}
PersonController.prototype.clone = function (req, res) {}
function saveAndReturnPerson(res, person) {
person.save(function(err, updatedPerson) {
person.save(function (err, updatedPerson) {
if (err) {
log.error('personUpdateFailed', updatedPerson)
return res.sendStatus(500)

View file

@ -11,11 +11,10 @@ const getToken = async () => {
`${config.strapi.protocol}://${config.strapi.host}:${config.strapi.port}/auth/local`,
{
identifier: config.strapi.username,
password: config.strapi.password
password: config.strapi.password,
}
)
}
catch(err) {
} catch (err) {
console.log('ERROR: Failed to load strapi token')
return false
}
@ -23,18 +22,18 @@ const getToken = async () => {
return result.data.jwt
}
const withToken = token => ({
const withToken = (token) => ({
headers: {
Authorization: `Bearer ${token}`,
}
},
})
const ext = type => {
const ext = (type) => {
switch (type.toLowerCase()) {
case 'image/jpg':
case 'image/jpeg':
return 'jpg'
break;
break
case 'image/png':
return 'png'
break
@ -46,47 +45,45 @@ const ext = type => {
}
}
const api = path => `${config.strapi.protocol}://${config.strapi.host}:${config.strapi.port}${path}`
const api = (path) =>
`${config.strapi.protocol}://${config.strapi.host}:${config.strapi.port}${path}`
// Uploads a picture to Strapi
const uploadPicture = async (img, name, token) => {
const form = new FormData()
const buff = asBuffer(img)
const extention = ext(buff.type)
if (!extention) return [false, {error: `Filetype ${buff.type} is not supported`}]
if (!extention) return [false, { error: `Filetype ${buff.type} is not supported` }]
// I hate you strapi, because this hack is the only way I can get your shitty upload to work
const filename = `${config.strapi.tmp}/viaBackend.${extention}`
const file = fs.createReadStream(filename)
form.append('files', file)
form.append('fileInfo', JSON.stringify({
alternativeText: `The picture/avatar for maker ${name}`,
caption: `Maker: ${name}`,
}))
form.append(
'fileInfo',
JSON.stringify({
alternativeText: `The picture/avatar for maker ${name}`,
caption: `Maker: ${name}`,
})
)
let result
try {
result = await axios.post(
api('/upload'),
form,
{
headers: {
...form.getHeaders(),
Authorization: `Bearer ${token}`,
},
}
)
}
catch (err) {
console.log("ERROR: Failed to upload picture")
return [false, {error: 'Upload failed'}]
result = await axios.post(api('/upload'), form, {
headers: {
...form.getHeaders(),
Authorization: `Bearer ${token}`,
},
})
} catch (err) {
console.log('ERROR: Failed to upload picture')
return [false, { error: 'Upload failed' }]
}
return [true, result.data]
}
const validRequest = body => (
const validRequest = (body) =>
body &&
body.displayname &&
body.about &&
@ -94,40 +91,36 @@ const validRequest = body => (
typeof body.displayname === 'string' &&
typeof body.about === 'string' &&
typeof body.picture === 'string'
)
// Creates a maker or author in Strapi
const createPerson = async (type, data, token) => {
let result
try {
result = await axios.post(
api(`/${type}s`),
data,
withToken(token)
)
}
catch (err) {
console.log("ERROR: Failed to create", type)
return [false, {error: 'Creation failed'}]
result = await axios.post(api(`/${type}s`), data, withToken(token))
} catch (err) {
console.log('ERROR: Failed to create', type)
return [false, { error: 'Creation failed' }]
}
return [true, result.data]
}
function StrapiController() {}
StrapiController.prototype.addPerson = async function(req, res, type) {
StrapiController.prototype.addPerson = async function (req, res, type) {
if (!validRequest(req.body)) return res.sendStatus(400)
const token = await getToken()
const [upload, picture] = await uploadPicture(req.body.picture, req.body.displayname, token)
if (!upload) return res.status(400).send(picture)
const [create, person] = await createPerson(type, {
picture: picture[0].id,
displayname: req.body.displayname,
about: req.body.about,
}, token)
const [create, person] = await createPerson(
type,
{
picture: picture[0].id,
displayname: req.body.displayname,
about: req.body.about,
},
token
)
if (!create) return res.status(400).send(person)
return res.send(person)

View file

@ -1,11 +1,5 @@
import { User, Confirmation, Person, Pattern } from '../models'
import {
log,
email,
ehash,
newHandle,
uniqueHandle,
} from '../utils'
import { log, email, ehash, newHandle, uniqueHandle } from '../utils'
import jwt from 'jsonwebtoken'
import config from '../config'
import path from 'path'
@ -15,15 +9,15 @@ import rimraf from 'rimraf'
function UserController() {}
UserController.prototype.login = function(req, res) {
UserController.prototype.login = function (req, res) {
if (!req.body || !req.body.username) return res.sendStatus(400)
User.findOne(
{
$or: [
{ username: req.body.username.toLowerCase().trim() },
{ username: req.body.username.trim() },
{ ehash: ehash(req.body.username) }
]
{ ehash: ehash(req.body.username) },
],
},
(err, user) => {
if (err) return res.sendStatus(400)
@ -44,9 +38,7 @@ UserController.prototype.login = function(req, res) {
Pattern.find({ user: user.handle }, (err, patternList) => {
if (err) return res.sendStatus(400)
for (let pattern of patternList) patterns[pattern.handle] = pattern
return user.updateLoginTime(() =>
res.send({ account, people, patterns, token })
)
return user.updateLoginTime(() => res.send({ account, people, patterns, token }))
})
})
}
@ -60,7 +52,7 @@ UserController.prototype.login = function(req, res) {
}
// For people who have forgotten their password, or password-less logins
UserController.prototype.confirmationLogin = function(req, res) {
UserController.prototype.confirmationLogin = function (req, res) {
if (!req.body || !req.body.id) return res.sendStatus(400)
Confirmation.findById(req.body.id, (err, confirmation) => {
if (err) return res.sendStatus(400)
@ -83,9 +75,7 @@ UserController.prototype.confirmationLogin = function(req, res) {
Pattern.find({ user: user.handle }, (err, patternList) => {
if (err) return res.sendStatus(400)
for (let pattern of patternList) patterns[pattern.handle] = pattern
return user.updateLoginTime(() =>
res.send({ account, people, patterns, token })
)
return user.updateLoginTime(() => res.send({ account, people, patterns, token }))
})
})
}
@ -112,7 +102,7 @@ UserController.prototype.create = (req, res) => {
log.info('accountActivated', { handle: user.handle })
let account = user.account()
let token = getToken(account)
user.save(function(err) {
user.save(function (err) {
if (err) return res.sendStatus(400)
Confirmation.findByIdAndDelete(req.body.id, (err, confirmation) => {
return res.send({ account, people: {}, patterns: {}, token })
@ -164,7 +154,7 @@ UserController.prototype.update = (req, res) => {
if (typeof data.settings !== 'undefined') {
user.settings = {
...user.settings,
...data.settings
...data.settings,
}
return saveAndReturnAccount(res, user)
} else if (data.newsletter === true || data.newsletter === false) {
@ -183,7 +173,7 @@ UserController.prototype.update = (req, res) => {
} else if (typeof data.consent === 'object') {
user.consent = {
...user.consent,
...data.consent
...data.consent,
}
return saveAndReturnAccount(res, user)
} else if (typeof data.avatar !== 'undefined') {
@ -223,15 +213,15 @@ UserController.prototype.update = (req, res) => {
language: user.settings.language,
email: {
new: req.body.email,
current: user.email
}
}
current: user.email,
},
},
})
confirmation.save(function(err) {
confirmation.save(function (err) {
if (err) return res.sendStatus(500)
log.info('emailchangeRequest', {
newEmail: req.body.email,
confirmation: confirmation._id
confirmation: confirmation._id,
})
email.emailchange(req.body.email, user.email, user.settings.language, confirmation._id)
return saveAndReturnAccount(res, user)
@ -242,7 +232,7 @@ UserController.prototype.update = (req, res) => {
}
function saveAndReturnAccount(res, user) {
user.save(function(err, updatedUser) {
user.save(function (err, updatedUser) {
if (err) {
log.error('accountUpdateFailed', err)
return res.sendStatus(500)
@ -274,7 +264,7 @@ UserController.prototype.signup = (req, res) => {
if (!req.body.language) return res.status(400).send('languageMissing')
User.findOne(
{
ehash: ehash(req.body.email)
ehash: ehash(req.body.email),
},
(err, user) => {
if (err) return res.sendStatus(500)
@ -293,10 +283,10 @@ UserController.prototype.signup = (req, res) => {
status: 'pending',
picture: handle + '.svg',
time: {
created: new Date()
}
created: new Date(),
},
})
user.save(function(err) {
user.save(function (err) {
if (err) {
log.error('accountCreationFailed', user)
console.log(err)
@ -310,10 +300,10 @@ UserController.prototype.signup = (req, res) => {
language: req.body.language,
email: req.body.email,
password: req.body.password,
handle
}
handle,
},
})
confirmation.save(function(err) {
confirmation.save(function (err) {
if (err) return res.sendStatus(500)
log.info('signupRequest', { email: req.body.email, confirmation: confirmation._id })
email.signup(req.body.email, req.body.language, confirmation._id)
@ -332,7 +322,7 @@ UserController.prototype.resend = (req, res) => {
if (!req.body.language) return res.status(400).send('languageMissing')
User.findOne(
{
ehash: ehash(req.body.email)
ehash: ehash(req.body.email),
},
(err, user) => {
if (err) return res.sendStatus(500)
@ -343,12 +333,15 @@ UserController.prototype.resend = (req, res) => {
data: {
language: req.body.language,
email: user.email,
handle: user.handle
}
handle: user.handle,
},
})
confirmation.save(function(err) {
confirmation.save(function (err) {
if (err) return res.sendStatus(500)
log.info('resendActivationRequest', { email: req.body.email, confirmation: confirmation._id })
log.info('resendActivationRequest', {
email: req.body.email,
confirmation: confirmation._id,
})
email.signup(req.body.email, req.body.language, confirmation._id)
return res.sendStatus(200)
})
@ -363,8 +356,8 @@ UserController.prototype.resetPassword = (req, res) => {
{
$or: [
{ username: req.body.username.toLowerCase().trim() },
{ ehash: ehash(req.body.username) }
]
{ ehash: ehash(req.body.username) },
],
},
(err, user) => {
if (err) {
@ -375,10 +368,10 @@ UserController.prototype.resetPassword = (req, res) => {
let confirmation = new Confirmation({
type: 'passwordreset',
data: {
handle: user.handle
}
handle: user.handle,
},
})
confirmation.save(function(err) {
confirmation.save(function (err) {
if (err) return res.sendStatus(500)
log.info('passwordresetRequest', { user: user.handle, confirmation: confirmation._id })
email.passwordreset(user.email, user.settings.language, confirmation._id)
@ -398,7 +391,7 @@ UserController.prototype.setPassword = (req, res) => {
if (user === null) return res.sendStatus(401)
if (confirmation.type === 'passwordreset' && confirmation.data.handle === user.handle) {
user.password = req.body.password
user.save(function(err) {
user.save(function (err) {
log.info('passwordSet', { user, req })
let account = user.account()
let token = getToken(account)
@ -433,7 +426,7 @@ UserController.prototype.patronList = (req, res) => {
let patrons = {
2: [],
4: [],
8: []
8: [],
}
for (let key of Object.keys(users)) {
let user = users[key].profile()
@ -443,7 +436,7 @@ UserController.prototype.patronList = (req, res) => {
bio: user.bio,
picture: user.picture,
social: user.social,
pictureUris: user.pictureUris
pictureUris: user.pictureUris,
})
}
return res.send(patrons)
@ -458,17 +451,17 @@ UserController.prototype.export = (req, res) => {
if (!dir) return res.sendStatus(500)
let zip = new Zip()
zip.file('account.json', JSON.stringify(user.export(), null, 2))
loadAvatar(user).then(avatar => {
loadAvatar(user).then((avatar) => {
if (avatar) zip.file(user.picture, data)
zip
.generateAsync({
type: 'uint8array',
comment: 'freesewing.org',
streamFiles: true
streamFiles: true,
})
.then(function(data) {
.then(function (data) {
let file = path.join(dir, 'export.zip')
fs.writeFile(file, data, err => {
fs.writeFile(file, data, (err) => {
log.info('dataExport', { user, req })
return res.send({ export: uri(file) })
})
@ -477,7 +470,7 @@ UserController.prototype.export = (req, res) => {
})
}
const loadAvatar = async user => {
const loadAvatar = async (user) => {
if (user.picture)
await fs.readFile(path.join(user.storagePath(), user.picture), (err, data) => data)
else return false
@ -489,7 +482,7 @@ UserController.prototype.restrict = (req, res) => {
User.findById(req.user._id, (err, user) => {
if (user === null) return res.sendStatus(400)
user.status = 'frozen'
user.save(function(err) {
user.save(function (err) {
if (err) {
log.error('accountFreezeFailed', user)
return res.sendStatus(500)
@ -504,7 +497,7 @@ UserController.prototype.remove = (req, res) => {
if (!req.user._id) return res.sendStatus(400)
User.findById(req.user._id, (err, user) => {
if (user === null) return res.sendStatus(400)
rimraf(user.storagePath(), err => {
rimraf(user.storagePath(), (err) => {
if (err) {
console.log('rimraf', err)
log.error('accountRemovalFailed', { err, user, req })
@ -520,14 +513,14 @@ UserController.prototype.remove = (req, res) => {
})
}
const getToken = account => {
const getToken = (account) => {
return jwt.sign(
{
_id: account._id,
handle: account.handle,
role: account.role,
aud: config.jwt.audience,
iss: config.jwt.issuer
iss: config.jwt.issuer,
},
config.jwt.secretOrKey
)
@ -535,7 +528,7 @@ const getToken = account => {
const createTempDir = () => {
let path = temporaryStoragePath(newHandle(10))
fs.mkdir(path, { recursive: true }, err => {
fs.mkdir(path, { recursive: true }, (err) => {
if (err) {
log.error('mkdirFailed', err)
path = false
@ -545,6 +538,6 @@ const createTempDir = () => {
return path
}
const uri = path => config.static + path.substring(config.storage.length)
const uri = (path) => config.static + path.substring(config.storage.length)
export default UserController

View file

@ -37,7 +37,7 @@
</style>
</head>
<body>
<div class='msg'>
<div class="msg">
<h1>Love the enthusiasm</h1>
<p>But you were already subscribed</p>
</div>

View file

@ -34,21 +34,24 @@
max-width: 36ch;
margin: 6rem auto;
}
a, a:visited, a:active {
a,
a:visited,
a:active {
color: #d0bfff !important;
text-decoration: none;
}
</style>
</head>
<body>
<div class='msg'>
<div class="msg">
<h1>Hi</h1>
<p>
This is the FreeSewing backend.
<br>
<br />
Try <a href="https://freesewing.org/">freesewing.org</a> instead.
</p>
<p>For questions, join us at
<p>
For questions, join us at
<a href="https://discord.freesewing.org/">discord.freesewing.org</a>
</p>
</div>

View file

@ -34,18 +34,18 @@
max-width: 36ch;
margin: 6rem auto;
}
a, a:visited, a:active {
a,
a:visited,
a:active {
color: #d0bfff !important;
text-decoration: none;
}
</style>
</head>
<body>
<div class='msg'>
<div class="msg">
<h1>What are you doing?</h1>
<p>
Try <a href="https://freesewing.org/">freesewing.org</a> instead.
</p>
<p>Try <a href="https://freesewing.org/">freesewing.org</a> instead.</p>
</div>
<img src="https://freesewing.org/avatar.svg" />
</body>

View file

@ -34,18 +34,18 @@
max-width: 36ch;
margin: 6rem auto;
}
a, a:visited, a:active {
a,
a:visited,
a:active {
color: #d0bfff !important;
text-decoration: none;
}
</style>
</head>
<body>
<div class='msg'>
<div class="msg">
<h1>Oops</h1>
<p>
That did not go as planned
</p>
<p>That did not go as planned</p>
</div>
<img src="https://freesewing.org/avatar.svg" />
</body>

View file

@ -37,7 +37,7 @@
</style>
</head>
<body>
<div class='msg'>
<div class="msg">
<h1>Done</h1>
<p>You are now subscribed to the FreeSewing newsletter</p>
</div>

View file

@ -37,7 +37,7 @@
</style>
</head>
<body>
<div class='msg'>
<div class="msg">
<h1>Gone</h1>
<p>You are no longer subscribed to the FreeSewing newsletter</p>
</div>

View file

@ -1,6 +1,6 @@
import bodyParser from 'body-parser'
export default app => {
export default (app) => {
app.use(bodyParser.json({ limit: '20mb' }))
app.use(bodyParser.urlencoded({ extended: true }))
}

View file

@ -1,5 +1,5 @@
import cors from 'cors'
export default app => {
export default (app) => {
app.use(cors())
}

Some files were not shown because too many files have changed in this diff Show more