diff --git a/app/components/ving/Dropzone.vue b/app/components/ving/Dropzone.vue index dd6ea286..c6687a0e 100644 --- a/app/components/ving/Dropzone.vue +++ b/app/components/ving/Dropzone.vue @@ -16,7 +16,7 @@ import '../node_modules/dropzone/dist/dropzone.css' Dropzone.autoDiscover = false; const getSignedURL = async (file) => { - const response = await useRest(`/api/${useRestVersion()}/s3file?includeMeta=true`, { + const response = await useRest(`/api/${useRestVersion()}/s3files?includeMeta=true`, { method: 'POST', body: { contentType: file.type, filename: file.name, diff --git a/app/composables/ving/useAdminLinks.mjs b/app/composables/ving/useAdminLinks.mjs index b69ef9d5..389b74fa 100644 --- a/app/composables/ving/useAdminLinks.mjs +++ b/app/composables/ving/useAdminLinks.mjs @@ -8,9 +8,9 @@ export default () => { const links = computed(() => { const out = [ - { label: 'Cron Jobs', to: '/cronjob', icon: 'ph:clock' }, + { label: 'Cron Jobs', to: '/cronjobs', icon: 'ph:clock' }, { label: 'System Wide Alert', to: '/system-wide-alert', icon: 'ph:megaphone' }, - { label: 'Users', to: '/user/admin', icon: 'ph:users' }, + { label: 'Users', to: '/users/admin', icon: 'ph:users' }, ]; return out; }); diff --git a/app/composables/ving/useCurrentUser.mjs b/app/composables/ving/useCurrentUser.mjs index 5e776c98..4fe1bbeb 100644 --- a/app/composables/ving/useCurrentUser.mjs +++ b/app/composables/ving/useCurrentUser.mjs @@ -3,12 +3,12 @@ import { isUndefined } from '#ving/utils/identify.mjs'; export const useCurrentUser = () => useVingRecord({ id: 'currentUser', - fetchApi: `/api/${useRestVersion()}/user/whoami`, - createApi: `/api/${useRestVersion()}/user`, + fetchApi: `/api/${useRestVersion()}/users/whoami`, + createApi: `/api/${useRestVersion()}/users`, query: { includeOptions: true, includeMeta: true, includeLinks: true }, extendedActions: { async login(login, password) { - const response = await useRest(`/api/${useRestVersion()}/session`, { + const response = await useRest(`/api/${useRestVersion()}/sessions`, { method: 'post', body: { login, @@ -24,7 +24,7 @@ export const useCurrentUser = () => useVingRecord({ }, async logout() { - const response = await useRest(`/api/${useRestVersion()}/session`, { + const response = await useRest(`/api/${useRestVersion()}/sessions`, { method: 'delete', }); this.setState({}); diff --git a/app/composables/ving/useMessageBus.mjs b/app/composables/ving/useMessageBus.mjs index 577f3420..cc63252e 100644 --- a/app/composables/ving/useMessageBus.mjs +++ b/app/composables/ving/useMessageBus.mjs @@ -29,7 +29,7 @@ export default async function useMessageBus() { const reconnect = () => setTimeout(tryToSetup, wait); const setupBusHandler = () => { - bus = new EventSource(`/api/${useRestVersion()}/user/messagebus`); + bus = new EventSource(`/api/${useRestVersion()}/users/messagebus`); bus.onmessage = (event) => { const message = JSON.parse(event.data); switch (message.type) { diff --git a/app/composables/ving/useRest.mjs b/app/composables/ving/useRest.mjs index f1348fdc..15d9820f 100644 --- a/app/composables/ving/useRest.mjs +++ b/app/composables/ving/useRest.mjs @@ -14,7 +14,7 @@ * * The `error` is `null` unless there is an error, and the `data` contains an object response from the endpoint. * @example - * const response = await useRest('/api/v1/user/xxx') + * const response = await useRest('/api/v1/users/xxx') */ export default async function (url, behavior = {}) { const notify = useNotify(); diff --git a/app/composables/ving/useUserSettingsButtons.mjs b/app/composables/ving/useUserSettingsButtons.mjs index ab1b9770..6b1598f9 100644 --- a/app/composables/ving/useUserSettingsButtons.mjs +++ b/app/composables/ving/useUserSettingsButtons.mjs @@ -9,7 +9,7 @@ export default () => { const currentUser = useCurrentUser(); const buttons = computed(() => { const out = [ - { label: 'Sign Out', to: '/user/logout', icon: 'ph:door', severity: 'primary' }, + { label: 'Sign Out', to: '/users/logout', icon: 'ph:door', severity: 'primary' }, ]; if (currentUser.props?.admin) out.push({ label: 'Admin', to: '/admin', icon: 'ph:users', severity: 'secondary' }); diff --git a/app/composables/ving/useUserSettingsLinks.mjs b/app/composables/ving/useUserSettingsLinks.mjs index 45a1f806..9bd5de33 100644 --- a/app/composables/ving/useUserSettingsLinks.mjs +++ b/app/composables/ving/useUserSettingsLinks.mjs @@ -9,12 +9,12 @@ export default () => { const currentUser = useCurrentUser(); const links = computed(() => { const out = [ - { label: 'Profile', to: '/user/settings', icon: 'ph:user' }, - { label: 'Account', to: '/user/settings/account', icon: 'ph:key' }, - { label: 'Preferences', to: '/user/settings/preferences', icon: 'ph:sliders' }, + { label: 'Profile', to: '/users/settings', icon: 'ph:user' }, + { label: 'Account', to: '/users/settings/account', icon: 'ph:key' }, + { label: 'Preferences', to: '/users/settings/preferences', icon: 'ph:sliders' }, ]; if (currentUser?.props?.developer == true) - out.push({ label: 'API Keys', to: '/user/settings/apikeys', icon: 'ph:lock' }); + out.push({ label: 'API Keys', to: '/users/settings/apikeys', icon: 'ph:lock' }); return out; }); return links; diff --git a/app/composables/ving/useVingKind.mjs b/app/composables/ving/useVingKind.mjs index 3db8f694..3cda24da 100644 --- a/app/composables/ving/useVingKind.mjs +++ b/app/composables/ving/useVingKind.mjs @@ -145,7 +145,7 @@ class VingKind { * @param {object} options Modify the behavior of this call. * @returns {Promise} A promise containing the response to the call. * @example - * const result = Users.call('post', '/user/xxx/send-reset-password', {os:'Windows'}); + * const result = Users.call('post', '/users/xxx/send-reset-password', {os:'Windows'}); */ async call(method, url, query = {}, options = {}) { const response = await useRest(url, { diff --git a/app/composables/ving/useVingRecord.mjs b/app/composables/ving/useVingRecord.mjs index 6bf12418..a8506b4d 100644 --- a/app/composables/ving/useVingRecord.mjs +++ b/app/composables/ving/useVingRecord.mjs @@ -32,8 +32,8 @@ import { isObject, isUndefined } from '#ving/utils/identify.mjs'; * @example * const user = useVingRecord({ * id : 'xxx', - * fetchApi: '/api/v1/user/xxx', - * createApi: '/api/v1/user', + * fetchApi: '/api/v1/users/xxx', + * createApi: '/api/v1/users', * query : { includeMeta : true }, * }); * await user.fetch(); diff --git a/app/layouts/default.vue b/app/layouts/default.vue index 2e3bcb9e..29fdcd4d 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -28,7 +28,7 @@ - + {{ currentUser.meta?.displayName }} @@ -41,7 +41,7 @@ - + Sign In @@ -82,8 +82,8 @@ const topNav = [ const userMenu = computed(() => { const out = [ - { label: 'Settings', to: '/user/settings', icon: 'fa6-solid:sliders' }, - { label: 'Sign Out', to: '/user/logout', icon: 'fa6-solid:door-closed' }, + { label: 'Settings', to: '/users/settings', icon: 'fa6-solid:sliders' }, + { label: 'Sign Out', to: '/users/logout', icon: 'fa6-solid:door-closed' }, ]; if (currentUser.props.admin) out.unshift({ label: 'Admin', to: '/admin', icon: 'prime:user-plus' }); diff --git a/app/middleware/auth.mjs b/app/middleware/auth.mjs index 0c8c0066..0e73f2ad 100644 --- a/app/middleware/auth.mjs +++ b/app/middleware/auth.mjs @@ -2,11 +2,11 @@ export default defineNuxtRouteMiddleware(async (to, from) => { const currentUser = useCurrentUser(); const isAuthenticated = await currentUser.isAuthenticated(); if (!isAuthenticated) { - return await navigateTo(`/user/login?redirectAfter=${to.fullPath}`); + return await navigateTo(`/users/login?redirectAfter=${to.fullPath}`); } else if (!currentUser.props?.verifiedEmail) { - if (to.fullPath != '/user/logout') { - return await navigateTo(`/user/must-verify-email?redirectAfter=${to.fullPath}`); + if (to.fullPath != '/users/logout') { + return await navigateTo(`/users/must-verify-email?redirectAfter=${to.fullPath}`); } } }); \ No newline at end of file diff --git a/app/pages/admin.vue b/app/pages/admin.vue index bd66de78..bb13de81 100644 --- a/app/pages/admin.vue +++ b/app/pages/admin.vue @@ -2,5 +2,5 @@ \ No newline at end of file diff --git a/app/pages/cronjob/[id]/edit.vue b/app/pages/cronjobs/[id]/edit.vue similarity index 96% rename from app/pages/cronjob/[id]/edit.vue rename to app/pages/cronjobs/[id]/edit.vue index fdccb811..90d0ea76 100644 --- a/app/pages/cronjob/[id]/edit.vue +++ b/app/pages/cronjobs/[id]/edit.vue @@ -42,8 +42,8 @@ const notify = useNotify(); const id = route.params.id.toString(); const cronjob = useVingRecord({ id, - fetchApi: `/api/${useRestVersion()}/cronjob/${id}`, - createApi: `/api/${useRestVersion()}/cronjob`, + fetchApi: `/api/${useRestVersion()}/cronjobs/${id}`, + createApi: `/api/${useRestVersion()}/cronjobs`, query: { includeMeta: true, includeOptions: true }, onUpdate() { notify.success('Updated Cron Job.'); diff --git a/app/pages/cronjob/index.vue b/app/pages/cronjobs/index.vue similarity index 93% rename from app/pages/cronjob/index.vue rename to app/pages/cronjobs/index.vue index 1220bbfd..dc048c09 100644 --- a/app/pages/cronjob/index.vue +++ b/app/pages/cronjobs/index.vue @@ -48,10 +48,13 @@ definePageMeta({ middleware: ['auth', 'admin'] }); const cronjobs = useVingKind({ - listApi: `/api/${useRestVersion()}/cronjob`, - createApi: `/api/${useRestVersion()}/cronjob`, + listApi: `/api/${useRestVersion()}/cronjobs`, + createApi: `/api/${useRestVersion()}/cronjobs`, query: { includeMeta: true, sortBy: 'schedule', sortOrder: 'asc' }, newDefaults: { schedule: '* * * * *', handler: 'Test', enabled: true }, + onCreate(props) { + navigateTo(props.links.edit.href) + }, }); await Promise.all([ cronjobs.search(), diff --git a/app/pages/user/[id]/profile.vue b/app/pages/users/[id]/profile.vue similarity index 92% rename from app/pages/user/[id]/profile.vue rename to app/pages/users/[id]/profile.vue index 4a3b6ca7..a08346de 100644 --- a/app/pages/user/[id]/profile.vue +++ b/app/pages/users/[id]/profile.vue @@ -33,8 +33,8 @@ const route = useRoute(); const user = useVingRecord({ - fetchApi: `/api/${useRestVersion()}/user/${route.params.id}`, - createApi: `/api/${useRestVersion()}/user`, + fetchApi: `/api/${useRestVersion()}/users/${route.params.id}`, + createApi: `/api/${useRestVersion()}/users`, query: { includeMeta: true, includeOptions: true }, }); await user.fetch() diff --git a/app/pages/user/[id]/reset-password.vue b/app/pages/users/[id]/reset-password.vue similarity index 91% rename from app/pages/user/[id]/reset-password.vue rename to app/pages/users/[id]/reset-password.vue index 9a5c040d..b04d9e4e 100644 --- a/app/pages/user/[id]/reset-password.vue +++ b/app/pages/users/[id]/reset-password.vue @@ -5,7 +5,7 @@

Reset Password

Remember your account? - Sign in + Sign in @@ -41,14 +41,14 @@ const config = useRuntimeConfig(); const notify = useNotify(); async function resetPassword() { notify.info('Please wait while we reset your password...'); - const response = await useRest(`/api/${useRestVersion()}/user/${route.params.id}/reset-password`, { + const response = await useRest(`/api/${useRestVersion()}/users/${route.params.id}/reset-password`, { method: 'post', query: { includeOptions: true }, body: { code: newPassword.code, password: newPassword.password }, }); if (!response.error) { notify.success('Password changed.'); - await navigateTo('/user/login'); + await navigateTo('/users/login'); } } \ No newline at end of file diff --git a/app/pages/user/admin/[id].vue b/app/pages/users/admin/[id].vue similarity index 97% rename from app/pages/user/admin/[id].vue rename to app/pages/users/admin/[id].vue index dc5fdade..9e89604b 100644 --- a/app/pages/user/admin/[id].vue +++ b/app/pages/users/admin/[id].vue @@ -88,8 +88,8 @@ const notify = useNotify(); const id = route.params.id.toString(); const user = useVingRecord({ id, - fetchApi: `/api/${useRestVersion()}/user/${id}`, - createApi: `/api/${useRestVersion()}/user`, + fetchApi: `/api/${useRestVersion()}/users/${id}`, + createApi: `/api/${useRestVersion()}/users`, query: { includeMeta: true, includeOptions: true }, onUpdate() { notify.success('Updated user.'); diff --git a/app/pages/user/admin/index.vue b/app/pages/users/admin/index.vue similarity index 95% rename from app/pages/user/admin/index.vue rename to app/pages/users/admin/index.vue index 174df1af..2e2a5d6f 100644 --- a/app/pages/user/admin/index.vue +++ b/app/pages/users/admin/index.vue @@ -80,10 +80,13 @@ definePageMeta({ }); const users = useVingKind({ - listApi: `/api/${useRestVersion()}/user`, - createApi: `/api/${useRestVersion()}/user`, + listApi: `/api/${useRestVersion()}/users`, + createApi: `/api/${useRestVersion()}/users`, query: { includeMeta: true, sortBy: 'username', sortOrder: 'asc' }, newDefaults: { username: '', realName: '', email: '' }, + onCreate(props) { + navigateTo(props.links.edit.href) + }, }); await users.search(); diff --git a/app/pages/user/create.vue b/app/pages/users/create.vue similarity index 96% rename from app/pages/user/create.vue rename to app/pages/users/create.vue index f9cf6519..cc837692 100644 --- a/app/pages/user/create.vue +++ b/app/pages/users/create.vue @@ -5,7 +5,7 @@

Create an Account

Already have an account? - Sign in + Sign in diff --git a/app/pages/user/login.vue b/app/pages/users/login.vue similarity index 91% rename from app/pages/user/login.vue rename to app/pages/users/login.vue index fd48f943..139a0d76 100644 --- a/app/pages/user/login.vue +++ b/app/pages/users/login.vue @@ -5,7 +5,7 @@

Welcome Back

Don't have an account? - Create one today! + Create one today! @@ -22,7 +22,7 @@
Forgot your password? - Reset + Reset your password. diff --git a/app/pages/user/logout.vue b/app/pages/users/logout.vue similarity index 100% rename from app/pages/user/logout.vue rename to app/pages/users/logout.vue diff --git a/app/pages/user/must-verify-email.vue b/app/pages/users/must-verify-email.vue similarity index 100% rename from app/pages/user/must-verify-email.vue rename to app/pages/users/must-verify-email.vue diff --git a/app/pages/user/reset-password.vue b/app/pages/users/reset-password.vue similarity index 92% rename from app/pages/user/reset-password.vue rename to app/pages/users/reset-password.vue index c435f024..858914c3 100644 --- a/app/pages/user/reset-password.vue +++ b/app/pages/users/reset-password.vue @@ -5,7 +5,7 @@

Send Password Reset

Remember your account? - Sign in + Sign in
@@ -33,7 +33,7 @@ const email = ref('') const notify = useNotify(); async function sendPasswordReset() { const parser = new ua(navigator.userAgent); - const response = await useRest(`/api/${useRestVersion()}/user/send-password-reset`, { + const response = await useRest(`/api/${useRestVersion()}/users/send-password-reset`, { method: 'post', query: { includeOptions: true }, body: { browser: parser.getBrowser().name, os: parser.getOS().name, email: email.value } diff --git a/app/pages/user/settings/account.vue b/app/pages/users/settings/account.vue similarity index 100% rename from app/pages/user/settings/account.vue rename to app/pages/users/settings/account.vue diff --git a/app/pages/user/settings/apikeys.vue b/app/pages/users/settings/apikeys.vue similarity index 98% rename from app/pages/user/settings/apikeys.vue rename to app/pages/users/settings/apikeys.vue index 10b98eca..d8e19b30 100644 --- a/app/pages/user/settings/apikeys.vue +++ b/app/pages/users/settings/apikeys.vue @@ -92,7 +92,7 @@ const links = useUserSettingsLinks(); const buttons = useUserSettingsButtons(); const apikeys = useVingKind({ listApi: currentUser.links?.apikeys.href, - createApi: `/api/${useRestVersion()}/apikey`, + createApi: `/api/${useRestVersion()}/apikeys`, query: { includeMeta: true, sortBy: 'name', sortOrder: 'asc' }, newDefaults: { name: 'My New API Key', reason: '', url: 'http://', userId: currentUser.props?.id }, }); diff --git a/app/pages/user/settings/index.vue b/app/pages/users/settings/index.vue similarity index 96% rename from app/pages/user/settings/index.vue rename to app/pages/users/settings/index.vue index 5b07157a..d1b2e84e 100644 --- a/app/pages/user/settings/index.vue +++ b/app/pages/users/settings/index.vue @@ -28,7 +28,7 @@ - + View your profile as others see it diff --git a/app/pages/user/settings/preferences.vue b/app/pages/users/settings/preferences.vue similarity index 100% rename from app/pages/user/settings/preferences.vue rename to app/pages/users/settings/preferences.vue diff --git a/app/pages/user/verify-email.vue b/app/pages/users/verify-email.vue similarity index 96% rename from app/pages/user/verify-email.vue rename to app/pages/users/verify-email.vue index 75e26846..ab983fdc 100644 --- a/app/pages/user/verify-email.vue +++ b/app/pages/users/verify-email.vue @@ -29,7 +29,7 @@ if (await currentUser.isAuthenticated()) { } } else { - await navigateTo('/user/login') + await navigateTo('/users/login') } \ No newline at end of file diff --git a/server/api/v1/apikey/[id]/index.delete.mjs b/server/api/v1/apikeys/[id]/index.delete.mjs similarity index 100% rename from server/api/v1/apikey/[id]/index.delete.mjs rename to server/api/v1/apikeys/[id]/index.delete.mjs diff --git a/server/api/v1/apikey/[id]/index.get.mjs b/server/api/v1/apikeys/[id]/index.get.mjs similarity index 100% rename from server/api/v1/apikey/[id]/index.get.mjs rename to server/api/v1/apikeys/[id]/index.get.mjs diff --git a/server/api/v1/apikey/[id]/index.put.mjs b/server/api/v1/apikeys/[id]/index.put.mjs similarity index 100% rename from server/api/v1/apikey/[id]/index.put.mjs rename to server/api/v1/apikeys/[id]/index.put.mjs diff --git a/server/api/v1/apikey/[id]/user.get.mjs b/server/api/v1/apikeys/[id]/user.get.mjs similarity index 100% rename from server/api/v1/apikey/[id]/user.get.mjs rename to server/api/v1/apikeys/[id]/user.get.mjs diff --git a/server/api/v1/apikey/apikey.rest b/server/api/v1/apikeys/apikey.rest similarity index 64% rename from server/api/v1/apikey/apikey.rest rename to server/api/v1/apikeys/apikey.rest index ae137c73..1a4d3f08 100644 --- a/server/api/v1/apikey/apikey.rest +++ b/server/api/v1/apikeys/apikey.rest @@ -1,5 +1,5 @@ ### createUser -POST http://localhost:3000/api/v1/user?includeMeta=true +POST http://localhost:3000/api/v1/users?includeMeta=true Content-Type: application/json { @@ -13,7 +13,7 @@ Content-Type: application/json ### login # @name login -POST http://localhost:3000/api/v1/session?includeMeta=true +POST http://localhost:3000/api/v1/sessions?includeMeta=true Content-Type: application/json { @@ -24,7 +24,7 @@ Content-Type: application/json ### create key # @name createKey -POST http://localhost:3000/api/v1/apikey?includeMeta=true +POST http://localhost:3000/api/v1/apikeys?includeMeta=true Content-Type: application/json { @@ -35,17 +35,17 @@ Content-Type: application/json } ### listKeys -GET http://localhost:3000/api/v1/user/{{login.response.body.$.props.userId}}/apikeys +GET http://localhost:3000/api/v1/users/{{login.response.body.$.props.userId}}/apikeys Content-Type: application/json ### get key -GET http://localhost:3000/api/v1/apikey/{{createKey.response.body.$.props.id}}?includeLinks=true&includeMeta=true&includeRelated=user +GET http://localhost:3000/api/v1/apikeys/{{createKey.response.body.$.props.id}}?includeLinks=true&includeMeta=true&includeRelated=user Cookie: vingSessionId={{login.response.body.$.props.id}} Content-Type: application/json ### login with key # @name loginWithKey -POST http://localhost:3000/api/v1/session?includeMeta=true +POST http://localhost:3000/api/v1/sessions?includeMeta=true Content-Type: application/json { @@ -54,13 +54,13 @@ Content-Type: application/json } ### get user using key -GET http://localhost:3000/api/v1/user/{{loginWithKey.response.body.$.props.userId}}?includeMeta=true +GET http://localhost:3000/api/v1/users/{{loginWithKey.response.body.$.props.userId}}?includeMeta=true Content-Type: application/json Cookie: vingSessionId={{loginWithKey.response.body.$.props.id}} ### put user with key - should fail -PUT http://localhost:3000/api/v1/user/{{loginWithKey.response.body.$.props.userId}} +PUT http://localhost:3000/api/v1/users/{{loginWithKey.response.body.$.props.userId}} Content-Type: application/json Cookie: vingSessionId={{loginWithKey.response.body.$.props.id}} @@ -69,7 +69,7 @@ Cookie: vingSessionId={{loginWithKey.response.body.$.props.id}} } ### put user with key - should succeed -PUT http://localhost:3000/api/v1/user/{{login.response.body.$.props.userId}} +PUT http://localhost:3000/api/v1/users/{{login.response.body.$.props.userId}} Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} @@ -79,6 +79,6 @@ Cookie: vingSessionId={{login.response.body.$.props.id}} ### delete key -DELETE http://localhost:3000/api/v1/apikey/{{createKey.response.body.$.props.id}} +DELETE http://localhost:3000/api/v1/apikeys/{{createKey.response.body.$.props.id}} Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} diff --git a/server/api/v1/apikey/index.get.mjs b/server/api/v1/apikeys/index.get.mjs similarity index 100% rename from server/api/v1/apikey/index.get.mjs rename to server/api/v1/apikeys/index.get.mjs diff --git a/server/api/v1/apikey/index.post.mjs b/server/api/v1/apikeys/index.post.mjs similarity index 100% rename from server/api/v1/apikey/index.post.mjs rename to server/api/v1/apikeys/index.post.mjs diff --git a/server/api/v1/apikey/options.get.mjs b/server/api/v1/apikeys/options.get.mjs similarity index 100% rename from server/api/v1/apikey/options.get.mjs rename to server/api/v1/apikeys/options.get.mjs diff --git a/server/api/v1/cronjob/[id]/index.delete.mjs b/server/api/v1/cronjobs/[id]/index.delete.mjs similarity index 100% rename from server/api/v1/cronjob/[id]/index.delete.mjs rename to server/api/v1/cronjobs/[id]/index.delete.mjs diff --git a/server/api/v1/cronjob/[id]/index.get.mjs b/server/api/v1/cronjobs/[id]/index.get.mjs similarity index 100% rename from server/api/v1/cronjob/[id]/index.get.mjs rename to server/api/v1/cronjobs/[id]/index.get.mjs diff --git a/server/api/v1/cronjob/[id]/index.put.mjs b/server/api/v1/cronjobs/[id]/index.put.mjs similarity index 100% rename from server/api/v1/cronjob/[id]/index.put.mjs rename to server/api/v1/cronjobs/[id]/index.put.mjs diff --git a/server/api/v1/cronjob/index.get.mjs b/server/api/v1/cronjobs/index.get.mjs similarity index 100% rename from server/api/v1/cronjob/index.get.mjs rename to server/api/v1/cronjobs/index.get.mjs diff --git a/server/api/v1/cronjob/index.post.mjs b/server/api/v1/cronjobs/index.post.mjs similarity index 100% rename from server/api/v1/cronjob/index.post.mjs rename to server/api/v1/cronjobs/index.post.mjs diff --git a/server/api/v1/cronjob/options.get.mjs b/server/api/v1/cronjobs/options.get.mjs similarity index 100% rename from server/api/v1/cronjob/options.get.mjs rename to server/api/v1/cronjobs/options.get.mjs diff --git a/server/api/v1/s3file/[id]/avatarusers.delete.mjs b/server/api/v1/s3files/[id]/avatarusers.delete.mjs similarity index 100% rename from server/api/v1/s3file/[id]/avatarusers.delete.mjs rename to server/api/v1/s3files/[id]/avatarusers.delete.mjs diff --git a/server/api/v1/s3file/[id]/avatarusers.get.mjs b/server/api/v1/s3files/[id]/avatarusers.get.mjs similarity index 100% rename from server/api/v1/s3file/[id]/avatarusers.get.mjs rename to server/api/v1/s3files/[id]/avatarusers.get.mjs diff --git a/server/api/v1/s3file/[id]/index.delete.mjs b/server/api/v1/s3files/[id]/index.delete.mjs similarity index 100% rename from server/api/v1/s3file/[id]/index.delete.mjs rename to server/api/v1/s3files/[id]/index.delete.mjs diff --git a/server/api/v1/s3file/[id]/index.get.mjs b/server/api/v1/s3files/[id]/index.get.mjs similarity index 100% rename from server/api/v1/s3file/[id]/index.get.mjs rename to server/api/v1/s3files/[id]/index.get.mjs diff --git a/server/api/v1/s3file/[id]/index.put.mjs b/server/api/v1/s3files/[id]/index.put.mjs similarity index 100% rename from server/api/v1/s3file/[id]/index.put.mjs rename to server/api/v1/s3files/[id]/index.put.mjs diff --git a/server/api/v1/s3file/[id]/user.get.mjs b/server/api/v1/s3files/[id]/user.get.mjs similarity index 100% rename from server/api/v1/s3file/[id]/user.get.mjs rename to server/api/v1/s3files/[id]/user.get.mjs diff --git a/server/api/v1/s3file/index.get.mjs b/server/api/v1/s3files/index.get.mjs similarity index 100% rename from server/api/v1/s3file/index.get.mjs rename to server/api/v1/s3files/index.get.mjs diff --git a/server/api/v1/s3file/index.post.mjs b/server/api/v1/s3files/index.post.mjs similarity index 100% rename from server/api/v1/s3file/index.post.mjs rename to server/api/v1/s3files/index.post.mjs diff --git a/server/api/v1/s3file/options.get.mjs b/server/api/v1/s3files/options.get.mjs similarity index 100% rename from server/api/v1/s3file/options.get.mjs rename to server/api/v1/s3files/options.get.mjs diff --git a/server/api/v1/session/[id].delete.mjs b/server/api/v1/sessions/[id].delete.mjs similarity index 100% rename from server/api/v1/session/[id].delete.mjs rename to server/api/v1/sessions/[id].delete.mjs diff --git a/server/api/v1/session/[id].get.mjs b/server/api/v1/sessions/[id].get.mjs similarity index 100% rename from server/api/v1/session/[id].get.mjs rename to server/api/v1/sessions/[id].get.mjs diff --git a/server/api/v1/session/index.delete.mjs b/server/api/v1/sessions/index.delete.mjs similarity index 100% rename from server/api/v1/session/index.delete.mjs rename to server/api/v1/sessions/index.delete.mjs diff --git a/server/api/v1/session/index.post.mjs b/server/api/v1/sessions/index.post.mjs similarity index 100% rename from server/api/v1/session/index.post.mjs rename to server/api/v1/sessions/index.post.mjs diff --git a/server/api/v1/session/session.rest b/server/api/v1/sessions/session.rest similarity index 63% rename from server/api/v1/session/session.rest rename to server/api/v1/sessions/session.rest index 186515c8..c3a5fee3 100644 --- a/server/api/v1/session/session.rest +++ b/server/api/v1/sessions/session.rest @@ -1,6 +1,6 @@ ### login # @name login -POST http://localhost:3000/api/v1/session +POST http://localhost:3000/api/v1/sessions Content-Type: application/json { @@ -10,19 +10,19 @@ Content-Type: application/json } ### get -GET http://localhost:3000/api/v1/session/{{login.response.body.$.props.id}}?includeRelated=user +GET http://localhost:3000/api/v1/sessions/{{login.response.body.$.props.id}}?includeRelated=user Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} ### logout -DELETE http://localhost:3000/api/v1/session/{{login.response.body.$.props.id}} +DELETE http://localhost:3000/api/v1/sessions/{{login.response.body.$.props.id}} Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} ### create user -POST http://localhost:3000/api/v1/user?includeMeta=true +POST http://localhost:3000/api/v1/users?includeMeta=true Content-Type: application/json { @@ -33,6 +33,6 @@ Content-Type: application/json } ### delete user -DELETE http://localhost:3000/api/v1/user/{{login.response.body.$.props.userId}} +DELETE http://localhost:3000/api/v1/users/{{login.response.body.$.props.userId}} Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} diff --git a/server/api/v1/user/[id]/apikeys.delete.mjs b/server/api/v1/users/[id]/apikeys.delete.mjs similarity index 100% rename from server/api/v1/user/[id]/apikeys.delete.mjs rename to server/api/v1/users/[id]/apikeys.delete.mjs diff --git a/server/api/v1/user/[id]/apikeys.get.mjs b/server/api/v1/users/[id]/apikeys.get.mjs similarity index 100% rename from server/api/v1/user/[id]/apikeys.get.mjs rename to server/api/v1/users/[id]/apikeys.get.mjs diff --git a/server/api/v1/user/[id]/avatar.get.mjs b/server/api/v1/users/[id]/avatar.get.mjs similarity index 100% rename from server/api/v1/user/[id]/avatar.get.mjs rename to server/api/v1/users/[id]/avatar.get.mjs diff --git a/server/api/v1/user/[id]/become.post.mjs b/server/api/v1/users/[id]/become.post.mjs similarity index 100% rename from server/api/v1/user/[id]/become.post.mjs rename to server/api/v1/users/[id]/become.post.mjs diff --git a/server/api/v1/user/[id]/import-avatar.put.mjs b/server/api/v1/users/[id]/import-avatar.put.mjs similarity index 100% rename from server/api/v1/user/[id]/import-avatar.put.mjs rename to server/api/v1/users/[id]/import-avatar.put.mjs diff --git a/server/api/v1/user/[id]/index.delete.mjs b/server/api/v1/users/[id]/index.delete.mjs similarity index 100% rename from server/api/v1/user/[id]/index.delete.mjs rename to server/api/v1/users/[id]/index.delete.mjs diff --git a/server/api/v1/user/[id]/index.get.mjs b/server/api/v1/users/[id]/index.get.mjs similarity index 100% rename from server/api/v1/user/[id]/index.get.mjs rename to server/api/v1/users/[id]/index.get.mjs diff --git a/server/api/v1/user/[id]/index.mjs b/server/api/v1/users/[id]/index.mjs similarity index 100% rename from server/api/v1/user/[id]/index.mjs rename to server/api/v1/users/[id]/index.mjs diff --git a/server/api/v1/user/[id]/index.put.mjs b/server/api/v1/users/[id]/index.put.mjs similarity index 100% rename from server/api/v1/user/[id]/index.put.mjs rename to server/api/v1/users/[id]/index.put.mjs diff --git a/server/api/v1/user/[id]/reset-password.post.mjs b/server/api/v1/users/[id]/reset-password.post.mjs similarity index 100% rename from server/api/v1/user/[id]/reset-password.post.mjs rename to server/api/v1/users/[id]/reset-password.post.mjs diff --git a/server/api/v1/user/[id]/s3files.delete.mjs b/server/api/v1/users/[id]/s3files.delete.mjs similarity index 100% rename from server/api/v1/user/[id]/s3files.delete.mjs rename to server/api/v1/users/[id]/s3files.delete.mjs diff --git a/server/api/v1/user/[id]/s3files.get.mjs b/server/api/v1/users/[id]/s3files.get.mjs similarity index 100% rename from server/api/v1/user/[id]/s3files.get.mjs rename to server/api/v1/users/[id]/s3files.get.mjs diff --git a/server/api/v1/user/[id]/send-verify-email.post.mjs b/server/api/v1/users/[id]/send-verify-email.post.mjs similarity index 92% rename from server/api/v1/user/[id]/send-verify-email.post.mjs rename to server/api/v1/users/[id]/send-verify-email.post.mjs index d8c43e18..bd310208 100644 --- a/server/api/v1/user/[id]/send-verify-email.post.mjs +++ b/server/api/v1/users/[id]/send-verify-email.post.mjs @@ -18,7 +18,7 @@ export default defineEventHandler(async (event) => { await useCache().set('verifyEmail-' + verifyKey, { userId: id, redirectAfter: query.redirectAfter }, 1000 * 60 * 60 * 24); await sendMail('verify-email', { options: { to: user.get('email') }, vars: { - action_url: config.public.site.url + '/user/verify-email?verify=' + verifyKey, + action_url: config.public.site.url + '/users/verify-email?verify=' + verifyKey, operating_system: query.os, browser_name: query.browser, } diff --git a/server/api/v1/user/[id]/verify-email.post.mjs b/server/api/v1/users/[id]/verify-email.post.mjs similarity index 100% rename from server/api/v1/user/[id]/verify-email.post.mjs rename to server/api/v1/users/[id]/verify-email.post.mjs diff --git a/server/api/v1/user/index.get.mjs b/server/api/v1/users/index.get.mjs similarity index 100% rename from server/api/v1/user/index.get.mjs rename to server/api/v1/users/index.get.mjs diff --git a/server/api/v1/user/index.post.mjs b/server/api/v1/users/index.post.mjs similarity index 100% rename from server/api/v1/user/index.post.mjs rename to server/api/v1/users/index.post.mjs diff --git a/server/api/v1/user/messagebus.get.mjs b/server/api/v1/users/messagebus.get.mjs similarity index 100% rename from server/api/v1/user/messagebus.get.mjs rename to server/api/v1/users/messagebus.get.mjs diff --git a/server/api/v1/user/options.get.mjs b/server/api/v1/users/options.get.mjs similarity index 100% rename from server/api/v1/user/options.get.mjs rename to server/api/v1/users/options.get.mjs diff --git a/server/api/v1/user/send-password-reset.post.mjs b/server/api/v1/users/send-password-reset.post.mjs similarity index 91% rename from server/api/v1/user/send-password-reset.post.mjs rename to server/api/v1/users/send-password-reset.post.mjs index d091cdcc..d95db52b 100644 --- a/server/api/v1/user/send-password-reset.post.mjs +++ b/server/api/v1/users/send-password-reset.post.mjs @@ -20,7 +20,7 @@ export default defineEventHandler(async (event) => { const query = getQuery(event); await sendMail('password-reset', { options: { to: user.get('email') }, vars: { - action_url: config.public.site.url + '/user/' + user.get('id') + '/reset-password?code=' + resetKey, + action_url: config.public.site.url + '/users/' + user.get('id') + '/reset-password?code=' + resetKey, operating_system: query.os, browser_name: query.browser, } diff --git a/server/api/v1/user/user.rest b/server/api/v1/users/user.rest similarity index 60% rename from server/api/v1/user/user.rest rename to server/api/v1/users/user.rest index 8e1651fd..c24dad2c 100644 --- a/server/api/v1/user/user.rest +++ b/server/api/v1/users/user.rest @@ -1,5 +1,5 @@ ### create -POST http://localhost:3000/api/v1/user?includeMeta=true +POST http://localhost:3000/api/v1/users?includeMeta=true Content-Type: application/json { @@ -11,7 +11,7 @@ Content-Type: application/json ### login # @name login -POST http://localhost:3000/api/v1/session +POST http://localhost:3000/api/v1/sessions Content-Type: application/json { @@ -21,20 +21,20 @@ Content-Type: application/json } ### list -GET http://localhost:3000/api/v1/user +GET http://localhost:3000/api/v1/users Content-Type: application/json ### get -GET http://localhost:3000/api/v1/user/{{login.response.body.$.props.userId}}?includeLinks=true&includeMeta=true +GET http://localhost:3000/api/v1/users/{{login.response.body.$.props.userId}}?includeLinks=true&includeMeta=true Content-Type: application/json ### get options -GET http://localhost:3000/api/v1/user/options +GET http://localhost:3000/api/v1/users/options Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} ### put -PUT http://localhost:3000/api/v1/user/{{login.response.body.$.props.userId}} +PUT http://localhost:3000/api/v1/users/{{login.response.body.$.props.userId}} Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} @@ -43,12 +43,12 @@ Cookie: vingSessionId={{login.response.body.$.props.id}} } ### get with auth -GET http://localhost:3000/api/v1/user/{{login.response.body.$.props.userId}}?includeOptions=true +GET http://localhost:3000/api/v1/users/{{login.response.body.$.props.userId}}?includeOptions=true Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} ### delete -DELETE http://localhost:3000/api/v1/user/{{login.response.body.$.props.userId}} +DELETE http://localhost:3000/api/v1/users/{{login.response.body.$.props.userId}} Content-Type: application/json Cookie: vingSessionId={{login.response.body.$.props.id}} diff --git a/server/api/v1/user/whoami.get.mjs b/server/api/v1/users/whoami.get.mjs similarity index 100% rename from server/api/v1/user/whoami.get.mjs rename to server/api/v1/users/whoami.get.mjs diff --git a/ving/docs/change-log.md b/ving/docs/change-log.md index f9160e63..84bf1110 100644 --- a/ving/docs/change-log.md +++ b/ving/docs/change-log.md @@ -10,6 +10,8 @@ outline: deep * NOTE: You'll need to add a describeLinks() override to classes generated before now. * NOTE: S3File's describe() now exposes the links for the file and thumbnail in links.file.href and links.thumbnail.href respectively instead of meta.fileUrl and meta.thumbnailUrl. This is a breaking change. * NOTE: User's describe() now exposes the link for the avatar in links.avatarImage.href instead of meta.avatarUrl. This is a breaking change. + * Implemented: apis should use plurals #183 + * NOTE: rest endpoints and pages use plurals like /users instead of /user now. This is a breaking change. Update your APIs and pages to use plurals. ### 2025-01-12 * Removed pulumi from the project. diff --git a/ving/docs/rest/APIKey.md b/ving/docs/rest/APIKey.md index f4c90a85..457f1886 100644 --- a/ving/docs/rest/APIKey.md +++ b/ving/docs/rest/APIKey.md @@ -16,37 +16,37 @@ Developers use an API Key to create a [Session](Session) via the Rest API and pe | Name | Record | Type | Endpoint | | --- | --- | --- | --- | -| user | [User](User) | Parent | /api/v1/apikey/:id/user | +| user | [User](User) | Parent | /api/v1/apikeys/:id/user | ## Endpoints ### List ``` -GET /api/v1/apikey +GET /api/v1/apikeys ``` ### Create ``` -POST /api/v1/apikey +POST /api/v1/apikeys ``` ### Read ``` -GET /api/v1/apikey/:id +GET /api/v1/apikeys/:id ``` ### Update ``` -PUT /api/v1/apikey/:id +PUT /api/v1/apikeys/:id ``` ### Delete ``` -DELETE /api/v1/apikey/:id +DELETE /api/v1/apikeys/:id ``` ### Options ``` -GET /api/v1/apikey/options +GET /api/v1/apikeys/options ``` \ No newline at end of file diff --git a/ving/docs/rest/CronJob.md b/ving/docs/rest/CronJob.md index 4fa4ccc8..dedcc27a 100644 --- a/ving/docs/rest/CronJob.md +++ b/ving/docs/rest/CronJob.md @@ -18,30 +18,30 @@ Run background [jobs](../subsystems/jobs) on a set schedule. ### List ``` -GET /api/v1/cronjob +GET /api/v1/cronjobs ``` ### Create ``` -POST /api/v1/cronjob +POST /api/v1/cronjobs ``` ### Read ``` -GET /api/v1/cronjob/:id +GET /api/v1/cronjobs/:id ``` ### Update ``` -PUT /api/v1/cronjob/:id +PUT /api/v1/cronjobs/:id ``` ### Delete ``` -DELETE /api/v1/cronjob/:id +DELETE /api/v1/cronjobs/:id ``` ### Options ``` -GET /api/v1/cronjob/options +GET /api/v1/cronjobs/options ``` \ No newline at end of file diff --git a/ving/docs/rest/S3File.md b/ving/docs/rest/S3File.md index da111ae0..18533bb9 100644 --- a/ving/docs/rest/S3File.md +++ b/ving/docs/rest/S3File.md @@ -13,7 +13,7 @@ The process of uploading a file happens in 3 steps: Here's a bit more detail: ``` -Browser / Your Code --> POST filename and content type to /api/v1/s3file +Browser / Your Code --> POST filename and content type to /api/v1/s3files * creates an S3File and sets its status to pending * generates a Presigned URL for S3 <-- Return S3File description, including meta.presignedUrl @@ -22,7 +22,7 @@ Browser / Your Code --> POST filename and content type to /api/v1/s3file * stores file in S3 <-- Return nothing - --> PUT s3file.props.id to an import API such as /api/v1/user/:id/import-avatar + --> PUT s3file.props.id to an import API such as /api/v1/users/:id/import-avatar * post processes the file uploaded to S3 * verifies that the file conforms to the import rules <-- Return updated record such as User @@ -43,40 +43,40 @@ Browser / Your Code --> POST filename and content type to /api/v1/s3file | Name | Record | Type | Endpoint | | --- | --- | --- | --- | -| user | [User](User) | Parent | /api/v1/s3file/:id/user | -| avatarUsers | [User](User) | Child | /api/v1/s3file/:id/avatarusers | +| user | [User](User) | Parent | /api/v1/s3files/:id/user | +| avatarUsers | [User](User) | Child | /api/v1/s3files/:id/avatarusers | ## Endpoints ### List ``` -GET /api/v1/s3file +GET /api/v1/s3files ``` ### Create ``` -POST /api/v1/s3file +POST /api/v1/s3files ``` You won't actually post the file here. You post the `filename`, `contentType`, and `sizeInBytes` here and it will return a `presignedUrl` in the `meta` section. ### Read ``` -GET /api/v1/s3file/:id +GET /api/v1/s3files/:id ``` ### Update ``` -PUT /api/v1/s3file/:id +PUT /api/v1/s3files/:id ``` ### Delete ``` -DELETE /api/v1/s3file/:id +DELETE /api/v1/s3files/:id ``` ### Options ``` -GET /api/v1/s3file/options +GET /api/v1/s3files/options ``` \ No newline at end of file diff --git a/ving/docs/rest/Session.md b/ving/docs/rest/Session.md index dbe8510b..c8c6099c 100644 --- a/ving/docs/rest/Session.md +++ b/ving/docs/rest/Session.md @@ -8,13 +8,13 @@ In order to access privileged data you'll on any ving endpoint you'll need to pa | Name | Record | Type | Endpoint | | --- | --- | --- | --- | -| user | [User](User) | Parent | /api/v1/session/:id/user | +| user | [User](User) | Parent | /api/v1/sessions/:id/user | ## Endpoints ### Login / Create ``` -POST /api/v1/session +POST /api/v1/sessions { "apiKey" : "1b8e4f16-08ca-4829-befe-865cec37679b", @@ -24,19 +24,19 @@ POST /api/v1/session ### Read ``` -GET /api/v1/session/:id +GET /api/v1/sessions/:id Cookie: vingSessionId=xxx ``` ### Logout / Delete ``` -DELETE /api/v1/session/:id +DELETE /api/v1/sessions/:id Cookie: vingSessionId=xxx ``` Or ``` -DELETE /api/v1/session +DELETE /api/v1/sessions Cookie: vingSessionId=xxx ``` diff --git a/ving/docs/rest/User.md b/ving/docs/rest/User.md index 9e7562a7..e362fbfa 100644 --- a/ving/docs/rest/User.md +++ b/ving/docs/rest/User.md @@ -20,20 +20,20 @@ Users can own records in ving. Users have privileges to access various types of | Name | Record | Type | Endpoint | | --- | --- | --- | --- | -| apikeys | [APIKey](APIKey) | Child | /api/v1/user/:id/apikeys | -| avatar | [S3File](S3File) | Child | /api/v1/user/:id/avatar | +| apikeys | [APIKey](APIKey) | Child | /api/v1/users/:id/apikeys | +| avatar | [S3File](S3File) | Child | /api/v1/users/:id/avatar | ## Endpoints ### List ``` -GET /api/v1/user +GET /api/v1/users ``` ### Create ``` -POST /api/v1/user +POST /api/v1/users { "username" : "adufresne", @@ -45,12 +45,12 @@ POST /api/v1/user ### Read ``` -GET /api/v1/user/:id +GET /api/v1/users/:id ``` ### Update ``` -PUT /api/v1/user/:id +PUT /api/v1/users/:id { "useAsDisplayName" : "realName" @@ -59,18 +59,18 @@ PUT /api/v1/user/:id ### Delete ``` -DELETE /api/v1/user/:id +DELETE /api/v1/users/:id ``` ### Options ``` -GET /api/v1/user/options +GET /api/v1/users/options ``` ### Who Am I? Returns a user record for the currently logged in user based upon the session passed. ``` -GET /api/v1/user/whoami +GET /api/v1/users/whoami Cookie: vingSessionId=xxx ``` @@ -78,7 +78,7 @@ Cookie: vingSessionId=xxx Attach an uploaded [S3File](S3File) to this user as an avatar. ``` -PUT /api/v1/user/:id/import-avatar +PUT /api/v1/users/:id/import-avatar Cookie: vingSessionId=xxx { diff --git a/ving/docs/subsystems/rest.md b/ving/docs/subsystems/rest.md index c6eb836d..391f406f 100644 --- a/ving/docs/subsystems/rest.md +++ b/ving/docs/subsystems/rest.md @@ -15,7 +15,7 @@ Your Rest endpoints will be generated from your [Ving Schema](ving-schema) by us > Note that you will need a [Ving Schema](ving-schema) and [Ving Record](ving-record) for `Foo` before the rest interface can function. -These will be placed in the `server/api/v1/foo` folder and can be modified by you after the fact. +These will be placed in the `server/api/v1/foos` folder and can be modified by you after the fact. ## Conventions @@ -49,7 +49,7 @@ To make a request to a Wing web service you need nothing more than a command lin ### Create a record ``` -POST http://ving.example.com/api/v1/article +POST http://ving.example.com/api/v1/articles Content-Type: application/json { @@ -73,7 +73,7 @@ Response: ### Read a record ``` -GET http://wing.example.com/api/v1/article/xxx +GET http://wing.example.com/api/v1/articles/xxx ``` Response: @@ -91,7 +91,7 @@ Response: ### Update a record ``` -PUT http://ving.example.com/api/v1/article/xxx +PUT http://ving.example.com/api/v1/articles/xxx Content-Type: application/json { @@ -113,7 +113,7 @@ Response: ### Delete a record ``` -DELETE http://ving.example.com/api/v1/article/xxx?includeMeta=true +DELETE http://ving.example.com/api/v1/articles/xxx?includeMeta=true ``` Response @@ -134,7 +134,7 @@ Response ### Get a list of records ``` -GET http://ving.example.com/api/v1/article +GET http://ving.example.com/api/v1/articles ``` Response: @@ -172,7 +172,7 @@ request information about your user account without specifying a `vingSessionId` then all you'd get back is an ID and some other basic information, like this: ``` -GET http://wing.example.com/api/v1/user/xxx?includeMeta=true +GET http://wing.example.com/api/v1/users/xxx?includeMeta=true ``` Response: @@ -195,7 +195,7 @@ But if you request your account information with your `vingSessionId`, then you' get a result set with everything we know about you: ``` -GET http://wing.example.com/api/v1/user/xxx?includeMeta=true +GET http://wing.example.com/api/v1/users/xxx?includeMeta=true Cookie: vingSessionId="yyy" ``` @@ -379,7 +379,7 @@ In addition to exceptions there can be less severe issues that come up. These ar All objects can have relationships to each other. When you fetch an object, you can pass `includeLinks=true` as a parameter if you want to get the relationship data as well. ``` -GET /api/v1/article/xxx?includeLinks=true +GET /api/v1/articles/xxx?includeLinks=true ``` Response: @@ -391,17 +391,31 @@ Response: }, "links" : { "base" : { - "href": "/api/v1/user", - "methods": ["GET","POST"] + "href": "/api/v1/users", + "methods": ["GET","POST"], + "usage" : "rest" }, "self" : { - "href": "/api/v1/user/xxx", - "methods": ["GET","PUT","DELETE"] + "href": "/api/v1/users/xxx", + "methods": ["GET","PUT","DELETE"], + "usage" : "rest" }, "articles" : { - "href": "/api/v1/user/xxx/articles", - "methods": ["GET"] + "href": "/api/v1/users/xxx/articles", + "methods": ["GET"], + "usage" : "rest" }, + "list" : { + "href": "/users/admin", + "methods": ["GET"], + "usage" : "page" + }, + "profile" : { + "href": "/users/xxx/profile", + "methods": ["GET"], + "usage" : "page" + }, + ... } } } @@ -414,7 +428,7 @@ You can then in-turn call the URI provided by each relationship to fetch the ite Likewise you can request related objects (those with relationship type of parent) be included directly in the result by adding the name of the related record relationship like `includeRelated=user as a parameter: ``` -GET /article/xxx?includeRelated=user +GET /articles/xxx?includeRelated=user ``` Response: @@ -450,14 +464,14 @@ Filters allow you to modify the result set when querying a list of records. Some relationships will allow you to use a `search` parameter on the URL that will allow you to search the result set. The documentation will tell you when this is the case and which fields will be searched to provide you with a result set. ``` -GET /api/v1/article/xxx/related-articles?search=prison +GET /api/v1/articles/xxx/related-articles?search=prison ``` #### Qualifiers In search engines these are sometimes called facets. They are criteria that allow you to filter the result set by specific values of a specfic field. The documentation will tell you when a relationship has a qualifier. To use it you'd add a parameter of the name of the qualifier to the URL along with the value you want to search for. ``` -GET /api/v1/article/xxx/related-articles?userId=xxx +GET /api/v1/articles/xxx/related-articles?userId=xxx ``` That will search for all related articles with a `userId` of `xxx`. @@ -465,7 +479,7 @@ That will search for all related articles with a `userId` of `xxx`. You can also modify the qualifier by prepending operators such as `>`, `>=`, `<=`, and `<>` (or `!=`) onto the value. For example: ``` - GET /api/v1/article/xxx/related-articles?wordCount=>=100 + GET /api/v1/articles/xxx/related-articles?wordCount=>=100 ``` Get all related articles with a word count greater than or equal to `100`. @@ -473,7 +487,7 @@ Get all related articles with a word count greater than or equal to `100`. You can also request that a qualifier be limited to a `null` value. ``` -GET /api/v1/article/xxx/related-articles?userId=null +GET /api/v1/articles/xxx/related-articles?userId=null ``` If you did this with an empty `''` or `undefined` value rather than specifically `null` then this qualifier will be skipped. @@ -482,7 +496,7 @@ If you did this with an empty `''` or `undefined` value rather than specifically You can also use ranged filters to limit data that must fall between 2 values by prepending `_start_` or `_end_` to the name of the prop you wish to filter on range. ``` -GET /api/v1/article/xxx/related-articles \ +GET /api/v1/articles/xxx/related-articles \ ?_start_createdAt=2012-04-23T18:25:43.511Z \ &_end_createdAt=2023-04-25T08:13:10.001Z ``` @@ -492,7 +506,7 @@ GET /api/v1/article/xxx/related-articles \ Some records will allow for extra includes, and will show this in the documenation. Extra includes are extra bits of data you can pull back when you request the record, that are unique to that record. ``` -GET /api/v1/article/xxx/related-articles?includeExtra=foo +GET /api/v1/articles/xxx/related-articles?includeExtra=foo ``` The result will then have `foo` in an `extras` block like: @@ -516,7 +530,7 @@ Sometimes a record will have fields that require you to choose an option from an This way would be most often used when you need the list of options in order to create a record. ``` -GET /api/v1/article/options +GET /api/v1/articles/options ``` Response: @@ -533,7 +547,7 @@ Response: This way would be most often used when you need the list of options to update an object, because you can get the properties of the object and the options in one call. ``` -GET http://ving.example.com/api/v1/article/xxx?includeOptions=true +GET http://ving.example.com/api/v1/articles/xxx?includeOptions=true ``` ```json @@ -564,14 +578,14 @@ There is a composable built into ving called [useVingKind](ui#usevingkind()) tha ``` curl -X POST -d '{"title":"Ethics in Prisons","author":"Andy Dufresne"}' \ -H Content-Type: application/json \ - -H Cookie: vingSessionId=yyy http://ving.example.com/api/v1/article + -H Cookie: vingSessionId=yyy http://ving.example.com/api/v1/articles ``` ### VS Code Rest Client [VS Code Rest Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) ``` -POST http://ving.example.com/api/v1/article +POST http://ving.example.com/api/v1/articles Content-Type: application/json Cookie: vingSessionId=yyy diff --git a/ving/docs/subsystems/ui.md b/ving/docs/subsystems/ui.md index 7493f23c..bb61d98f 100644 --- a/ving/docs/subsystems/ui.md +++ b/ving/docs/subsystems/ui.md @@ -182,8 +182,8 @@ Sometimes you need to list a bunch of management functions in a tight space, lik ```html ``` @@ -406,7 +406,7 @@ You would then use the Notify Component in your layout. A wrapper around the Nuxt composable `$fetch()` that allows for streamlined fetches, but integrate's with ving's subsystems. ```js -const response = useFetch(`/api/${useRestVersion()}/user`); +const response = useFetch(`/api/${useRestVersion()}/users`); ``` ### useRestVersion() @@ -458,8 +458,8 @@ A client for interacting with [server-side ving kinds](ving-record#kind-api) thr ```js const users = useVingKind({ - listApi : `/api/${useRestVersion()}/user`, - createApi : `/api/${useRestVersion()}/user`, + listApi : `/api/${useRestVersion()}/users`, + createApi : `/api/${useRestVersion()}/users`, query: { includeMeta: true, sortBy: 'username', sortOrder: 'asc' }, newDefaults: { username: '', realName: '', email: '' }, }); @@ -490,14 +490,14 @@ A client for interacting with [server-side ving records](ving-record#record-api) const id = route.params.id.toString(); const user = useVingRecord<'User'>({ id, - fetchApi: `/api/${useRestVersion()}/user/${id}`, - createApi: `/api/${useRestVersion()}/user`, + fetchApi: `/api/${useRestVersion()}/users/${id}`, + createApi: `/api/${useRestVersion()}/users`, query: { includeMeta: true, includeOptions: true }, onUpdate() { notify.success('Updated user.'); }, async onDelete() { - await navigateTo('/user/admin'); + await navigateTo(user.links.list.href); }, }); await user.fetch() diff --git a/ving/generator/nuxtapis.mjs b/ving/generator/nuxtapis.mjs index 96e3a939..1b9d6e49 100644 --- a/ving/generator/nuxtapis.mjs +++ b/ving/generator/nuxtapis.mjs @@ -141,7 +141,7 @@ export default defineEventHandler(async (event) => { export const generateRest = async (params) => { const context = { ...getContext({}), ...params }; - const folderName = `server/api/${(await ving.getConfig()).rest.version}/${context.name.toLowerCase()}`; + const folderName = `server/api/${(await ving.getConfig()).rest.version}/${context.name.toLowerCase()}s`; let gen = Promise.resolve(context); let filePath = `${folderName}/[id]/index.delete.mjs`; if (!(params.skipExisting && fs.existsSync(filePath))) diff --git a/ving/generator/nuxtpages.mjs b/ving/generator/nuxtpages.mjs index a7326be2..fa9be1b9 100644 --- a/ving/generator/nuxtpages.mjs +++ b/ving/generator/nuxtpages.mjs @@ -163,10 +163,13 @@ const indexTemplate = ({ name, schema }) =>