Skip to content

Commit

Permalink
Add editable username
Browse files Browse the repository at this point in the history
  • Loading branch information
bcomnes committed Dec 8, 2022
1 parent 2885ecd commit 405e1f4
Show file tree
Hide file tree
Showing 24 changed files with 208 additions and 57 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ COPY . .
# Running npm install
RUN npm install --omit=dev

RUN node scripts/bootstrap-yt-dlp.js
# RUN node scripts/bootstrap-yt-dlp.js

# Create a user group 'nodegroup', create a user 'nodeuser' under 'nodegroup' and chown all the files to the app user.
RUN addgroup -S nodegroup && \
Expand Down
4 changes: 3 additions & 1 deletion routes/api/user/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { getUser } from './get-user.js'
import { putUser } from './put-user.js'

export default async function userRoutes (fastify, opts) {
await Promise.all([
getUser(fastify, opts)
getUser(fastify, opts),
putUser(fastify, opts)
])
}
53 changes: 53 additions & 0 deletions routes/api/user/put-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import SQL from '@nearform/sql'

export async function putUser (fastify, opts) {
fastify.put(
'/',
{
preHandler: fastify.auth([fastify.verifyJWT]),
schema: {
body: {
type: 'object',
additionalProperties: false,
minProperties: 1,
properties: {
username: {
type: 'string',
minLength: 1,
maxLength: 100
},
password: {
type: 'string',
minLength: 8,
maxLength: 50
}
}
}
}
},
async function putUserHandler (request, reply) {
return fastify.pg.transact(async client => {
const userID = request.user.id
const user = request.body

const updates = []

if (user.username) updates.push(SQL`username = ${user.username}`)
if (user.password) updates.push(SQL`password = crypt(${user.password}, gen_salt('bf'))`)

if (updates.length > 0) {
const query = SQL`
update users
set ${SQL.glue(updates, ' , ')}
where id = ${userID}
`
await client.query(query)
}

return {
status: 'ok'
}
})
}
)
}
2 changes: 0 additions & 2 deletions routes/api/user/user-props.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@

