tighten / ziggy

Use your Laravel routes in JavaScript.
MIT License
3.94k stars 250 forks source link

Wildcard based application with absolute path #455

Closed paulocastellano closed 3 years ago

paulocastellano commented 3 years ago

Ziggy version

1.3.5

Laravel version

8.40

Description

Hi,

I have a wildcard application based on subdomain.

Ex: company-1.domain.com this works well.

The problem is when this "company-1" want use a custom domain via proxy, ex: portal.company-1.com and I need use absolute path.

Because ziggy not understand what param is from subdomain and what param is for URL.

The route helper:

route('posts.show', {'subdomain': 'company-1', 'id': post.id, false})

Show: /posts/ID?subdomain=company-1 instead of show /posts/ID (laravel route helper works well with this method)

Ziggy call and context

route('posts.show', {'subdomain': 'company-1', 'id': post.id, false})

Ziggy configuration

{
    "url": "http://feedback.domain.test",
    "port": null,
    "defaults": {},
    "routes": {
        "debugbar.openhandler": {
            "uri": "_debugbar/open",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "debugbar.clockwork": {
            "uri": "_debugbar/clockwork/{id}",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "debugbar.telescope": {
            "uri": "_debugbar/telescope/{id}",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "debugbar.assets.css": {
            "uri": "_debugbar/assets/stylesheets",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "debugbar.assets.js": {
            "uri": "_debugbar/assets/javascript",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "debugbar.cache.delete": {
            "uri": "_debugbar/cache/{key}/{tags?}",
            "methods": [
                "DELETE"
            ]
        },
        "ignition.healthCheck": {
            "uri": "_ignition/health-check",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "ignition.executeSolution": {
            "uri": "_ignition/execute-solution",
            "methods": [
                "POST"
            ]
        },
        "ignition.shareReport": {
            "uri": "_ignition/share-report",
            "methods": [
                "POST"
            ]
        },
        "ignition.scripts": {
            "uri": "_ignition/scripts/{script}",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "ignition.styles": {
            "uri": "_ignition/styles/{style}",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "cashier.payment": {
            "uri": "stripe/payment/{id}",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "cashier.webhook": {
            "uri": "stripe/webhook",
            "methods": [
                "POST"
            ]
        },
        "receipts.download": {
            "uri": "spark/{type}/{id}/receipts/{receiptId}/download",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "spark.portal": {
            "uri": "admin/settings/team/billing/{type?}/{id?}",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "vapor-ui": {
            "uri": "vapor-ui/{view?}",
            "methods": [
                "GET",
                "HEAD"
            ]
        },
        "api.markdown-upload": {
            "uri": "api/markdown/images",
            "methods": [
                "POST"
            ]
        },
        "notifications.index": {
            "uri": "admin/notifications",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "notifications.update": {
            "uri": "admin/notifications/{id}",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "teams.store": {
            "uri": "admin/teams",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "teams.update-current-team": {
            "uri": "admin/teams/update-current-team",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "dashboard": {
            "uri": "admin/dashboard",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "posts.index": {
            "uri": "admin/posts",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "posts.store": {
            "uri": "admin/posts",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "posts.show": {
            "uri": "admin/posts/{id}",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "posts.update": {
            "uri": "admin/posts/{id}",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "posts.destroy": {
            "uri": "admin/posts/{id}",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "posts.comments.store": {
            "uri": "admin/posts/{postId}/post-comments",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "posts.comments.update": {
            "uri": "admin/posts/{postId}/post-comments/{commentId}",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "posts.comments.destroy": {
            "uri": "admin/posts/{postId}/post-comments/{commentId}",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "posts.votes.store": {
            "uri": "admin/posts/{id}/post-votes",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "roadmap.index": {
            "uri": "admin/roadmap",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.index": {
            "uri": "admin/changelog",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.create": {
            "uri": "admin/changelog/new",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.store": {
            "uri": "admin/changelog",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.edit": {
            "uri": "admin/changelog/{id}/edit",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.update": {
            "uri": "admin/changelog/{id}",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.destroy": {
            "uri": "admin/changelog/{id}",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "users.index": {
            "uri": "admin/users",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "teams.edit": {
            "uri": "admin/settings/team",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "teams.update": {
            "uri": "admin/settings/team",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "teams.destroy": {
            "uri": "admin/settings/team",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "teams.update-logo": {
            "uri": "admin/settings/team/logo",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "teams.delete-logo": {
            "uri": "admin/settings/team/logo",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "teammates.index": {
            "uri": "admin/settings/teammates",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "teammates.update-user-role": {
            "uri": "admin/settings/teammates/{id}",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "teammates.destroy": {
            "uri": "admin/settings/teammates/{id}",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "team-invitations.index": {
            "uri": "admin/settings/team-invitations",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "team-invitations.store": {
            "uri": "admin/settings/team-invitations",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "team-invitations.destroy": {
            "uri": "admin/settings/team-invitations/{id}",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "teams.custom-domain.edit": {
            "uri": "admin/settings/team/custom-domain",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "teams.custom-domain.update": {
            "uri": "admin/settings/team/custom-domain",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "roadmap.edit": {
            "uri": "admin/settings/roadmap",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "roadmap.update": {
            "uri": "admin/settings/roadmap",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.labels.index": {
            "uri": "admin/settings/labels",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.labels.store": {
            "uri": "admin/settings/labels",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.labels.update": {
            "uri": "admin/settings/labels/{id}",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "changelogs.labels.destroy": {
            "uri": "admin/settings/labels/{id}",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "boards.index": {
            "uri": "admin/settings/boards",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "boards.store": {
            "uri": "admin/settings/boards",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "boards.show": {
            "uri": "admin/settings/boards/{slug}",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "boards.update": {
            "uri": "admin/settings/boards/{id}",
            "methods": [
                "PUT"
            ],
            "domain": "app.domain.test"
        },
        "boards.destroy": {
            "uri": "admin/settings/boards/{id}",
            "methods": [
                "DELETE"
            ],
            "domain": "app.domain.test"
        },
        "integrations.google-analytics.edit": {
            "uri": "admin/settings/google-analytics",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "integrations.google-analytics.update": {
            "uri": "admin/settings/google-analytics",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "register.show": {
            "uri": "register",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "register": {
            "uri": "register",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "login": {
            "uri": "login",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "password.request": {
            "uri": "forgot-password",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "password.email": {
            "uri": "forgot-password",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "password.reset": {
            "uri": "reset-password/{token}",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "password.update": {
            "uri": "reset-password",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "verification.notice": {
            "uri": "verify-email",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "verification.verify": {
            "uri": "verify-email/{id}/{hash}",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "verification.send": {
            "uri": "email/verification-notification",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "password.confirm": {
            "uri": "confirm-password",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "logout": {
            "uri": "logout",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "team-invitations.show": {
            "uri": "invites/{token}",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "app.domain.test"
        },
        "auth.invites.accept": {
            "uri": "invites/{token}",
            "methods": [
                "POST"
            ],
            "domain": "app.domain.test"
        },
        "portal.roadmap.index": {
            "uri": "roadmap",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.changelogs.index": {
            "uri": "changelog",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.changelogs.show": {
            "uri": "changelog/{slug}",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.auth.login": {
            "uri": "auth/login",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.auth.register": {
            "uri": "auth/register",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.auth.send-reset-link": {
            "uri": "auth/send-reset-link",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.auth.forgot-password": {
            "uri": "auth/forgot-password",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.auth.reset-password": {
            "uri": "auth/reset-password",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.auth.logout": {
            "uri": "auth/logout",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.posts.store": {
            "uri": "posts",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.post-comments.store": {
            "uri": "post-comments",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.post-votes.store": {
            "uri": "post-votes",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.current-team": {
            "uri": "current-team",
            "methods": [
                "PUT"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.profile.edit": {
            "uri": "profile",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.profile.update": {
            "uri": "profile",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.profile.update-password": {
            "uri": "profile/update-password",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.user-preference": {
            "uri": "profile/user-preference",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.profile.update-avatar": {
            "uri": "profile/avatar",
            "methods": [
                "POST"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.profile.delete-avatar": {
            "uri": "profile/avatar",
            "methods": [
                "DELETE"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.current-user.destroy": {
            "uri": "current-user",
            "methods": [
                "DELETE"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.notifications.index": {
            "uri": "notifications",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.notifications.update": {
            "uri": "notifications/{id}",
            "methods": [
                "PUT"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.posts.index": {
            "uri": "{slug?}",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        },
        "portal.posts.show": {
            "uri": "{slug}/p/{postSlug}",
            "methods": [
                "GET",
                "HEAD"
            ],
            "domain": "{subdomain}.domain.test"
        }
    }
}

Route definition

Route::group([
    'domain' => '{subdomain}.' . parse_url(ENV('APP_URL'), PHP_URL_HOST)
], function () {

    // roadmap
    Route::get('/roadmap', [RoadmapController::class, 'index'])->name('portal.roadmap.index');

});
bakerkretzmar commented 3 years ago

It looks like there are two small errors in the examples you shared, false should be the third parameter to route(), outside the brackets, and I think the route you want is portal.posts.show, not posts.show. Does this work?

- route('posts.show', {'subdomain': 'company-1', 'id': post.id, false})
+ route('portal.posts.show', {'subdomain': 'company-1', 'id': post.id }, false)

Can you also share a working example of using Laravel's route() function in PHP to accomplish this? Thanks.

paulocastellano commented 3 years ago

It looks like there are two small errors in the examples you shared, false should be the third parameter to route(), outside the brackets, and I think the route you want is portal.posts.show, not posts.show. Does this work?

- route('posts.show', {'subdomain': 'company-1', 'id': post.id, false})
+ route('portal.posts.show', {'subdomain': 'company-1', 'id': post.id }, false)

Can you also share a working example of using Laravel's route() function in PHP to accomplish this? Thanks.

Yes, sorry i wrong... the param false is out the brackets...

Helper:

<a :href="route('portal.posts.show', {'subdomain': team.subdomain, 'slug': post.board.slug, 'postSlug': post.slug}, false)" class="flex w-full">

Full URL:

http://feedback.domain.test/bugs-fixes/p/bug-on-integration?subdomain=feedback

Example of laravel route with subdomain and absolute path:

Helper:

<a href="{{ route('portal.changelogs.show', ['subdomain' => $project->subdomain, 'slug' => $post->slug], false) }}" >

Full URL:

https://feedback.domain.test/changelogs/april-improvements

Some help links from laravel:

@bakerkretzmar If i can help, please tell me.

bakerkretzmar commented 3 years ago

@paulocastellano to get the complete absolute URL, including the origin, route('portal.posts.show', { subdomain: 'portal-1', slug: 'posts', postSlug: 'my-first-post' }) works for me. To get the relative URL without the domain, you can just omit the subdomain parameter: route('portal.posts.show', { slug: 'posts', postSlug: 'my-first-post' }, false) works for me and returns /posts/p/my-first-post. Can you try that?

paulocastellano commented 3 years ago

@paulocastellano to get the complete absolute URL, including the origin, route('portal.posts.show', { subdomain: 'portal-1', slug: 'posts', postSlug: 'my-first-post' }) works for me. To get the relative URL without the domain, you can just omit the subdomain parameter: route('portal.posts.show', { slug: 'posts', postSlug: 'my-first-post' }, false) works for me and returns /posts/p/my-first-post. Can you try that?

Yeah this will work, but my problem is, my application run with subdomain and custom domain ex:

subdomain.my-application.com subdomain.client-domain.com

subdomain.client-domain.com are proxy to subdomain.my-application.com.

And some customers can have or do not have custom domains.

Because of this, I need every pass subdomain param, because the application is based on a subdomain.

because with subdomain param, I will get all information of the customer.

bakerkretzmar commented 3 years ago

Ahh, gotcha. That might not be possible in Ziggy directly, I think you'll have to work around it.

The issue is that your route definition, Route::group(['domain' => '{subdomain}.' . parse_url(env('APP_URL'), PHP_URL_HOST)], function () { ... }), uses the APP_URL environment variable, which is always https://my-application.com. Ziggy will usually use url('/') instead, which will always be the URL of the current request, but because your route actually contains the domain in the route definition itself, Ziggy will always have it in the config of every route as the domain property, hard-coded to APP_URL.

You'll have to overwrite that part of the route client-side with something like this:

import route from 'ziggy';

const customRoute = (name, params, absolute, config) => {
    return window.location.origin + route(name, params, false, config);
}

I didn't run that code so you may need to tweak it, but that should replace the URL origin with the current one in the browser, so it'll always match whatever the user sees / is using. Does that make sense?

paulocastellano commented 3 years ago

Ahh, gotcha. That might not be possible in Ziggy directly, I think you'll have to work around it.

The issue is that your route definition, Route::group(['domain' => '{subdomain}.' . parse_url(env('APP_URL'), PHP_URL_HOST)], function () { ... }), uses the APP_URL environment variable, which is always https://my-application.com. Ziggy will usually use url('/') instead, which will always be the URL of the current request, but because your route actually contains the domain in the route definition itself, Ziggy will always have it in the config of every route as the domain property, hard-coded to APP_URL.

You'll have to overwrite that part of the route client-side with something like this:

import route from 'ziggy';

const customRoute = (name, params, absolute, config) => {
    return window.location.origin + route(name, params, false, config);
}

I didn't run that code so you may need to tweak it, but that should replace the URL origin with the current one in the browser, so it'll always match whatever the user sees / is using. Does that make sense?

I trying create an solution this to fix my problem.

I fixed for GET routes, but POST routes not join inside route method?

example:

const customRoute = (name, params, absolute, config) => {
//ROUTE POST NOT JOIN HERE
}

It's correct?

bakerkretzmar commented 3 years ago

Sorry I'm not sure what you mean by "join", are you using the @routes Blade directive?

If you are using @routes, try something like this in your HTML right after @routes:

<script>
    const customRoute = (name, params, absolute, config = Ziggy) => {
        return window.location.origin + route(name, params, false, config);
    }
</script>

Otherwise, if you're setting up Ziggy in your JavaScript code, make sure you import the route definitions and pass them to the custom route function:

import route from 'ziggy';
import Ziggy from './ziggy.js';

const customRoute = (name, params, absolute, config = Ziggy) => {
    return window.location.origin + route(name, params, false, config);
}

Does that help?

paulocastellano commented 3 years ago

@bakerkretzmar thanks for your help!

I found the "solution" to my problem, after try a lot, the solution to my case was:

app.js

.mixin({
    methods: {
        route: (name, params, absolute) => {
            let isAbsolute = true;

            if (name) {
                if (name.includes("portal.")) { // all my public routes, the name start with portal.
                    isAbsolute = false;
                    params.subdomain = null;
                }
            }

            return route(name, params, isAbsolute, {
                ...Ziggy,
            });
        },
    },
})

But this works only for GET ROUTES, for POST, PUT and DELETE routes i still change manually every component removing de subdomain from param (absolute routes not need subdomain), example:

login() {
  this.loginForm.post(route("portal.auth.login", {}, false), {
      preserveScroll: true,
      preserveState: true,
      onSuccess: () => (
          (this.isOpen = false), this.loginForm.reset()
      ),
  });
},

I hope this "solution" can help other people in the feature.

@bakerkretzmar you want close this issue?