I feel like pulling my hair out; this is either super simple and i'm having brain freeze or it is not that simple.
What I want
I am trying to unshorten a shortened URL using firebase, when a user goes to:
myapp.firebaseappurl.com/url/SHORTENEDLINK
SO wont let me add a shortened URL
I would like the output to be:
{
"url": "https://stackoverflow.com/questions/45420989/sphinx-search-how-to-use-an-empty-before-match-and-after-match"
}
What I have tried
firebase.json file:
{
"hosting": {
"public": "public",
"rewrites": [ {
"source": "/url/:item",
"destination": "/url/:item"
} ]
}
}
index.js file:
const functions = require('firebase-functions');
exports.url = functions.https.onRequest((requested, response) => {
var uri = requested.url;
request({
uri: uri,
followRedirect: true
},
function(err, httpResponse) {
if (err) {
return console.error(err);
}
response.send(httpResponse.headers.location || uri);
}
);
});
Result
When I go to myapp.firebaseappurl.com/url/SHORTENEDLINK I get the following:
Error: could not handle the request
You are seeing Error: could not handle the request since there probably was an exception and it timed out.
Check your logs using:
firebase functions:log
Refer docs for more details
Here's how I got URL unshortening to work
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const http = require('http');
const urlP = require('url');
const unshorten = (url, cb) => {
const _r = http.request(
Object.assign(
{},
urlP.parse(url),
{
method: 'HEAD',
}
),
function(response) {
cb(null, response.headers.location || url);
}
);
_r.on('error', cb);
_r.end();
};
const resolveShortUrl = (uri, cb) => {
unshorten(uri, (err, longUrl) => {
if (longUrl === uri) {
cb(null, longUrl);
} else {
resolveShortUrl(longUrl, cb);
}
});
};
exports.url = functions.https.onRequest((requested, response) => {
var uri = requested.query.url;
resolveShortUrl(uri, (err, url) => {
if (err) {
// handle err
} else {
response.send({ url });
}
});
});
You can follow the hello world example straight away and use the above code as your function.
Above code uses HEAD requests to peek into 'Location` field of the headers and decides if the url can be further unshortened.
This is lighter as HEAD requests ask for no body (thereby avoiding body parsing). Also, no third party lib required!
Also note that the url passed as a query param. So the request would be
http://<your_firebase_server>/url?url=<short_url>
Saves you the trouble of URL re-writes. Plus semantically makes a little more sense.
Did you tried using { source: '/url/**' } syntax?
You can use something like this;
{
"hosting": {
"public": "public",
"rewrites": [ {
"source": "/url/**",
"function": "/url"
}]
}
}
and then you can parse the url from the request.
exports.url = functions.https.onRequest((req, res) => {
// parse the url from the req and redirect to the correct link
});
You should try this in the firebase.json, its worked for me:
"source": "/**",
I also tried "source": "/url/**" but its not worked.
I think your code is fine. What you're doing incorrectly is that you're using Express-js notations in your firebase.json's rewrites node. (the :item part). These don't work in the Firebase Realtime Database.
So, instead of doing that, change your firebase.json to the following :-
{
"hosting": {
"public": "public",
"rewrites": {
"source": "YOUR SHORTENED URL",
"destination": "YOUR ORIGINAL URL"
}
}
}
This is also the advocated approach in the Cloud Functions for Firebase's URL Shortener sample.
First make sure you are receiving the request properly with the shortened url.
const functions = require('firebase-functions');
const express = require('express');
var express_app = express();
express_app.use(body_parser.text({type: ()=>true}));
express_app.all('*', (req, res) => {
console.log(req.path);
res.send(JSON.stringify(req.path));
});
exports.url = functions.https.onRequest(express_app);
Now when you visit myapp.firebaseappurl.com/url/SHORTENEDLINK you should see the SHORTENEDLINK in plain text. When that's working, try the redirect.
const functions = require('firebase-functions');
const express = require('express');
const request = require('request');
var express_app = express();
express_app.use(body_parser.text({type: ()=>true}));
express_app.all('*', (req, res) => {
var url = req.path;
request({
uri: uri,
followRedirect: true
},
function(err, httpResponse) {
if (err) {
return console.error(err);
}
res.send(httpResponse.headers.location || uri);
}
);
});
exports.url = functions.https.onRequest(express_app);
Also it's good practice to npm install with --save so they end up in the packages.json. While firebase copies your node_modules folder, most other SaaS platforms run npm install.
Related
I hope you can help me, I need to send some parameters in json format like this:
{
"InformationA": {
"str_id": 1,
"str_description": "message",
"str_email": "abcd#abcd.com.co"
},
"AddConfiguration": [
{
"int_code": 1,
"str_valor": "32201"
},
{
"int_code": 104,
"str_valor": "https://www.google.com.co/"
},
{
"int_code": 108,
"str_valor": "1"
}
]
}
I am trying to send the json through the angular service in this way but I don't know if it is correct?:
sendData(InformationA,AddConfiguration){
const params = 'InformationA=' +JSON.stringify(InformationA)+'AddConfiguration=' +
JSON.stringify(AddConfiguration);
return this.http.post<any>(`${this.route}/send-data`, params , { headers: this.headers });
}
also create a function in the nodejs backend to see how it would arrive:
#Post('send-data')
async receibeData(#Req() req, #Res() res) {
try {
const data = req.body;
res.status(HttpStatus.OK).json(data)
} catch (err) {
throw err;
}
}
and by console it is printed in this way:
{,…}
InformationA:"
[{"str_id":"1","str_description":"message","str_email":"abcd#abcd.com.co"}]Addconfiguration=
[{"int_code":1,"str_valor":"32201 "},{"int_code":104,"str_valor":"https://www.google.com.co
"},{"int_code":108,"str_valor":"1 "}]"
I am really very new to this and I would like to know how I adapt my data so that it can be sent as requested.
I think you should try to build the JSON object corresponding to your requirement. You should not use JSON.stringify for this purpose. I hope this will help you out.
sendData(InformationA,AddConfiguration) {
const params = {
InformationA: InformationA,
AddConfiguration: AddConfiguration
};
return this.http.post<any>(`${this.route}/send-data`, params , { headers: this.headers });
}
I'm create a hook file with the following information, which is Hooks.js
Hooks.js is working to authenticate an actions with JWT when need it, I dont need it in all servies calls.
As my understanding the syntax to call a hook was app/use route/hooks and those hooks were only applied to and specific route and not globally.
module.exports = {
errorHandler: (context) => {
if (context.error) {
context.error.stack = null;
return context;
}
},
isValidToken: (context) => {
const token = context.params.headers.authorization;
const payload = Auth.validateToken(token);
console.log(payload);
if(payload !== "Invalid" && payload !== "No Token Provided"){
context.data = payload._id;
}
else {
throw new errors.NotAuthenticated('Authentication Error Token');
}
},
isValidDomain: (context) => {
if (
config.DOMAINS_WHITE_LIST.includes(
context.params.headers.origin || context.params.headers.host
)
) {
return context;
}
throw new errors.NotAuthenticated("Not Authenticated Domain");
},
normalizedId: (context) => {
context.id = context.id || context.params.route.id;
},
normalizedCode: (context) => {
context.id = context.params.route.code;
},
};
Then I create a file for services and routes, like the following:
const Hooks = require("../../Hooks/Hooks");
const userServices = require("./user.services");
module.exports = (app) => {
app
.use("/users", {
find: userServices.find,
create: userServices.createUser,
})
.hooks({
before: {
find: [Hooks.isValidDomain],
create: [Hooks.isValidDomain],
},
});
app
.use("/users/:code/validate", {
update: userServices.validateCode,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.normalizedCode],
},
});
app
.use("/users/personal", {
update: userServices.personalInfo,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.isValidToken],
},
});
};
Why Hooks.isValidToken applies to all my update methods? Even if I'm not calling it?
Please help.
app.hooks registers an application level hook which runs for all services. If you only want it for a specific service and method it needs to be app.service('users').hooks().
I'm developing a site with multiple languages. Some routes will therefore also have to be localized and I'm not sure how to do this properly.
I'm using #koa/router for routing.
For this example it's only English and Swedish but the site will handle more languages.
I can setup routes to match words in different languages like
router.get('/(create-account|skapa-konto)/', (ctx, next) => {
ctx.body = translate('signup_welcome');
await next();
});
But, I want the English site to only respond to '/sign-up' and send 404 for '/skapa-konto' (and vice versa).
In the real world the route would point to some controller function. So if I set up individual routes for each language I would have to change all localized routes manually should the controller function change in the future. That's something I would like to avoid ;)
Any suggestions?
I ended up solving this by extending the Router like this:
const LocalizedRouter = class extends Router {
/**
* Set up route mapping
* #param {object} options
*/
constructor(options) {
if (!Array.isArray(options.languages)) {
throw new TypeError('Languages must be of type Array');
}
super(options);
this.languages = options.languages;
}
/**
* Router function for GET method
* #param {string | Object<string, string>} RouteCollection
*/
get(routes, func) {
if (typeof(routes) === 'string') {
super.get(routes, func);
return;
}
if (typeof(routes) === 'object') {
for(const key in routes) {
if(!this.languages.includes(key)) {
continue;
}
if(typeof(func) !== 'function') {
throw new TypeError('Middleware must be a function');
}
const checkLanguageAndMount = async (ctx, next) => {
if(ctx.state.lang !== key) {
return next();
}
return func(ctx, next);
};
super.get(routes[key], checkLanguageAndMount);
}
return;
}
throw new TypeError('"Routes" must be a string or an object');
}
};
I can then set up my routes like this:
const myRouter = new LocalizedRouter({
languages: ['en', 'sv']
});
myRouter.get({
'en': '/create-account',
'sv': '/skapa-konto'
}, (ctx, next) => {
ctx.body = translate('signup_welcome');
await next();
};
This can probably be cleaned up but it does solve what I wanted to do.
EDIT: Fixed bug that caused 404 if two languages had identical paths
This problem interested me so I created a small github repo with some code. I'll try to explain here:
I created an array with some options:
const localeConfig = [
{
locale: "en",
routes: [
{
path: "/sign-up",
controllers: [enController],
method: "GET",
},
],
prefix: false,
},
{
locale: "se",
routes: [
{
path: "/skapa-konto",
controllers: [seController],
method: "GET",
},
],
prefix: false,
},
];
I then pass this object to a setupRoutes function that basically iterates the array, generating all the routes according to those options.
const setupRoutes = (localeConfig) => {
// Have some check to prevent duplicate routes
localeConfig.forEach((opt) => {
// Adding prefix according to option
const localePrefix = opt.prefix ? `/${opt.locale}` : "";
opt.routes.forEach((route) => {
const path = `${localePrefix}${route.path}`;
router[route.method.toLowerCase()].apply(router, [
path,
...route.controllers,
]);
});
});
};
So, for instance, if you were to change any of the controllers in either language you would only need to update the specific locale object.route.controllers. I imagine you could even have each different locale in a different file to have some modularity.
The github repo is here and I would really like to have you contribute to it if you have any idea on how to improve this.
Cheers!
I am trying to create an app and within the app the user can install a theme, however, I can't seem to work out why the theme is not being created. It keeps pulling the themes already installed on my store to the console, my code doesn't seem to create a theme that would show up on my shopify store.
server.js
router.post('/api/theme', async (ctx) => {
try {
const results = await fetch("https://" + ctx.cookies.get('shopOrigin') + "/admin/themes.json", {
headers: {
'X-Shopify-Access-Token': ctx.cookies.get('accessToken')
},
})
.then(response => response.json())
.then(json => {
console.log("https://" + ctx.cookies.get('shopOrigin') + "/admin/api/2020-01/themes.json", json);
});
ctx.body = {
data: results
};
} catch (err) {
console.log(err)
}
});
frontend .js file
async function getUser() {
var url = `/api/theme`;
var method = 'post';
const theme = {
theme: {
name: "Lemongrass",
src: "https://codeload.github.com/Shopify/skeleton-theme/zip/master"
}
};
const data = JSON.stringify(theme);
fetch(url, { method: method, body: data})
}
In order to create a theme you need a zip archive of the theme you like to create.
The end point should be /admin/api/2020-01/themes.json and the body should be something like this:
{
"theme": {
"name": "Theme name",
"src": "http://themes.shopify.com/theme.zip",
"role": "unpublished"
}
}
Please refer to https://shopify.dev/docs/admin-api/rest/reference/online-store/theme#create-2020-01 for more information.
At the moment from your code I don't see neither the correct POST request, neither the archive file.
I am developing a web app using the MEAN stack (no Mongo for now)
I am trying to pass the name of a file on the server using a query paramerer, the error happens when i get :
"localhost:8080/api/result?filename=for-debug-file-name"
It is working well if I remove the console.log() right below
But when I get the query parameter it gets me the "Error: No default engine was specified and no extension was provided”.
(This route correspond to api/result)
var express = require('express');
var router = express.Router();
router.get('/', function(req, res) {
console.log(req.query('filename')); // ERROR
res.status(200).json({ "json-test": 42 });
})
module.exports = router;
Here are my angular routes :
const appRoutes: Routes = [
{
path: 'result',
component: ResultComponent,
},
{
path: 'upload',
component: UploaderComponent,
},
{
path: '',
redirectTo: '/upload',
pathMatch: 'full'
}];
And here is my ResultComponent.ts :
ngOnInit() {
this.getParsedDocumentData('for-debug-file-name');
}
getParsedDocumentData(fileName: string): Observable<string[]> {
let params = new URLSearchParams();
params.append('filename', fileName);
let options = new RequestOptions({ params: params });
return this.http.get('http://localhost:8080/api/result/', options)
.map(res => res.json())
.catch(this.handleError);
}
private handleError (error: any) {
return Observable.throw(error);
}
I would really appreciate your help as I have been stuck for 4 hours.
Thanks.
query method in request object does not exists. Instead use query property to access filename parameter.
console.log(req.query.filename);
Reference