I want to write a URL shortener as a standalone CouchApp, but I'm wondering if it is possible.
Obviously, a core requirement for a URL shortener is to have short and unique keys.
What I want is to POST a long URL to CouchDB and get a shortened URL. I thought about using an update handler, but it would have to query the DB to check if the key is unique, which seems not to be possible.
Is there a way to generate short and unique keys with CouchDB? Or do I need a thin wrapper around CouchDB?
I would go for a thin wrapper, based on documents with the following structure:
{ _id : short_url , url : long_url }
Inserting of a new long URL could be done in a single step: have the wrapper generate a new _id, attempt a PUT, and try again with a new _id until it succeeds. This will guarantee that every short URL is only used once.
I'm afraid this "generate, attempt, retry" approach is the only strategy that ensures uniqueness, and it's not available without a wrapper.
If you wish the same long URL to reuse the same short URL, you can also add a view that echo(doc.url,null) and grab the _id for your URL if it does exist. This means that, unless several clients try to add the same long URL at the exact same time, only one short URL will be used for that long URL.
Related
I am needing to make some sharable blog post URLs. To do that, the URL must be something like webpage.com/blog-post-title. It cannot be webpage.com/5GFd5GDSg2345WD.
Since I am using dynamic routing, I need to get a Contentful entry using nothing but what is on the URL. There should not be any queries because queries are ugly and reduce shareability, like webpage.com/blog-post-title?query=queriesAreUgly.
Unfortunately, I need the entryId to get the entry. Also unfortunately, the entryIds are all very ugly and therefore completely useless/unusable. I wish I could set my own entryId, but this does not appear to be possible for mysterious reasons.
I could make a lookup table that pairs URLs with entryIds, but I'm going to be handing this contentful project to someone who is not tech savy, and they should not have to manage a lookup table.
I could get all blog entries then filter by blog title, but, very obviously, this is inefficient, as I would be loading thousands of lines of text for no reason at all.
I could create my own backend API and doing all this myself, but this is also a bad solution because it would take too much time and I could not give it to my non-tech-savvy client.
There are seemingly no solutions to this problem which created by Contentful's inherent needless inflexibility.
The only efficient way to get this to work is to find the entry not by its ID but by one of its fields. Is there a performant/efficient way to do this, or am I just going to have to filter through every single blog post until I find the one with the correct title?
How about adding a 'slug' field to the blog post content type, which you can auto-generate from the title using the field settings (so you don't have to type it out manually?)
You can then filter on the slug field in the query.
If you're using the JavaScript SDK (which it sounds like you are), you can use getEntries() and filter by the slug field to get a single blog post. Like so:
import { createClient } from "contentful";
const client = createClient({
space: {SPACE_ID},
accessToken: {ACCESS_TOKEN},
});
const response = await client
.getEntries({
content_type: "blogPost",
limit: 1,
"fields.slug": "blog-post-title",
})
.then((entry) => entry)
.catch(console.error);
What's the difference between using : and ? in a URL? For example /products/:id and /products?id=1? I am trying to get the values from the URL like this Product.findById (req.params.id) but I was wondering which one is most suitable. I know using : do I have to use req.params and ? req.query but I don't understand the difference between them, are they the same?
in my point of view, it is totally different if you are using RESTFUL API pattern
/products/:id called path parameters
The path parameters determine the resource you’re requesting for. Think of it like an automatic answering machine that asks you to press 1 for service, press 2 for another service, 3 for yet another service and so on.
Path parameters are part of the endpoint itself and are not optional
but query parameters
Technically, query parameters are not part of the REST architecture, and they used to help you completely understand how to read and use API’s Query parameters give you the option to modify your request with key-value pairs.
Having your parameters in the query is conceptually optional to the router, query parameters are more of properties and descriptions of the request itself, like when saying GET /users?sort=asc, in this case, the sort attribute was more of a description to the request and the request could complete the fetch without it, that might not always be the case, but a query parameter still describes its request even if it was mandatory.
On the other hand, URL parameters are part of the request itself, the URL without the parameter doesn't make sense, like GET /users/:userID, in this case, not supplying userID will supply unexpected data (A list of users for example) if it didn't break the router completely. URL parameters play part in defining the request rather than just describing it, and they can't be optional.
I want to fetch all documents in a CouchDB database where the document ID starts with a given prefix.
Did some searching and found, according to the CouchDB Documentation, the best way to accomplish this is by using a startkey and endkey, where the startkey is the prefix, and the endkey is the prefix with a high-value unicode character appended at the end.
So, as I understand it, a call to "http://server:5984/some_db/_all_docs?startkey=2018&endkey=2018\ufff0&include_docs=true" should fetch all docs from some_db with an ID starting with '2018'.
That url is being encoded by the web browser as follows:
http://server:5984/some_db/_all_docs?startkey=2018&endkey=2018%EF%BF%B0&include_docs=true
And the response I get back is {"error":"bad_request","reason":"invalid UTF-8 JSON"}
So I tried just sticking to pure ASCII and used ~ instead of \ufff0. Same response. Also got the same response using a z.
If I do something like _all_docs?startkey=2018&endkey=2019&include_docs=true&inclusive_end=false everything works fine and I get the expected results. However, I can't guarantee the prefix will always be a number, and I get the impression trying to implement it like that programmatically will cause me issues some where or some how. Any thoughts?
I'm using Dart running in the web browser to make the request, if it makes a difference.
Update
So, I've realized in actuality _all_docs does not support the endkey and startkey parameters. The request I originally thought was working was actually just returning the entire database.
I had assumed _all_docs supports startkey and andkey because I have used PouchDB in the past, which does support those parameters in the allDocs() function.
Still looking for a solution, since this project is not using PouchDB, but at least now I know what the problem is.
Update 2
Previous update was wrong, Although the documentation of _all_docs doesn't have these parameters listed, there is a note which I had missed indicating it also supports the parameters for view, see my answer below.
Okay, I figured it out.
I was wrong in my update, startkey and endkey are supported by _all_docs because it's just a built-in view, so all the parameters for views apply. However, it expects the passed values to be JSON values, not just a bare string as a key. The solution is just to put quotation marks around the keys.
That is, encoded quotation marks, e.g. startkey=%222018%22&endkey=%222018%EF%BF%B0%22
People get the data from url like http://domain/api/user/1 and the 1 is the id(primary key) in the database. We want to avoid this to prevent people detect the ids.
Then I found Hashids which can encode/decode the id to string, the url may change to http://domain/api/user/D42dhDLdk
It is a wonderful solution at the first glance, however how to use it in the real application?
1 encode
When records are retrieved from the database, we should encode the id to string, which is the right place to do this job? The only right place I can thought is the deserialization of the records, for example, when you serve data of json, you can encode the related ids to string, but you have to make sure which fields should be encoded.
For example:
{user:{id:1,name:'xx',projects:[{id:1,name:'p1',id:2,name:'p2'}]},icon_id:1}}
The id of the user project and the icon_id should be encoded, there are no clear rules to abstract that.
2 decode
You have to decode the string to id when retrieve the data from db, I think we can do this in the route or controller layer, however there are still no clear rules to make a abstract middleware to hanlde this, since the encoded string may exist in the url or the query params.
I have not find any general solution from google, so I wonder how do you handle this kind of problem?
Update:
I am using Mysql, and Express with sequelzie as the ORM tool.
I am currently working with the Algolia search API and I am unable to figure out how I would limit the results by key value searching + query string. By this i mean this.
I have a list of properties.
Each property belongs to a client.
Within the application If i am looking at a client information card and I want to search for a property that client owns It would make more sense to limit the results to the client and then look for the query string.
I am using MongoDB as my DB and storing the client id as a sub document like so
//Property Document
{
_id : "randomID"
client : {
_id : "randomID",
name : "ClientName"
}
}
If you want to restrict the search to a specific client, I would go for facet filtering to restrict the search to that client only.
Add client._id in the attributesForFaceting in your index settings
Filter your searches with the facetFilters=client._id:MYCLIENTID query parameter
Then, you should also take a look at the Secured API keys which are able to encode such restriction in a secure way (so the end-user cannot patch the JS code and work-around the filtering).
There is parameter called restrictSearchableAttributes[link] to restrict, at query time, search to some attributes only. Nevertheless, in your case I think you'd get more accurate results by putting each client info into a different record (+ the info of the related document).