feat: migrate from paths constant to new route management system

This commit is contained in:
Sonny
2024-05-16 18:53:53 +02:00
committed by Sonny
parent 905f0ba1c7
commit 57ed2c5e94
20 changed files with 82 additions and 75 deletions

View File

@@ -22,6 +22,7 @@ FROM base as build
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules /app/node_modules COPY --from=deps /app/node_modules /app/node_modules
ADD . . ADD . .
RUN node ace izzy:routes
RUN node ace build RUN node ace build
# Production stage # Production stage

View File

@@ -1,31 +1,4 @@
const PATHS = { const PATHS = {
AUTH: {
LOGIN: '/login',
LOGOUT: '/auth/logout',
GOOGLE: '/auth/google',
},
HOME: '/',
DASHBOARD: '/dashboard',
SHARED: '/shared',
PRIVACY: '/privacy',
TERMS: '/terms',
ADMIN: '/admin',
COLLECTION: {
CREATE: '/collections/create',
EDIT: '/collections/edit',
REMOVE: '/collections/remove',
},
LINK: {
CREATE: '/link/create',
EDIT: '/link/edit',
REMOVE: '/link/remove',
},
API: {
COLLECTION: '/collections',
LINK: '/api/link',
},
NOT_FOUND: '/404',
SERVER_ERROR: '/505',
AUTHOR: 'https://www.sonny.dev/', AUTHOR: 'https://www.sonny.dev/',
REPO_GITHUB: 'https://github.com/Sonny93/my-links', REPO_GITHUB: 'https://github.com/Sonny93/my-links',
EXTENSION: EXTENSION:

View File

@@ -1,10 +1,10 @@
import PATHS from '#constants/paths';
import User from '#models/user'; import User from '#models/user';
import type { HttpContext } from '@adonisjs/core/http'; import type { HttpContext } from '@adonisjs/core/http';
import logger from '@adonisjs/core/services/logger'; import logger from '@adonisjs/core/services/logger';
import { RouteName } from '@izzyjs/route/types';
export default class UsersController { export default class UsersController {
private redirectTo = PATHS.HOME; private redirectTo: RouteName = 'auth.login';
login({ inertia }: HttpContext) { login({ inertia }: HttpContext) {
return inertia.render('login'); return inertia.render('login');
@@ -17,17 +17,17 @@ export default class UsersController {
if (google.accessDenied()) { if (google.accessDenied()) {
// TODO: translate error messages + show them in UI // TODO: translate error messages + show them in UI
session.flash('flash', 'Access was denied'); session.flash('flash', 'Access was denied');
return response.redirect(this.redirectTo); return response.redirectToNamedRoute(this.redirectTo);
} }
if (google.stateMisMatch()) { if (google.stateMisMatch()) {
session.flash('flash', 'Request expired. Retry again'); session.flash('flash', 'Request expired. Retry again');
return response.redirect(this.redirectTo); return response.redirectToNamedRoute(this.redirectTo);
} }
if (google.hasError()) { if (google.hasError()) {
session.flash('flash', google.getError() || 'Something went wrong'); session.flash('flash', google.getError() || 'Something went wrong');
return response.redirect(this.redirectTo); return response.redirectToNamedRoute(this.redirectTo);
} }
const { const {
@@ -56,13 +56,13 @@ export default class UsersController {
session.flash('flash', 'Successfully authenticated'); session.flash('flash', 'Successfully authenticated');
logger.info(`[${user.email}] auth success`); logger.info(`[${user.email}] auth success`);
response.redirect(PATHS.DASHBOARD); response.redirectToNamedRoute('dashboard');
} }
async logout({ auth, response, session }: HttpContext) { async logout({ auth, response, session }: HttpContext) {
await auth.use('web').logout(); await auth.use('web').logout();
session.flash('flash', 'Successfully disconnected'); session.flash('flash', 'Successfully disconnected');
logger.info(`[${auth.user?.email}] disconnected successfully`); logger.info(`[${auth.user?.email}] disconnected successfully`);
response.redirect(this.redirectTo); response.redirectToNamedRoute(this.redirectTo);
} }
} }

View File

@@ -1,4 +1,3 @@
import PATHS from '#constants/paths';
import { ExceptionHandler, HttpContext } from '@adonisjs/core/http'; import { ExceptionHandler, HttpContext } from '@adonisjs/core/http';
import app from '@adonisjs/core/services/app'; import app from '@adonisjs/core/services/app';
import type { import type {
@@ -38,7 +37,7 @@ export default class HttpExceptionHandler extends ExceptionHandler {
*/ */
async handle(error: unknown, ctx: HttpContext) { async handle(error: unknown, ctx: HttpContext) {
if (error instanceof errors.E_ROW_NOT_FOUND) { if (error instanceof errors.E_ROW_NOT_FOUND) {
return ctx.response.redirect(PATHS.DASHBOARD); return ctx.response.redirectToNamedRoute('dashboard');
} }
return super.handle(error, ctx); return super.handle(error, ctx);
} }

View File

@@ -1,7 +1,7 @@
import PATHS from '#constants/paths';
import type { Authenticators } from '@adonisjs/auth/types'; import type { Authenticators } from '@adonisjs/auth/types';
import type { HttpContext } from '@adonisjs/core/http'; import type { HttpContext } from '@adonisjs/core/http';
import type { NextFn } from '@adonisjs/core/types/http'; import type { NextFn } from '@adonisjs/core/types/http';
import { route } from '@izzyjs/route/client';
/** /**
* Auth middleware is used authenticate HTTP requests and deny * Auth middleware is used authenticate HTTP requests and deny
@@ -11,7 +11,7 @@ export default class AuthMiddleware {
/** /**
* The URL to redirect to, when authentication fails * The URL to redirect to, when authentication fails
*/ */
redirectTo = PATHS.AUTH.LOGIN; redirectTo = route('auth.login').url;
async handle( async handle(
ctx: HttpContext, ctx: HttpContext,

View File

@@ -1,6 +1,6 @@
import KEYS from '#constants/keys'; import KEYS from '#constants/keys';
import PATHS from '#constants/paths';
import { router } from '@inertiajs/react'; import { router } from '@inertiajs/react';
import { route } from '@izzyjs/route/client';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import useGlobalHotkeys from '~/hooks/use_global_hotkeys'; import useGlobalHotkeys from '~/hooks/use_global_hotkeys';
@@ -13,7 +13,7 @@ export default function BackToDashboard({ children }: { children: ReactNode }) {
useHotkeys( useHotkeys(
KEYS.ESCAPE_KEY, KEYS.ESCAPE_KEY,
() => { () => {
router.visit(appendCollectionId(PATHS.DASHBOARD, collectionId)); router.visit(appendCollectionId(route('dashboard').url, collectionId));
}, },
{ enabled: globalHotkeysEnabled, enableOnFormTags: ['INPUT'] } { enabled: globalHotkeysEnabled, enableOnFormTags: ['INPUT'] }
); );

View File

@@ -21,7 +21,7 @@ const LinksWrapper = styled.div({
const CollectionHeaderWrapper = styled.h2(({ theme }) => ({ const CollectionHeaderWrapper = styled.h2(({ theme }) => ({
fontWeight: 400, fontWeight: 400,
color: theme.colors.font, color: theme.colors.font,
paddingInline: '0.8em 1.1em', paddingRight: '1.1em',
display: 'flex', display: 'flex',
gap: '0.4em', gap: '0.4em',
alignItems: 'center', alignItems: 'center',

View File

@@ -1,5 +1,5 @@
import PATHS from '#constants/paths';
import type Collection from '#models/collection'; import type Collection from '#models/collection';
import { route } from '@izzyjs/route/client';
import { BsThreeDotsVertical } from 'react-icons/bs'; import { BsThreeDotsVertical } from 'react-icons/bs';
import { GoPencil } from 'react-icons/go'; import { GoPencil } from 'react-icons/go';
import { IoIosAddCircleOutline } from 'react-icons/io'; import { IoIosAddCircleOutline } from 'react-icons/io';
@@ -14,16 +14,19 @@ const CollectionControls = ({
collectionId: Collection['id']; collectionId: Collection['id'];
}) => ( }) => (
<Dropdown label={<BsThreeDotsVertical />} svgSize={18}> <Dropdown label={<BsThreeDotsVertical />} svgSize={18}>
<DropdownItemLink href={PATHS.LINK.CREATE}> <DropdownItemLink href={route('link.create-form').url}>
<IoIosAddCircleOutline /> Add <IoIosAddCircleOutline /> Add
</DropdownItemLink> </DropdownItemLink>
<DropdownItemLink <DropdownItemLink
href={appendCollectionId(PATHS.COLLECTION.EDIT, collectionId)} href={appendCollectionId(route('collection.edit-form').url, collectionId)}
> >
<GoPencil /> Edit <GoPencil /> Edit
</DropdownItemLink> </DropdownItemLink>
<DropdownItemLink <DropdownItemLink
href={appendCollectionId(PATHS.COLLECTION.REMOVE, collectionId)} href={appendCollectionId(
route('collection.delete-form').url,
collectionId
)}
danger danger
> >
<IoTrashOutline /> Delete <IoTrashOutline /> Delete

View File

@@ -1,7 +1,7 @@
import PATHS from '#constants/paths';
import type Link from '#models/link'; import type Link from '#models/link';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { route } from '@izzyjs/route/client';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { AiFillStar, AiOutlineStar } from 'react-icons/ai'; import { AiFillStar, AiOutlineStar } from 'react-icons/ai';
import { BsThreeDotsVertical } from 'react-icons/bs'; import { BsThreeDotsVertical } from 'react-icons/bs';
@@ -48,9 +48,12 @@ export default function LinkControls({ link }: { link: Link }) {
); );
const onFavorite = () => { const onFavorite = () => {
const editRoute = route('link.edit', {
params: { id: link.id },
});
makeRequest({ makeRequest({
url: `${PATHS.API.LINK}/${link.id}`, url: editRoute.url,
method: 'PUT', method: editRoute.method,
body: { body: {
name: link.name, name: link.name,
url: link.url, url: link.url,
@@ -80,12 +83,18 @@ export default function LinkControls({ link }: { link: Link }) {
)} )}
</StartItem> </StartItem>
<DropdownItemLink <DropdownItemLink
href={appendCollectionId(PATHS.LINK.EDIT, link.collectionId)} href={appendCollectionId(
route('link.edit-form').url,
link.collectionId
)}
> >
<GoPencil /> Edit <GoPencil /> Edit
</DropdownItemLink> </DropdownItemLink>
<DropdownItemLink <DropdownItemLink
href={appendCollectionId(PATHS.LINK.REMOVE, link.collectionId)} href={appendCollectionId(
route('link.delete-form').url,
link.collectionId
)}
danger danger
> >
<IoTrashOutline /> Delete <IoTrashOutline /> Delete

View File

@@ -1,6 +1,6 @@
import PATHS from '#constants/paths';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Link } from '@inertiajs/react'; import { Link } from '@inertiajs/react';
import { route } from '@izzyjs/route/client';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useActiveCollection from '~/hooks/use_active_collection'; import useActiveCollection from '~/hooks/use_active_collection';
import { appendCollectionId } from '~/lib/navigation'; import { appendCollectionId } from '~/lib/navigation';
@@ -30,7 +30,9 @@ export function NoCollection() {
return ( return (
<NoCollectionStyle> <NoCollectionStyle>
<Text>{t('select-collection')}</Text> <Text>{t('select-collection')}</Text>
<Link href={PATHS.COLLECTION.CREATE}>{t('or-create-one')}</Link> <Link href={route('collection.create-form').url}>
{t('or-create-one')}
</Link>
</NoCollectionStyle> </NoCollectionStyle>
); );
} }
@@ -51,7 +53,12 @@ export function NoLink() {
), ),
}} }}
/> />
<Link href={appendCollectionId(PATHS.LINK.CREATE, activeCollection?.id)}> <Link
href={appendCollectionId(
route('link.create-form').url,
activeCollection?.id
)}
>
{t('link.create')} {t('link.create')}
</Link> </Link>
</NoCollectionStyle> </NoCollectionStyle>

View File

@@ -4,6 +4,7 @@ import { Link } from '@inertiajs/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import ExternalLink from '~/components/common/external_link'; import ExternalLink from '~/components/common/external_link';
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
import { route } from '@izzyjs/route/client';
const FooterStyle = styled.footer(({ theme }) => ({ const FooterStyle = styled.footer(({ theme }) => ({
fontSize: '0.9em', fontSize: '0.9em',
@@ -21,9 +22,9 @@ export default function Footer() {
return ( return (
<FooterStyle> <FooterStyle>
<div className="row"> <div className="row">
<Link href={PATHS.PRIVACY}>{t('privacy')}</Link> <Link href={route('privacy').url}>{t('privacy')}</Link>
{' • '} {' • '}
<Link href={PATHS.TERMS}>{t('terms')}</Link> <Link href={route('terms').url}>{t('terms')}</Link>
{' • '} {' • '}
<ExternalLink href={PATHS.EXTENSION}>Extension</ExternalLink> <ExternalLink href={PATHS.EXTENSION}>Extension</ExternalLink>
</div> </div>

View File

@@ -1,11 +1,11 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Link } from '@inertiajs/react'; import { Link } from '@inertiajs/react';
import { route } from '@izzyjs/route/client';
import { FormEvent, ReactNode } from 'react'; import { FormEvent, ReactNode } from 'react';
import Button from '~/components/common/form/_button'; import Button from '~/components/common/form/_button';
import Form from '~/components/common/form/_form'; import Form from '~/components/common/form/_form';
import BaseLayout from './_base_layout';
import { appendCollectionId } from '~/lib/navigation'; import { appendCollectionId } from '~/lib/navigation';
import PATHS from '#constants/paths'; import BaseLayout from './_base_layout';
const FormLayoutStyle = styled.div(({ theme }) => ({ const FormLayoutStyle = styled.div(({ theme }) => ({
height: 'fit-content', height: 'fit-content',
@@ -49,7 +49,7 @@ const FormLayout = ({
</Button> </Button>
</Form> </Form>
{!disableHomeLink && ( {!disableHomeLink && (
<Link href={appendCollectionId(PATHS.DASHBOARD, collectionId)}> <Link href={appendCollectionId(route('dashboard').url, collectionId)}>
Back to home Back to home
</Link> </Link>
)} )}

View File

@@ -1,5 +1,6 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Link } from '@inertiajs/react'; import { Link } from '@inertiajs/react';
import { route } from '@izzyjs/route/client';
import { IoIosLogOut } from 'react-icons/io'; import { IoIosLogOut } from 'react-icons/io';
import Dropdown from '~/components/common/dropdown/dropdown'; import Dropdown from '~/components/common/dropdown/dropdown';
import { DropdownItemLink } from '~/components/common/dropdown/dropdown_item'; import { DropdownItemLink } from '~/components/common/dropdown/dropdown_item';
@@ -50,7 +51,7 @@ export default function Navbar() {
<Nav> <Nav>
<NavList> <NavList>
<li> <li>
<Link href={PATHS.HOME} css={{ fontSize: '24px' }}> <Link href={route('home').url} css={{ fontSize: '24px' }}>
MyLinks MyLinks
</Link> </Link>
</li> </li>
@@ -70,7 +71,7 @@ export default function Navbar() {
{isAuthenticated && !!user ? ( {isAuthenticated && !!user ? (
<> <>
<li> <li>
<Link href={PATHS.DASHBOARD}>Dashboard</Link> <Link href={route('dashboard').url}>Dashboard</Link>
</li> </li>
<li> <li>
<Dropdown <Dropdown
@@ -85,7 +86,7 @@ export default function Navbar() {
</UserCard> </UserCard>
} }
> >
<DropdownItemLink href={PATHS.AUTH.LOGOUT} danger> <DropdownItemLink href={route('auth.logout').url} danger>
<IoIosLogOut /> Logout <IoIosLogOut /> Logout
</DropdownItemLink> </DropdownItemLink>
</Dropdown> </Dropdown>
@@ -93,7 +94,7 @@ export default function Navbar() {
</> </>
) : ( ) : (
<li> <li>
<Link href={PATHS.AUTH.LOGIN}>Login</Link> <Link href={route('auth.login').url}>Login</Link>
</li> </li>
)} )}
</NavList> </NavList>

View File

@@ -1,6 +1,7 @@
import PATHS from '#constants/paths';
import Collection from '#models/collection'; import Collection from '#models/collection';
import Link from '#models/link'; import Link from '#models/link';
import { route } from '@izzyjs/route/client';
import { appendCollectionId } from '~/lib/navigation';
import { SearchItem, SearchResult } from '~/types/search'; import { SearchItem, SearchResult } from '~/types/search';
export function buildSearchItem( export function buildSearchItem(
@@ -13,7 +14,7 @@ export function buildSearchItem(
url: url:
type === 'link' type === 'link'
? (item as Link).url ? (item as Link).url
: `${PATHS.DASHBOARD}?collectionId=${item.id}`, : appendCollectionId(route('dashboard').url, item.id),
type, type,
collection: type === 'link' ? (item as Link).collection : undefined, collection: type === 'link' ? (item as Link).collection : undefined,
}; };

View File

@@ -1,5 +1,4 @@
import KEYS from '#constants/keys'; import KEYS from '#constants/keys';
import PATHS from '#constants/paths';
import type Collection from '#models/collection'; import type Collection from '#models/collection';
import Link from '#models/link'; import Link from '#models/link';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
@@ -116,14 +115,16 @@ function DashboardProviders(
useHotkeys( useHotkeys(
KEYS.OPEN_CREATE_LINK_KEY, KEYS.OPEN_CREATE_LINK_KEY,
() => { () => {
router.visit(`${PATHS.LINK.CREATE}?collectionId=${activeCollection?.id}`); router.visit(
appendCollectionId(route('link.create-form').url, activeCollection?.id)
);
}, },
{ enabled: globalHotkeysEnabled } { enabled: globalHotkeysEnabled }
); );
useHotkeys( useHotkeys(
KEYS.OPEN_CREATE_COLLECTION_KEY, KEYS.OPEN_CREATE_COLLECTION_KEY,
() => { () => {
router.visit(PATHS.COLLECTION.CREATE); router.visit(route('collection.create-form').url);
}, },
{ enabled: globalHotkeysEnabled } { enabled: globalHotkeysEnabled }
); );

View File

@@ -1,10 +1,9 @@
import { route } from '@izzyjs/route/client';
import ContentLayout from '~/components/layouts/content_layout'; import ContentLayout from '~/components/layouts/content_layout';
import PATHS from '../../app/constants/paths';
export default function LoginPage() { const LoginPage = () => (
return ( <ContentLayout>
<ContentLayout> <a href={route('auth.google').url}>Continue with Google</a>
<a href={PATHS.AUTH.GOOGLE}>Continue with Google</a> </ContentLayout>
</ContentLayout> );
); export default LoginPage;
}

View File

@@ -20,6 +20,8 @@ declare module '@adonisjs/core/http' {
Response.macro( Response.macro(
'redirectToNamedRoute', 'redirectToNamedRoute',
function (this: Response, routeName, options) { function (this: Response, routeName, options) {
// TODO: fix this
// @ts-ignore
const current = route(routeName, options); const current = route(routeName, options);
this.redirect().toRoute(current.url, current.params, { this.redirect().toRoute(current.url, current.params, {
qs: current.qs, qs: current.qs,

View File

@@ -6,6 +6,9 @@ const AppsController = () => import('#controllers/apps_controller');
*/ */
router.group(() => { router.group(() => {
router.get('/', [AppsController, 'index']).as('home'); router.get('/', [AppsController, 'index']).as('home');
router.get('/privacy', () => 'privacy').as('privacy');
router.get('/terms', () => 'terms').as('terms');
router router
.post('/user/theme', [AppsController, 'updateUserTheme']) .post('/user/theme', [AppsController, 'updateUserTheme'])
.as('user.theme'); .as('user.theme');

View File

@@ -25,6 +25,8 @@ router
router router
.put('/:id', [CollectionsController, 'update']) .put('/:id', [CollectionsController, 'update'])
.as('collection.edit'); .as('collection.edit');
router.get('/delete', () => 'delete').as('collection.delete-form');
}) })
.prefix('/collections'); .prefix('/collections');
}) })

View File

@@ -11,6 +11,11 @@ router
.get('/create', [LinksController, 'showCreatePage']) .get('/create', [LinksController, 'showCreatePage'])
.as('link.create-form'); .as('link.create-form');
router.post('/', [LinksController, 'store']).as('link.create'); router.post('/', [LinksController, 'store']).as('link.create');
router.get('/edit', () => 'edit form').as('link.edit-form');
router.put('/:id', () => 'edit route api').as('link.edit');
router.get('/delete', () => 'delete').as('link.delete-form');
}) })
.middleware([middleware.auth()]) .middleware([middleware.auth()])
.prefix('/links'); .prefix('/links');