html_url,issue_url,id,node_id,user,created_at,updated_at,author_association,body,reactions,issue,performed_via_github_app
https://github.com/simonw/datasette/issues/647#issuecomment-1061282743,https://api.github.com/repos/simonw/datasette/issues/647,1061282743,IC_kwDOBm6k_c4_QeO3,9599,2022-03-08T00:32:34Z,2022-03-08T00:32:47Z,OWNER,It would be neat if the plugin could spot old-style hyphen hash URLs (maybe on 404) and redirect those too.,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",531755959,
https://github.com/simonw/datasette/issues/647#issuecomment-1061276646,https://api.github.com/repos/simonw/datasette/issues/647,1061276646,IC_kwDOBm6k_c4_Qcvm,9599,2022-03-08T00:22:11Z,2022-03-08T00:22:11Z,OWNER,I'm now convinced this is feasible enough that it's worth doing in time for Datasette 1.0.,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",531755959,
https://github.com/simonw/datasette/issues/647#issuecomment-1061276399,https://api.github.com/repos/simonw/datasette/issues/647,1061276399,IC_kwDOBm6k_c4_Qcrv,9599,2022-03-08T00:21:47Z,2022-03-08T00:21:47Z,OWNER,"This seems to do the job:
```python
@hookimpl
def startup(datasette):
    for name, database in datasette.databases.items():
        if database.hash:
            new_name = ""{}_{}"".format(name, database.hash[:7])
            del datasette.databases[name]
            datasette.databases[new_name] = database
```
Would have to teach the rest of the plugin to split on `_` and to only redirect if the user seems to be hitting the URL for an old hash after which Datasette has been restarted with an updated database.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",531755959,
https://github.com/simonw/datasette/issues/647#issuecomment-1061272544,https://api.github.com/repos/simonw/datasette/issues/647,1061272544,IC_kwDOBm6k_c4_Qbvg,9599,2022-03-08T00:14:42Z,2022-03-08T00:14:42Z,OWNER,Maybe the plugin should interfere with `datasette.databases` on startup and change the registered name for each one?,"{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",531755959,
https://github.com/simonw/datasette/issues/647#issuecomment-1061267615,https://api.github.com/repos/simonw/datasette/issues/647,1061267615,IC_kwDOBm6k_c4_Qaif,9599,2022-03-08T00:05:43Z,2022-03-08T00:05:43Z,OWNER,"Built a prototype of that plugin:
```python
from datasette import hookimpl
from functools import wraps


@hookimpl
def asgi_wrapper(datasette):
    def wrap_with_hashed_urls(app):
        @wraps(app)
        async def hashed_urls(scope, receive, send):
            # Only triggers on pages with a path not starting in /-/
            # and where the first page component matches a database name
            if scope.get(""type"") != ""http"":
                await app(scope, receive, send)
                return
            path = scope[""path""].lstrip(""/"")
            if not path or path.startswith(""-/""):
                await app(scope, receive, send)
                return
            potential_database = path.split(""/"")[0]
            # It may or may not be already dbname~hash
            if ""~"" in potential_database:
                db_name, hash = potential_database.split(""~"", 1)
            else:
                db_name = potential_database
                hash = """"
            # Is db_name a database we have a hash for?
            try:
                db = datasette.get_database(db_name)
            except KeyError:
                await app(scope, receive, send)
                return

            if db.hash is not None:
                # TODO: make sure db.hash is documented
                if db.hash[:7] != hash:
                    # Send a redirect
                    path_bits = path.split(""/"")
                    new_path = ""/"" + ""/"".join([""{}-{}"".format(db_name, db.hash[:7])] + path_bits[1:])
                    if scope.get(""query_string""):
                        new_path += ""?"" + scope[""query_string""].decode(""latin-1"")

                    await send({
                        ""type"": ""http.response.start"",
                        ""status"": 302,
                        ""headers"": [
                            [b""location"", new_path.encode(""latin1"")]
                        ],
                    })
                    await send({""type"": ""http.response.body"", ""body"": b""""})
                    return
                else:
                    # Add a far-future cache header
                    async def wrapped_send(event):
                        if event[""type""] == ""http.response.start"":
                            original_headers = event.get(""headers"") or []
                            event = {
                                ""type"": event[""type""],
                                ""status"": event[""status""],
                                ""headers"": original_headers + [
                                    [b""Cache-Control"", b""max-age=31536000""]
                                ],
                            }
                        await send(event)

                    await app(scope, receive, wrapped_send)
                    return
                
            await app(scope, receive, send)
        return hashed_urls
    return wrap_with_hashed_urls
```
One catch: it doesn't affect the way URLs are generated - so every internal link within Datasette links to the non-hash version and then triggers a 302 redirect to the hashed version.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",531755959,