mecha-cms / x.panel

Control panel feature.
Other
6 stars 0 forks source link

Propose More Readable Panel Route and Data #23

Closed taufik-nurrohman closed 2 years ago

taufik-nurrohman commented 2 years ago

The $state->x->panel->path property just adds complexity. Since panel relies on user, it would be simpler if we use the $state->x->user->path and $state->x->user->guard->path value as the namespace for panel routes.

Currently, the route pattern contains a command stored as /::command-name::/ in the URL. This pattern for sure makes it easy to instantly swap the commands by replacing the URL value using functions like strtr() and str_replace() due to its striking pattern. This pattern has been around since version 1.x.x of the panel, but currently, such URL patterns got quite dirty and becomes more and more difficult to be served as dynamic route patterns as more features in the panel grew.

Roughly speaking, I’d like to suggest a URL pattern for panel version 3.x.x, which is neater and will blends-in nicely with the front-end routes:

./user → the log-in page
./user/taufik → the user page
./panel/fire/asdf/page/lorem-ipsum.page → the panel path for “asdf” (custom) task
./panel/get/page/lorem-ipsum.page → the panel path for “edit” task
./panel/let/page/lorem-ipsum.page → the panel path for “delete” task
./panel/set/page → the panel path for “create” task

Both /user and /panel path will be stored in the same extension state storage (of the user extension). /user will be stored in $state->x->user->path and /panel can be stored in $state->x->user->guard->path. Due to the nature of user being independent from panel, the (secret) value of $state->x->user->guard->path don’t have to be assigned to user, so that by default, the /panel path will be returned as /user, since it was the default value for the secret user log-in path when that secret value is not set (as /panel, for example).

./user/get/page/lorem-ipsum.page → the default panel path for “edit” task
./user/let/page/lorem-ipsum.page → the default panel path for “delete” task
./user/set/page → the default panel path for “create” task
$base = $state->x->user->guard->path ?? $state->x->user->path ?? '/user';

Route::set($base . '/fire/?/*', function($fn, $path) { … });

Route::set($base . '/get/*', function($path) { … });
Route::set($base . '/let/*', function($path) { … });
Route::set($base . '/set/*', function($path) { … });

Others

Remove $_['/'] property. We should consider to use custom pattern in URL string that later will be replaced by the correct base panel URL. For example, we can use dummy URL protocol or fake relative URL which later will be replaced with http://127.0.0.1/panel/?/ in the output:

get://page/lorem-ipsum.page
let://page/lorem-ipsum.page
set://page/lorem-ipsum.page
.../get/page/lorem-ipsum.page
&/get/page/lorem-ipsum.page

Replace $_['f'] property with $_['source'], which its value can be either file or folder path. Ideally, the most common property name to store a file/folder path in Mecha is “path”. But, it is already used to store URL parts after the folder name, along with $_['hash'], $_['i'], $_['id'], and $_['query'] properties.

Alternatively, instead of removing $_['/'], we can rename it with $_['root'] to follow Mecha’s standard URL properties. But this likely will change the $_['path'] value, ideally, to also include the folder path. Which will make $_['id'] property becomes useless. Later, $_['root'] ideally should also include the $_['task'] part, which will make $_['task'] property becomes useless.

$_['ground'] // "http://127.0.0.1/panel"
$_['root'] // "http://127.0.0.1/panel/get"
$_['path'] // "page/lorem-ipsum.page"
$_['i'] // 1
$_['query'] // "foo=bar&baz=qux"
$_['hash'] // "foo"
// Detect current task
if (str_ends_with($_['root'], '/get')) { … }

// Detect current folder
if ('page' === $_['path'] || str_starts_with($_['path'], 'page/')) { … }
taufik-nurrohman commented 2 years ago

Or, to completely remove the task part in URL, and assign it to the query string instead.

./user/page → “edit” or “read” task by default
./user/page?task=set&type=page/page → “create” task
./user/page?task=let → “delete” task

The benefit is, you can run “delete” task using POST request. Makes it possible to trigger a hook to delete featured image file automatically.

<form method="post">
  <button name="task" type="submit" value="let">
    Delete
  </button>
</form>
taufik-nurrohman commented 2 years ago

What if I put the user name in URL too? This will make it easier to validate current user.

./user/taufik/page → “edit” or “read” task by default
./user/taufik/page?task=set&type=page/page → “create” task
./user/taufik/page?task=let → “delete” task

