feat(backend): Updates for account pages
This commit is contained in:
parent
4744759d0b
commit
5ee9491485
5 changed files with 118 additions and 19 deletions
|
@ -117,6 +117,18 @@ UsersController.prototype.profile = async (req, res, tools) => {
|
||||||
return User.sendResponse(res)
|
return User.sendResponse(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns all user data
|
||||||
|
*
|
||||||
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
|
*/
|
||||||
|
UsersController.prototype.allData = async (req, res, tools) => {
|
||||||
|
const User = new UserModel(tools)
|
||||||
|
await User.allData(req)
|
||||||
|
|
||||||
|
return User.sendResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Checks whether a submitted username is available
|
* Checks whether a submitted username is available
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,15 +9,20 @@ import { decorateModel } from '../utils/model-decorator.mjs'
|
||||||
* @param {tools} object - A set of tools loaded in src/index.js
|
* @param {tools} object - A set of tools loaded in src/index.js
|
||||||
* @returns {ApikeyModel} object - The ApikeyModel
|
* @returns {ApikeyModel} object - The ApikeyModel
|
||||||
*/
|
*/
|
||||||
export function ApikeyModel(tools) {
|
export function ApikeyModel(tools, models) {
|
||||||
/*
|
/*
|
||||||
* See utils/model-decorator.mjs for details
|
* See utils/model-decorator.mjs for details
|
||||||
*/
|
*/
|
||||||
return decorateModel(this, tools, {
|
return decorateModel(
|
||||||
name: 'apikey',
|
this,
|
||||||
encryptedFields: ['name'],
|
tools,
|
||||||
models: ['user'],
|
{
|
||||||
})
|
name: 'apikey',
|
||||||
|
encryptedFields: ['name'],
|
||||||
|
models: ['user'],
|
||||||
|
},
|
||||||
|
models
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -167,12 +172,18 @@ ApikeyModel.prototype.userApikeys = async function (uid) {
|
||||||
/*
|
/*
|
||||||
* Keys are an array, remove sercrets with map() and decrypt prior to returning
|
* Keys are an array, remove sercrets with map() and decrypt prior to returning
|
||||||
*/
|
*/
|
||||||
return keys.map((key) => {
|
return keys.map((key) => this.asKeyData(key))
|
||||||
delete key.secret
|
}
|
||||||
key.name = this.decrypt(key.name)
|
|
||||||
|
|
||||||
return key
|
/*
|
||||||
})
|
* Takes non-instatiated key data and prepares it so it can be returned
|
||||||
|
*/
|
||||||
|
ApikeyModel.prototype.asKeyData = async function (key) {
|
||||||
|
delete key.secret
|
||||||
|
delete key.aud
|
||||||
|
key.name = this.decrypt(key.name)
|
||||||
|
|
||||||
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -51,6 +51,43 @@ UserModel.prototype.profile = async function ({ params }) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loads a user from the database based on the where clause you pass it
|
||||||
|
* In addition prepares it for returning all account data
|
||||||
|
* This is guarded so it enforces access control and validates input
|
||||||
|
* This is an anonymous route returning limited info (profile data)
|
||||||
|
*
|
||||||
|
* @param {params} object - The request (URL) parameters
|
||||||
|
* @returns {UserModel} object - The UserModel
|
||||||
|
*/
|
||||||
|
UserModel.prototype.allData = async function ({ params }) {
|
||||||
|
/*
|
||||||
|
* Is id set?
|
||||||
|
*/
|
||||||
|
if (typeof params.id === 'undefined') return this.setResponse(403, 'idMissing')
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to find the record in the database
|
||||||
|
* Note that find checks lusername, ehash, and id but we
|
||||||
|
* pass it in the username value as that's what the login
|
||||||
|
* rout does
|
||||||
|
*/
|
||||||
|
await this.read(
|
||||||
|
{ id: Number(params.id) },
|
||||||
|
{ apikeys: true, bookmarks: true, patterns: true, sets: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If it does not exist, return 404
|
||||||
|
*/
|
||||||
|
if (!this.exists) return this.setResponse(404)
|
||||||
|
|
||||||
|
return this.setResponse200({
|
||||||
|
result: 'success',
|
||||||
|
data: this.asData(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Loads a user from the database based on the where clause you pass it
|
* Loads a user from the database based on the where clause you pass it
|
||||||
* In addition prepares it for returning the account data
|
* In addition prepares it for returning the account data
|
||||||
|
@ -857,15 +894,21 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
|
||||||
/*
|
/*
|
||||||
* Image (img)
|
* Image (img)
|
||||||
*/
|
*/
|
||||||
if (typeof body.img === 'string')
|
if (typeof body.img === 'string') {
|
||||||
data.img = await replaceImage({
|
const imgData = {
|
||||||
id: `user-${this.record.ihash}`,
|
id: `user-${this.record.ihash}`,
|
||||||
metadata: {
|
metadata: {
|
||||||
user: user.uid,
|
user: user.uid,
|
||||||
ihash: this.record.ihash,
|
ihash: this.record.ihash,
|
||||||
},
|
},
|
||||||
b64: body.img,
|
}
|
||||||
})
|
/*
|
||||||
|
* Allow both a base64 encoded binary image or an URL
|
||||||
|
*/
|
||||||
|
if (body.img.slice(0, 4) === 'http') imgData.url = body.img
|
||||||
|
else imgData.b64 = body.img
|
||||||
|
data.img = await replaceImage(imgData)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now update the database record
|
* Now update the database record
|
||||||
|
@ -1161,6 +1204,7 @@ UserModel.prototype.asProfile = function () {
|
||||||
id: this.record.id,
|
id: this.record.id,
|
||||||
bio: this.clear.bio,
|
bio: this.clear.bio,
|
||||||
img: this.clear.img,
|
img: this.clear.img,
|
||||||
|
ihash: this.record.ihash,
|
||||||
patron: this.record.patron,
|
patron: this.record.patron,
|
||||||
role: this.record.role,
|
role: this.record.role,
|
||||||
username: this.record.username,
|
username: this.record.username,
|
||||||
|
@ -1185,7 +1229,7 @@ UserModel.prototype.asAccount = function () {
|
||||||
createdAt: this.record.createdAt,
|
createdAt: this.record.createdAt,
|
||||||
email: this.clear.email,
|
email: this.clear.email,
|
||||||
data: this.clear.data,
|
data: this.clear.data,
|
||||||
ihash: this.ihash,
|
ihash: this.record.ihash,
|
||||||
img: this.clear.img,
|
img: this.clear.img,
|
||||||
imperial: this.record.imperial,
|
imperial: this.record.imperial,
|
||||||
initial: this.clear.initial,
|
initial: this.clear.initial,
|
||||||
|
@ -1208,6 +1252,31 @@ UserModel.prototype.asAccount = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns all user data (that is not included in the account data)
|
||||||
|
*
|
||||||
|
* @return {account} object - The account data as a plain object
|
||||||
|
*/
|
||||||
|
UserModel.prototype.asData = function () {
|
||||||
|
/*
|
||||||
|
* Nothing to do here but construct the object to return
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
apikeys: this.record.apikeys
|
||||||
|
? this.record.apikeys.map((key) => {
|
||||||
|
delete key.secret
|
||||||
|
delete key.aud
|
||||||
|
key.name = this.decrypt(key.name)
|
||||||
|
|
||||||
|
return key
|
||||||
|
})
|
||||||
|
: [],
|
||||||
|
bookmarks: this.record.bookmarks || [],
|
||||||
|
patterns: this.record.patterns || [],
|
||||||
|
sets: this.record.sets || [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns a list of records as search results
|
* Returns a list of records as search results
|
||||||
* Typically used by admin search
|
* Typically used by admin search
|
||||||
|
|
|
@ -55,6 +55,14 @@ export function usersRoutes(tools) {
|
||||||
Users.isUsernameAvailable(req, res, tools)
|
Users.isUsernameAvailable(req, res, tools)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Load full user data
|
||||||
|
app.get('/users/:id/jwt', passport.authenticate(...jwt), (req, res) =>
|
||||||
|
Users.allData(req, res, tools)
|
||||||
|
)
|
||||||
|
app.get('/users/:id/key', passport.authenticate(...bsc), (req, res) =>
|
||||||
|
Users.allData(req, res, tools)
|
||||||
|
)
|
||||||
|
|
||||||
// Load a user profile
|
// Load a user profile
|
||||||
app.get('/users/:id', (req, res) => Users.profile(req, res, tools))
|
app.get('/users/:id', (req, res) => Users.profile(req, res, tools))
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export async function replaceImage(props, isTest = false) {
|
||||||
const form = getFormData(props)
|
const form = getFormData(props)
|
||||||
// Ignore errors on delete, probably means the image does not exist
|
// Ignore errors on delete, probably means the image does not exist
|
||||||
try {
|
try {
|
||||||
await axios.delete(`${config.api}/${props.id}`)
|
await axios.delete(`${config.api}/${props.id}`, { headers })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// It's fine
|
// It's fine
|
||||||
log.info(`Could not delete image ${props.id}`)
|
log.info(`Could not delete image ${props.id}`)
|
||||||
|
@ -58,10 +58,9 @@ export async function replaceImage(props, isTest = false) {
|
||||||
result = await axios.post(config.api, form, { headers })
|
result = await axios.post(config.api, form, { headers })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Failed to replace image on cloudflare', err)
|
console.log('Failed to replace image on cloudflare', err)
|
||||||
console.log(err.response.data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data?.result?.id ? result.data.result.id : false
|
return result?.data?.result?.id ? result.data.result.id : false
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue