Path parameters in Angular2 - ajax

How are we meant to handle path parameters in Angular 2 when doing RESTful calls to a Web Service?
I've found the URLSearchParams object for query parameters but from what I have found it seems we'll have to make do with string-concatenation for the path itself. Like
let url = 'api/v1/something/' + encodeURIComponent(someParameter) +
'/etc/' + encodeURIComponent(anotherParam) + '/and/so/on';
Is there, included in angular2, something that does similar to:
let url = new URL('api/v1/something/{id}/etc/{name}/and/so/on', param1, param2);
I can of course create something similar myself but prefer if there is something included in angular2.

Indeed, you can use string interpolation and nearly exactly the same thing you suggest, assuming id and name are already set and encoded.
let url = new URL(`api/v1/something/${id}/etc/${name}/and/so/on`);
I'll note that currently the ES6 URL class only supports searchParams, but has no notion of path parameters.
Also, I know of no method that will automatically encode your parameters without implementing your own QueryEncoder with URLSearchParams

If anybody is interested, this is what I ended up doing:
export interface PathParameters {
[parameterName: string]: string;
}
export class Url {
public static create(url: string, parameters: PathParameters) {
const placeholders = url.match(/({[a-zA-Z]*})/g);
placeholders.forEach((placeholder: string) => {
const key = placeholder.substr(1, placeholder.length - 2);
const value = parameters[key];
if (!value) {
throw new Error(`parameter ${key} was not provided`);
}
url = url.replace(placeholder, encodeURIComponent(value));
});
return url;
}
}
How to use:
const url = Url.create('/api/cars/{carId}/parts/{partId}', {
carId: carId,
partId: partId
});

Related

Remix: Generate links to all routes programatically

I'm a creating a blog using Remix.
Remix supports MDX as a route, which is perfect for me, as I can just write my blog posts as .mdx files and they'll naturally become routes.
However, if you access the index route - I would like to display the list of links to all the articles, (ideally sorted by creation date, or some kind of metadata in the .mdx file).
I can't see a natural way to do this in the Remix documentation.
Best solution I've got is that I would run a script as part of my build process, that the examines the routes/ folder and generates a TableOfContents.tsx file, which the index route can use.
Is there an out of the box solution?
The remix documentation does hint at this, but doesn't appear to provide a solid suggestion.
Clearly this is not a scalable solution for a blog with thousands of posts. Realistically speaking, writing is hard, so if your blog starts to suffer from too much content, that's an awesome problem to have. If you get to 100 posts (congratulations!), we suggest you rethink your strategy and turn your posts into data stored in a database so that you don't have to rebuild and redeploy your blog every time you fix a typo.
(Personally, I don't think redeploying each time you fix a typo is too much of a problem, it's more I don't want to have to manually add links everytime I add a new post).
For posterity, I'll post my current solution.
I run this script at build time, and it inspects all the blog posts and creates a <ListOfArticles> components.
import { readdir, writeFile, appendFile } from 'node:fs/promises';
const PATH_TO_ARTICLES_COMPONENT = "app/generated/ListOfArticles.tsx";
const PATH_TO_BLOG_POSTS = "app/routes/posts"
async function generateListOfArticles() {
try {
const files = await readdir(PATH_TO_BLOG_POSTS);
await writeFile(PATH_TO_ARTICLES_COMPONENT, `
export const ListOfArticles = () => {
return <ul>`);
for (const file of files) {
if (!file.endsWith(".mdx")) {
console.warn(`Found a non-mdx file in ${PATH_TO_BLOG_POSTS}: ${file}`)
}
else {
const fileName = file.split('.mdx')[0];
await appendFile(PATH_TO_ARTICLES_COMPONENT, `
<li>
<a
href="/posts/${fileName}"
>
${fileName} </a>
</li>
`)
}
}
appendFile(PATH_TO_ARTICLES_COMPONENT, `
</ul>
}
`)
} catch (err) {
throw err;
}
}
generateListOfArticles().then(() => {
console.info(`Successfully generated ${PATH_TO_ARTICLES_COMPONENT}`)
})
Is quite rudimentary - could be extended by inspecting the metadata section of the MDX file to get a proper title, doing sort order, etc.
This is not exactly what you were looking for but I used your solution as inspiration to figure out a solution to my own problem: Generating a type for routes so that the type checker can make sure there aren't broken links hanging around in the app.
Here's my script, generate-app-path-type.ts:
import { appendFile, readdir, writeFile } from "node:fs/promises";
const PATH_TO_APP_PATHS = "app/utils/app-path.generated.ts";
const PATHS_PATH = "app/routes";
async function collectFiles(currentPaths: string[], currentPath = "") {
const glob = await readdir(PATHS_PATH + currentPath, { withFileTypes: true });
for (const entry of glob) {
if (entry.isFile()) {
currentPaths.push(currentPath + "/" + entry.name);
} else if (entry.isDirectory()) {
await collectFiles(currentPaths, currentPath + "/" + entry.name);
}
}
}
// NOTE: the `withoutNamespacing` regex only removes top level namespacing like routes/__auth but
// wouldn't catch namespacing like routes/my/__new-namespace
function filenameToRoute(filename: string): string {
const withoutExtension = filename.split(/.tsx?$/)[0];
const withoutIndex = withoutExtension.split(/\/index$/)[0];
const withoutNamespacing = withoutIndex.replace(/^\/__\w+/, "");
// Return root path in the case of "index.tsx"
return withoutNamespacing || "/";
}
async function generateAppPathType() {
const filenames: string[] = [];
await collectFiles(filenames);
await writeFile(PATH_TO_APP_PATHS, `export type AppPath =\n`);
const remixRoutes = filenames.slice(0, filenames.length - 1).map(filenameToRoute);
// only unique routes, and alphabetized please
const routes = [...new Set(remixRoutes)].sort();
for (const route of routes) {
await appendFile(PATH_TO_APP_PATHS, ` | "${route}"\n`);
}
await appendFile(
PATH_TO_APP_PATHS,
` | "${filenameToRoute(filenames[filenames.length - 1])}";\n`,
);
}
generateAppPathType().then(() => {
// eslint-disable-next-line no-console
console.info(`Successfully generated ${PATH_TO_APP_PATHS}`);
});
The script generates a type like:
export type AppPath =
| "/"
| "/login";
I use this type with a utility file:
import type { AppPath } from "./app-path.generated";
import { SERVER_URL } from "./env";
export function p(path: AppPath): AppPath {
return path;
}
export function url(path: AppPath): string {
return new URL(path, SERVER_URL).toString();
}
Finally, whenever I need a path (like when I redirect a user), I can do this which is a little more typing than the string literal but is a confirmed URL according to the type checker:
p("/login") // or url("/login")

HotChocolate Stitching How to remove argument from a mutation?

I have an GraphQL api implemented using HotChocolate 11. The schema changed recently and I would like to use our stitching layer to keep it the same for clients.
I have an mutation that looked like this:
type Mutation {
generate(apertures: [ApertureInput!]! templates: [TemplateInput!]!): [GeneratedApertures!]!
}
I had to add one more argument, also we added a query to get the additional data
type Mutation {
generate(apertures: [ApertureInput!]! templates: [TemplateInput!]! algorithm: AlgorithmInput!): [GeneratedApertures!]!
}
type Query {
standardAlgorithm: StandardAlgorithm
}
type StandardAlgorithm {
shrink: Int!
}
What I'm trying to do is to use our stitching layer to add the old signature of the mutation and call a remote schema in the resolver to get the additional data and pass it to the new mutation.
I have renamed the new signature on the stitch and created a mutation that looks like the old one:
extend type Mutation {
product_generate(apertures: [ApertureInput!]! templates: [TemplateInput!]! algorithm: AlgorithmInput!): GeneratedApertures!
#source(name: "generate" schema: "productdefinition")
#delegate(schema: "productdefinition")
generate(apertures: [ApertureInput!]! templates: [TemplateInput!]!): GeneratedApertures!
}
But I am lost using the stitching context to build the sub queries.
public class GenerationMutationTypeExtension : ObjectTypeExtension
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Name("Mutation");
descriptor.Field("generate")
.Resolve(async ctx => {
var context = ctx.Service<IStitchingContext>();
var executor = context.GetRemoteRequestExecutor("productdefinition");
var request =
QueryRequestBuilder.New()
.SetQuery("{ standardAlgorithm { shrink } }")
.SetServices(executor.Services)
.Create();
var result = (QueryResult)await executor.ExecuteAsync(request);
var template = result.Data?["standardTemplate"];
if (template is NullValueNode || template is null) throw new GraphQLRequestException("Missing configuration!!!");
var shrink = (string)((IValueNode)((Dictionary<string, object>)template)["shrinkPercent"]).Value;
var apertures = ctx.ArgumentLiteral<IValueNode>("apertures");
var templates = ctx.ArgumentLiteral<IValueNode>("templates");
var selection = ctx.Selection.SyntaxNode.SelectionSet;
var query =
$"{{ product_generateDeposits(apertures: {apertures} templates: {templates} algorithm: {{ standardAlgorithm: {{ shrink: {shrink} }} }}) {selection} }}";
request =
QueryRequestBuilder.New()
.SetQuery(query)
.SetServices(executor.Services)
.Create();
result = (QueryResult)await executor.ExecuteAsync(request);
return result;
});
}
}
The first query for the shrink is working fine, I get the data. But I struggle with putting the other mutation together. Is there maybe a better way how to achieve this? Documentation is very vague when it comes to stitching.

How to write data back to storage?

I have a method called changePlaceName and i know it is working but after i call getPlaces to see the changes, i don't see the new place name instead i see the name when i created a new place.
this is changePlaceName
export function changePlaceName(placeId: u32, placeName: PlaceName): void {
assert(placeId >= 0, 'Place ID must be >= 0');
const place = Place.find(placeId);
logging.log(place.name); //gives "Galata Tower"
place.name = placeName;
logging.log(place.name); // gives "New Galata Tower"
}
I need to save it somehow but I don't know how to do it.
I also tried this way;
export function changePlaceName(placeId: u32, placeName: string): void {
assert(placeId >= 0, 'Place ID must be >= 0');
const place = Place.find(placeId);
logging.log(place.name);
place.name = placeName;
let newPlace = storage.get<string>(placeName, 'new galata tower');
storage.set<string>(placeName, newPlace);
logging.log('New place is now: ' + newPlace);
}
Now my visual code is complaining about the newPlace inside the storage.set
How do I fix it?
What is the code of Place.find? I assume you are using a persistent map under the hood.
Is there a Place.set? You need to store the Place back to the same key used to find it.
because you're using some kind of class to manage the concept of "Place", why not add an instance method to that class to save() the place once you've changed it's name?
would help if you also posted your code for Place here, by the way
my guess is that it looks something like this?
!note: this is untested code
#nearBindgen
class Place {
private id: number | null
private name: string
static find (placeId: number): Place {
// todo: add some validation for placeId here
const place = places[placeId]
place.id = placeId
return place
}
// here is the instance method that can save this class
save(): bool {
places[this.id] = this
}
}
// a collection of places where placeId is the index
const places = new PersistentVector<Place>("p")

Hidden parameter in springdoc-openapi doesn't work

I have Spring Boot application with version 2.3.0.
and springdoc-openapi-webflux-ui in version 1.4.1.
I have annotated parameter in operation like this.
parameters = {
#Parameter(
hidden = true, schema = #Schema(implementation = Boolean.class),
in = ParameterIn.QUERY, name = DO_NOT_FORWARD
)
With hidden = true I expected that this parameter will not be visible in swagger-ui. But it is.
Did I misunderstood this parameter or is it not doing what it was supposed to do?
I want this parameter to be in api-docs, to have generated client able to use this parameter, but I want it invisible in swagger-ui
Try
#Parameter(name = "paramName", hidden = true)
#GetMapping("/example")
public Object example(String paramName) {
return null;
}
instead of
#Operation(
parameters = {
#Parameter(name = "paramName", hidden = true)
}
)
#GetMapping("/example")
public Object example(String paramName) {
return null;
}
You just make sure that the name of in the #Parameter annotation, is the exact name of the operation parameter you want to hide.
You can have a look at the documentation as well:
https://springdoc.org/faq.html#how-can-i-hide-a-parameter-from-the-documentation-
If you are still having coniguration issue, you can add the code of sample HelloController that reproduces your problem, or you can add the link to a minimal, reproducible sample in github.
As per the doc
#GetMapping("/example")
fun someCall: Response (
#Parameter(hidden = true) String paramName
) {
return response;
}

How to setup dynamic URLs for Resources in admin-on-rest?

I've a requirement where the below resources are accessed based on the type of user logged in.
R1: /mit/oss/12345/peers
R2: /mit/md/6879/ngrp
R1 should be accessible by an user with id: 12345. And R2 should be accessible by an user with id - 6879.
The question is - how to define Resource URLs with dynamic values (like: userId in the URLs) based on the user who is logged it. I am aware of aor-permissions library to switch the Menus based on user permission, but is it possible to define the resources themselves dynamically with ids in the URLs?
You can write a wrapper on your rest client that can intercept the call and dynamically generate the URL.
Basically decorate the rest client like in the docs here --> https://marmelab.com/admin-on-rest/RestClients.html#decorating-your-rest-client-example-of-file-upload
You can then check for a case like in below psuedocode
if (type === 'AOR_REST_TYPE' && resource === 'BASE_RESOURCE') {
if (getUserFromLocalStorage === usr1) {
url = url1
} else {
url = url2
}
options.method = 'GET';
// other options
}
Here is a simple example of remapping the resource URL using a map.
import {simpleRestClient} from 'admin-on-rest';
// Update the remap table appropriately to map from a resource name to a different path
const remap = {
"resource1" : "resource1Remapped",
"releasepresets" : "productionintents/releasepresets"
}
const simpleRestClientWithResourceUrlRemap = (apiUrl) => {
var client = simpleRestClient(apiUrl);
return (type, resource, params) => {
if (remap[resource]) {
console.log('remapping resource from ' + resource + ' to ' + remap[resource]);
resource = remap[resource];
}
return client(type, resource, params);
}
}
export default (simpleRestClientWithResourceUrlRemap);
Instead of a simple remap, a function with logic could be used.

Resources