1
0
Fork 0

feat(backend): Updates for account pages

This commit is contained in:
joostdecock 2023-08-19 18:01:51 +02:00
parent 4744759d0b
commit 5ee9491485
5 changed files with 118 additions and 19 deletions

View file

@ -117,6 +117,18 @@ UsersController.prototype.profile = async (req, res, tools) => {
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
*

View file

@ -9,15 +9,20 @@ import { decorateModel } from '../utils/model-decorator.mjs'
* @param {tools} object - A set of tools loaded in src/index.js
* @returns {ApikeyModel} object - The ApikeyModel
*/
export function ApikeyModel(tools) {
export function ApikeyModel(tools, models) {
/*
* See utils/model-decorator.mjs for details
*/
return decorateModel(this, tools, {
name: 'apikey',
encryptedFields: ['name'],
models: ['user'],
})
return decorateModel(
this,
tools,
{
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
*/
return keys.map((key) => {
delete key.secret
key.name = this.decrypt(key.name)
return keys.map((key) => this.asKeyData(key))
}
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
}
/*

View file

@ -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
* In addition prepares it for returning the account data
@ -857,15 +894,21 @@ UserModel.prototype.guardedUpdate = async function ({ body, user }) {
/*
* Image (img)
*/
if (typeof body.img === 'string')
data.img = await replaceImage({
if (typeof body.img === 'string') {
const imgData = {
id: `user-${this.record.ihash}`,
metadata: {
user: user.uid,
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
@ -1161,6 +1204,7 @@ UserModel.prototype.asProfile = function () {
id: this.record.id,
bio: this.clear.bio,
img: this.clear.img,
ihash: this.record.ihash,
patron: this.record.patron,
role: this.record.role,
username: this.record.username,
@ -1185,7 +1229,7 @@ UserModel.prototype.asAccount = function () {
createdAt: this.record.createdAt,
email: this.clear.email,
data: this.clear.data,
ihash: this.ihash,
ihash: this.record.ihash,
img: this.clear.img,
imperial: this.record.imperial,
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
* Typically used by admin search

View file

@ -55,6 +55,14 @@ export function usersRoutes(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
app.get('/users/:id', (req, res) => Users.profile(req, res, tools))

View file

@ -48,7 +48,7 @@ export async function replaceImage(props, isTest = false) {
const form = getFormData(props)
// Ignore errors on delete, probably means the image does not exist
try {
await axios.delete(`${config.api}/${props.id}`)
await axios.delete(`${config.api}/${props.id}`, { headers })
} catch (err) {
// It's fine
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 })
} catch (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
}
/*