This would minimize the data used in panel. Less confusing.

$_['root'];
$_['path'];
$_['i'];
$_['query'];
$_['hash'];
igoynawamreh commented 2 years ago

What if I put the user name in URL too? This will make it easier to validate current user.

How about removing /user in the URL?

taufik-nurrohman commented 2 years ago

How about removing /user in the URL?

@igoynawamreh the reason why I want to keep the /user part is because user extension has a secret log-in path feature. It’s just that I don’t have the time to document it. If you set a path property to the guard property with random value, that value will then becomes the log-in path:

<?php

return [
    'path' => '/user',
    'guard' => [
        'path' => '/panel'
    ]
];

From the states above, expect to have http://127.0.0.1/user/taufik-nurrohman as the user profile page, and http://127.0.0.1/panel as the log-in path.

igoynawamreh commented 2 years ago

If so then I agree to use username in the URL.

igoynawamreh commented 2 years ago

But for some web apps I prefer not to put the user in the URL.

What do you think?

taufik-nurrohman commented 2 years ago

What kind of web app is that?

You can always alias the route if you want:

Route::set('taufik-nurrohman', function() {
    // Probably put some conditional check here, to prevent
    // conflict with the existing static page URL.
    if ($file = File::exist(LOT . DS . 'page' . DS . 'taufik-nurrohman.page')) {
        // Load static page here!
        Route::fire('*', ['taufik-nurrohman']);
    } else {
        Route::fire('user/:user', ['taufik-nurrohman']);
    }
});
taufik-nurrohman commented 2 years ago

You can also make your own control panel application if you want, by detecting if user is logged-in this way:

if (Is::user()) {
    require __DIR__ . DS . 'app.php';
} else {
    $path = $state->x->user->guard->path ?? $state->x->user->path ?? '/user';
    Guard::kick($path); // Redirect to log-in form
}

Check specific user name:

if (Is::user('taufik-nurrohman')) { … }
taufik-nurrohman commented 2 years ago

Ummm, probably shouldn’t put the user name in URL. User name in URL is useless because internally, we take the current user name from document cookie.

And to prevent conflict with public user URL, a task data in URL path is still needed anyway.

./user/asset → a user page for `@asset` user name
./user/get → a user page for `@get` user name
./user/get/asset → a panel page to retrieve the asset folder details
taufik-nurrohman commented 2 years ago

Expect panel to set $state->x->user->guard->path value automatically if it is not set:

// `.\lot\x\panel\index.php`
if (!State::get('x.user.guard.path')) {
    State::set('x.user.guard.path', '/panel');
}
taufik-nurrohman commented 2 years ago

Rethinking the root data. Probably, a path property is enough to store panel URL for testing purpose.

---
asset:
  0: []
  1: []
  2: []
  # …
  script: []
  style: []
author: null
can: []
d: null
description: null
form:
  lot: []
  type: null
has: []
hash: null
i: null
icon: []
is: []
kick: null
lot: []
not: []
path: null
query: []
source: null
status: 200
title: null
type: null
taufik-nurrohman commented 2 years ago

Internal URL builder for link and url component can be added to convert URL via component properties.

namespace x\panel\from {
    function link($value, $key) {}
}

namespace x\panel\to {
    function link($value, $key) {}
}
// `http://127.0.0.1/user/get/page/article/1?type=files#main`
echo x\panel\to\link([
    'd' => 'user/get',
    'hash' => 'main',
    'i' => 1,
    'path' => 'page/article',
    'query' => [
        'type' => 'files'
    ]
]);
taufik-nurrohman commented 2 years ago
if (str_ends_with($_['d'], '/get') && str_starts_with($_['type'] . '/', 'page/')) {
    // Is editing a page file and is in page editor mode
}
taufik-nurrohman commented 2 years ago

In Mecha 3.x.x, d and i property will be removed from URL data, including the form API.

---
asset:
  0: []
  1: []
  2: []
  # …
  script: []
  style: []
author: null
can: []
description: null
file: null
folder: null
has: []
hash: null
icon: []
is: []
kick: null
lot: []
not: []
path: null
query: []
status: 200
title: null
type: null
taufik-nurrohman commented 2 years ago

All data stored in $_ variable must be primitive and can be easily encoded/decoded from/to JSON for easy data transmission.

Who know, someday I will have the chance to fully convert the panel extension into React or Vue thing.