export const userJsonSchema = {

id: { type: 'string', format: 'uuid' },
email: { type: 'string', format: 'email' },
username: { type: 'string' },
Expand Down
14 changes: 10 additions & 4 deletions web/account/client.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
/* eslint-env browser */
import { Component, html, render, useEffect, useState } from 'uland-isomorphic'
import { Component, html, render, useEffect, useState, useCallback } from 'uland-isomorphic'
import { useUser } from '../hooks/useUser.js'
import { useWindow } from '../hooks/useWindow.js'
import { useLSP } from '../hooks/useLSP.js'
import { usernameField } from './username/username-field.js'

export const page = Component(() => {
const state = useLSP()
const { user, loading } = useUser()
const window = useWindow()

const [dataReload, setDataReload] = useState(0)
const reload = useCallback(() => {
setDataReload(dataReload + 1)
}, [dataReload, setDataReload])

const { user, loading } = useUser({ reload: dataReload })

const [requestingEmailVerification, setRequestingEmailVerification] = useState(false)
const [emailVerificationRequested, setEmailVerificationRequested] = useState(false)

Expand Down Expand Up @@ -47,8 +54,7 @@ export const page = Component(() => {
return html`
<div>
<dl>
<dt>username</dt>
<dd>${user?.username}</dd>
${usernameField({ user, reload })}
<dt>email${!loading && user?.email_confirmed === false ? html`<span> (unconfirmed)</span>` : null}</dt>
<dd>${user?.email}${!loading && user?.email_confirmed === false ? html`<button ?disabled=${requestingEmailVerification || emailVerificationRequested} onclick=${handleClick}>${emailVerificationRequested ? 'Email verification sent' : 'Send confirmation email'}</button>` : null}</dd>
<dt>created at</dt>
Expand Down
51 changes: 51 additions & 0 deletions web/account/username/username-edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Component, html, useState, useRef, useCallback } from 'uland-isomorphic'

export const usernameEdit = Component(({ user, onSave, onCancelEdit }) => {
const [error, setError] = useState(null)
const [disabled, setDisabled] = useState(false)
const formRef = useRef()

const handleSave = useCallback(async (ev) => {
ev.preventDefault()
setDisabled(true)
setError(null)

const form = formRef.current

const username = form.username.value

const formState = {
username
}

try {
await onSave(formState)
} catch (err) {
setDisabled(false)
setError(err)
}
}, [setDisabled, setError, formRef?.current, onSave])

return html`
<div class='bc-account-username-edit'>
<form ref="${formRef}" class="bc-account-username-edit-form" id="bc-account-username-edit-form" onsubmit=${handleSave}>
<fieldset ?disabled=${disabled}>
<legend class="bc-account-username-edit-legend">Edit username</legend>
<div>
<label class='block'>
username:
<input class='block' type="text" name="username" value="${user.username}"/>
</label>
</div>
<div class="bc-account-username-edit-submit-line">
<div class="button-cluster">
${onSave ? html`<input name="submit-button" type="submit">` : null}
${onCancelEdit ? html`<button onClick=${onCancelEdit}>Cancel</button>` : null}
</div>
</div>
${error ? html`<div class="error-box">${error.message}</div>` : null}
</fieldset>
</form>
</div>
`
})
52 changes: 52 additions & 0 deletions web/account/username/username-field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* eslint-env browser */
import { Component, html, useState, useCallback } from 'uland-isomorphic'
import { useLSP } from '../../hooks/useLSP.js'
import { usernameEdit } from './username-edit.js'
import { usernameView } from './username-view.js'

export const usernameField = Component(({ user, reload }) => {
const state = useLSP()
const [editing, setEditing] = useState(false)

const handleEdit = useCallback(() => {
setEditing(true)
}, [setEditing])

const handleCancelEdit = useCallback(() => {
setEditing(false)
}, [setEditing])

const handleSave = useCallback(async ({ username }) => {
const endpoint = `${state.apiUrl}/user`
const response = await fetch(endpoint, {
method: 'put',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({ username })
})

if (response.ok && response.headers.get('content-type')?.includes('application/json')) {
console.log(await response.json())
setEditing(false)
reload()
} else {
throw new Error(`${response.status} ${response.statusText}: ${await response.text()}`)
}
}, [user?.username, state.apiUrl, setEditing])

return html`
${
editing
? usernameEdit({
user,
onSave: handleSave,
onCancelEdit: handleCancelEdit
})
: usernameView({
user,
onEdit: handleEdit
})
}
`
})
11 changes: 11 additions & 0 deletions web/account/username/username-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Component, html } from 'uland-isomorphic'

export const usernameView = Component(({ user, onEdit }) => {
return html`
<dt>username</dt>
<dd>
${user?.username}
<button onClick=${onEdit}>Edit</button>
</dd>
`
})
6 changes: 2 additions & 4 deletions web/admin/flags/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ export const page = Component(() => {
headers: {
'accept-encoding': 'application/json'
},
signal: controller.signal,
credentials: 'include'
signal: controller.signal
})

if (response.ok && response.headers.get('content-type')?.includes('application/json')) {
Expand Down Expand Up @@ -81,8 +80,7 @@ export const page = Component(() => {
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(payload),
credentials: 'include'
body: JSON.stringify(payload)
})

if (response.ok) {
Expand Down
6 changes: 2 additions & 4 deletions web/bookmarks/add/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ export const page = Component(() => {
const response = await fetch(`${state.apiUrl}/bookmarks?${searchParams}`, {
headers: {
'content-type': 'application/json'
},
credentials: 'include'
}
})

if (response.ok && response.headers.get('content-type')?.includes('application/json')) {
Expand Down Expand Up @@ -87,8 +86,7 @@ export const page = Component(() => {
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(payload),
credentials: 'include'
body: JSON.stringify(payload)
})

if (response.ok) {
Expand Down
3 changes: 1 addition & 2 deletions web/bookmarks/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ export const page = Component(() => {
headers: {
'accept-encoding': 'application/json'
},
signal: controller.signal,
credentials: 'include'
signal: controller.signal
})

if (response.ok && response.headers.get('content-type')?.includes('application/json')) {
Expand Down
3 changes: 1 addition & 2 deletions web/bookmarks/view/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ export const page = Component(() => {
method: 'get',
headers: {
'accept-encoding': 'application/json'
},
credentials: 'include'
}
})

if (response.ok && response.headers.get('content-type')?.includes('application/json')) {
Expand Down
15 changes: 5 additions & 10 deletions web/components/bookmark/bookmark-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ export const bookmarkList = Component(({ bookmark, reload, onDelete }) => {
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(payload),
credentials: 'include'
body: JSON.stringify(payload)
})

reload()
Expand All @@ -40,8 +39,7 @@ export const bookmarkList = Component(({ bookmark, reload, onDelete }) => {
method: 'delete',
headers: {
'accept-encoding': 'application/json'
},
credentials: 'include'
}
})

setDeleted(true)
Expand All @@ -57,8 +55,7 @@ export const bookmarkList = Component(({ bookmark, reload, onDelete }) => {
},
body: JSON.stringify({
toread: !bookmark.toread
}),
credentials: 'include'
})
})

// TODO: optimistic updates without full reload
Expand All @@ -74,8 +71,7 @@ export const bookmarkList = Component(({ bookmark, reload, onDelete }) => {
},
body: JSON.stringify({
starred: !bookmark.starred
}),
credentials: 'include'
})
})

// TODO: optimistic updates without full reload
Expand All @@ -91,8 +87,7 @@ export const bookmarkList = Component(({ bookmark, reload, onDelete }) => {
},
body: JSON.stringify({
sensitive: !bookmark.sensitive
}),
credentials: 'include'
})
})

// TODO: optimistic updates without full reload
Expand Down
6 changes: 2 additions & 4 deletions web/components/episode/episode-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ export const episodeList = Component(({ episode, reload, onDelete }) => {
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(payload),
credentials: 'include'
body: JSON.stringify(payload)
})

reload()
Expand All @@ -41,8 +40,7 @@ export const episodeList = Component(({ episode, reload, onDelete }) => {
method: 'delete',
headers: {
'accept-encoding': 'application/json'
},
credentials: 'include'
}
})

setDeleted(true)
Expand Down
3 changes: 1 addition & 2 deletions web/components/feed-header/feed-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ export const feedHeader = Component(({ feed, feeds, reload }) => {
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(payload),
credentials: 'include'
body: JSON.stringify(payload)
})

reload()
Expand Down
3 changes: 1 addition & 2 deletions web/episodes/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ export const page = Component(() => {
headers: {
'accept-encoding': 'application/json'
},
signal: controller.signal,
credentials: 'include'
signal: controller.signal
})

if (response.ok && response.headers.get('content-type')?.includes('application/json')) {
Expand Down
3 changes: 1 addition & 2 deletions web/episodes/view/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ export const page = Component(() => {
headers: {
'accept-encoding': 'application/json'
},
signal: controller.signal,
credentials: 'include'
signal: controller.signal
})

if (response.ok && response.headers.get('content-type')?.includes('application/json')) {
Expand Down

0 comments on commit 405e1f4

Please sign in to comment.