Skip to content

Commit

Permalink
feat: Add basic storage limit request functionality (#1398)
Browse files Browse the repository at this point in the history
* feat: add basic storage limit request functionality

* add pinning request modal and hide button when there is a pending request

* feat: Adding slack notification and fixing env.MODE in user.js

* fixing lint errors

Co-authored-by: trigramdev9 <jsdevel@trigram.co>
  • Loading branch information
e-schneid and jsdevel authored Jun 16, 2022
1 parent 4fe0120 commit 1347ed5
Show file tree
Hide file tree
Showing 19 changed files with 762 additions and 263 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"wrangler": "^2.0.6"
},
"standard": {
"globals": ["fetch"],
"ignore": [
"packages/website"
]
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { envAll } from './env.js'
import { statusGet } from './status.js'
import { carHead, carGet, carPut, carPost } from './car.js'
import { uploadPost } from './upload.js'
import { userLoginPost, userTokensPost, userTokensGet, userTokensDelete, userUploadsGet, userUploadsDelete, userAccountGet, userUploadsRename, userInfoGet } from './user.js'
import { userLoginPost, userTokensPost, userTokensGet, userTokensDelete, userUploadsGet, userUploadsDelete, userAccountGet, userUploadsRename, userInfoGet, userRequestPost } from './user.js'
import { pinDelete, pinGet, pinPost, pinsGet } from './pins.js'
import { metricsGet } from './metrics.js'
import { versionGet } from './version.js'
Expand Down Expand Up @@ -91,6 +91,7 @@ router.delete('/user/uploads/:cid', auth['👤🗑️'](userUploadsDelete))
router.post('/user/uploads/:cid/rename', auth['👤'](userUploadsRename))
router.get('/user/tokens', auth['👤'](userTokensGet))
router.post('/user/tokens', auth['👤'](userTokensPost))
router.post('/user/request', auth['👤'](userRequestPost))
router.delete('/user/tokens/:id', auth['👤🗑️'](userTokensDelete))
router.get('/user/account', auth['👤'](userAccountGet))
router.get('/user/info', auth['👤'](userInfoGet))
Expand Down
128 changes: 118 additions & 10 deletions packages/api/src/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import * as JWT from './utils/jwt.js'
import { JSONResponse } from './utils/json-response.js'
import { JWT_ISSUER } from './constants.js'
import { HTTPError } from './errors.js'
import { getTagValue, hasTag } from './utils/tags.js'
import { getTagValue, hasPendingTagProposal, hasTag } from './utils/tags.js'
import {
NO_READ_OR_WRITE,
READ_WRITE,
maintenanceHandler
} from './maintenance.js'

/**
* @typedef {{ _id: string, issuer: string }} User
* @typedef {{ _id: string, name: string }} AuthToken
Expand Down Expand Up @@ -42,15 +43,16 @@ async function loginOrRegister (request, env) {
throw new Error('missing required metadata')
}

const parsed = data.type === 'github'
? parseGitHub(data.data, metadata)
: parseMagic(metadata)
const parsed =
data.type === 'github'
? parseGitHub(data.data, metadata)
: parseMagic(metadata)

let user
// check if maintenance mode
if (env.mode === NO_READ_OR_WRITE) {
if (env.MODE === NO_READ_OR_WRITE) {
return maintenanceHandler()
} else if (env.mode === READ_WRITE) {
} else if (env.MODE === READ_WRITE) {
user = await env.db.upsertUser(parsed)
} else {
user = await env.db.getUser(parsed.issuer)
Expand Down Expand Up @@ -141,7 +143,10 @@ export async function userAccountGet (request, env) {
* @param {import('./env').Env} env
*/
export async function userInfoGet (request, env) {
const user = await env.db.getUser(request.auth.user.issuer, { includeTags: true })
const user = await env.db.getUser(request.auth.user.issuer, {
includeTags: true,
includeTagProposals: true
})

return new JSONResponse({
info: {
Expand All @@ -152,11 +157,43 @@ export async function userInfoGet (request, env) {
HasPsaAccess: hasTag(user, 'HasPsaAccess', 'true'),
HasSuperHotAccess: hasTag(user, 'HasSuperHotAccess', 'true'),
StorageLimitBytes: getTagValue(user, 'StorageLimitBytes', '')
},
tagProposals: {
HasAccountRestriction: hasPendingTagProposal(user, 'HasAccountRestriction'),
HasDeleteRestriction: hasPendingTagProposal(user, 'HasDeleteRestriction'),
HasPsaAccess: hasPendingTagProposal(user, 'HasPsaAccess'),
HasSuperHotAccess: hasPendingTagProposal(user, 'HasSuperHotAccess'),
StorageLimitBytes: hasPendingTagProposal(user, 'StorageLimitBytes')
}
}
})
}

/**
* Post a new user request.
*
* @param {AuthenticatedRequest} request
* @param {import('./env').Env} env
*/
export async function userRequestPost (request, env) {
const user = request.auth.user
const { tagName, requestedTagValue, userProposalForm } = await request.json()
const res = await env.db.createUserRequest(
user._id,
tagName,
requestedTagValue,
userProposalForm
)

try {
notifySlack(user, tagName, requestedTagValue, userProposalForm, env)
} catch (e) {
console.error('Failed to notify Slack: ', e)
}

return new JSONResponse(res)
}

/**
* Retrieve user auth tokens.
*
Expand Down Expand Up @@ -220,9 +257,16 @@ export async function userUploadsGet (request, env) {
})

const oldest = uploads[uploads.length - 1]
const headers = uploads.length === size
? { Link: `<${requestUrl.pathname}?size=${size}&before=${encodeURIComponent(oldest.created)}>; rel="next"` }
: undefined
const headers =
uploads.length === size
? {
Link: `<${
requestUrl.pathname
}?size=${size}&before=${encodeURIComponent(
oldest.created
)}>; rel="next"`
}
: undefined
return new JSONResponse(uploads, { headers })
}

Expand Down Expand Up @@ -259,3 +303,67 @@ export async function userUploadsRename (request, env) {
const res = await env.db.renameUpload(user, cid, name)
return new JSONResponse(res)
}

/**
*
* @param {number} userId
* @param {string} userProposalForm
* @param {string} tagName
* @param {string} requestedTagValue
* @param {DBClient} db
*/
const notifySlack = async (
user,
tagName,
requestedTagValue,
userProposalForm,
env
) => {
const webhookUrl = env.SLACK_USER_REQUEST_WEBHOOK_URL

if (!webhookUrl) {
return
}

/** @type {import('../bindings').RequestForm} */
let form
try {
form = JSON.parse(userProposalForm)
} catch (e) {
console.error('Failed to parse user request form: ', e)
return
}

fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
text: `
>*Username*
>${user.name}
>
>*Email*
>${user.email}
>
>*User Id*
>${user._id}
>
>*Requested Tag Name*
>${tagName}
>
>*Requested Tag Value*
>${requestedTagValue}
>${form
.map(
({ label, value }) => `
>*${label}*
>${value}
>`
)
.join('')}
`
})
})
}
11 changes: 11 additions & 0 deletions packages/api/src/utils/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,14 @@ export function hasTag (user, tagName, value) {
)
)
}

export function hasPendingTagProposal (user, tagName) {
return Boolean(
user.tagProposals?.find(
(proposal) =>
proposal.tag === tagName &&
!proposal.admin_decision_type &&
!proposal.deleted_at
)
)
}
Loading

0 comments on commit 1347ed5

Please sign in to comment.