Firebase startAfter is not working for doc ref - node.js

Docs I am following: https://firebase.google.com/docs/firestore/query-data/query-cursors
I have code like below;
async List(query: any): Promise<Array<any>> {
let collectionQuery = super.GetCollectionReference();
if (query.VideoChannelId) {
collectionQuery = collectionQuery.where(
"VideoChannel.Id",
"==",
query.VideoChannelId
);
}
let startAfterDoc: any = "";
if (query.StartAfter) {
startAfterDoc = await super.GetDocumentReference(query.StartAfter);
}
collectionQuery = collectionQuery
.orderBy(query.OrderBy, "desc")
.startAfter(startAfterDoc)
.limit(query.Limit);
let items = await super.List(collectionQuery);
return items;
}
And utility methods:
GetDocumentReference(id: string): any {
return this.GetCollectionReference().doc(id);
}
async GetDocumentSnapshot(id: string): Promise<any> {
return await this.GetDocumentReference(id).get();
}
GetCollectionReference(): any {
return this.db.collection(this.CollectionName);
}
Regardless what ever value I pass for query.StartAfter it always returns top document in the collection.
I am pretty sure document exists with id query.StartAfter.
If I use GetDocumentSnapshot instead of GetCollectionReference, then I am getting parse error at firebase API.
Indexes has been added for query.OrderBy (CreateDate) and Id fields.
What possibly I would be missing here?

Related

IPageRetriever not working with Ajax calls

I have the following API call to retrieve page data
List<VillageNewsItem> newsList = pageRetriever.RetrieveAsync<VillageNewsItem>(
query => query
.Path("/Home/Village-News", PathTypeEnum.Children)
.Published(true)
.OnSite(SiteContext.CurrentSiteName)
.OrderByDescending(x => x.DocumentCreatedWhen)
)?.Result?.ToList();
It works fine and return 2 records if I run the query on page load. Inside Index action of the controller.
public VillageNewsListController(IPageDataContextRetriever dataRetriever, VillageNewsListRepository villageNewsListRepository,
IPageRetriever pagesRetriever, IPageDataContextRetriever pageDataContextRetriever, IPageUrlRetriever pageUrlRetriever)
{
this._dataRetriever = dataRetriever;
this._villageNewsListRepository = villageNewsListRepository;
this._pagesRetriever = pagesRetriever;
this.pageDataContextRetriever = pageDataContextRetriever;
this.pageUrlRetriever = pageUrlRetriever;
}
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
try
{
List<VillageNewsItem> newsList = pagesRetriever.RetrieveAsync<VillageNewsItem>(
query => query
.Path("/Home/Village-News", PathTypeEnum.Children)
.Published(true)
.OnSite(SiteContext.CurrentSiteName)
.OrderByDescending(x => x.DocumentCreatedWhen)
)?.Result?.ToList();
newsItems.VillageNewsItems = newsList;
return View(newsItems);
}
catch (Exception ex)
{
ErrorHandler.EventLog.LogError(ex.Source, ex.Message, ex.StackTrace);
return RedirectToAction("ErrorPage", "Error");
}
}
However, if I try to make the same API call via a client side AJAX call, it doesn't work and return 0 records. Why it's not working with Ajax calls?
Ajax call
function loadMoreNews() {
$.ajax({
url: '/VillageNewsList/VillageNewsItemList',
//data: { "term": request.term },
type: "POST",
success: function (data) {
response($.map(data,
function (item) {
console.log(data);
}));
},
error: function (response) {
//alert(response.responseText);
},
failure: function (response) {
// alert(response.responseText);
}
});
}
Server side method.
[HttpPost]
[Route("VillageNewsList/VillageNewsItemList")]
public VillageNewsListViewModel VillageNewsItemList(string NodeAliasPath = "", int villageId = 0, string state = "", int page = 1, int pageSize = 4)
{
try
{
List<VillageNewsItem> newsList = pagesRetriever.RetrieveAsync<VillageNewsItem>(
query => query
.Path("/Home/Village-News", PathTypeEnum.Children)
.Published(true)
.OnSite(SiteContext.CurrentSiteName)
.OrderByDescending(x => x.DocumentCreatedWhen)
)?.Result?.ToList();
var model = new VillageNewsListViewModel
{
VillageNewsItems = newsList, // returns 0 records
};
return model;
}
catch (Exception ex)
{
ErrorHandler.EventLog.LogError(ex.Source, ex.Message, ex.StackTrace);
//return RedirectToAction("ErrorPage", "Error");
}
return null;
}
Couple things I see.
You're calling IPageRetriever.RetrieveAsync, but you aren't putting an await before it. There may be some odd behavior due to this. Get rid of the ?.Result?.ToList() and instead just put await before it, it will return an IEnumerable of the specified type.
You don't need ".Published" nor "OnSite" with IPageRetriever, this API automatically uses the Current Site Context, the current culture, and either Published or not / Latest Version or not based on if it's in edit/preview mode or not.
See if those things fix the issue!
I also asume it is caused by async context here...
You can try to use a document query instead.
Would be something like this:
var items = new DocumentQuery<VillageNewsItem>(
.Path("/Home/Village-News", PathTypeEnum.Children)
.PublishedVersion()
.Published()
.OnCurrentSite()
.OrderByDescending(x => x.DocumentCreatedWhen))
?.Result
?.ToList();
If you have multiple cultures, add the culture to your query, too.
.Culture(LocalizationContext.CurrentCulture.CultureCode)

How to pass default parameters to the #Query class in Nest.Js?

I'm trying to pass the default parameters maxnodes=3 and addstats=false to the controller via the #Query parameter in Nest.Js.
The code works fine, but the default parameters are not used. When I pass on the query parameters the ones that are passed are shown, but if none are passed, the default values (3 and false) are not used.
How to fix that?
context.contructor.ts:
import { CreateContextQuery } from './context.query';
import { CreateContextDto } from './context.dto';
#Post('graph')
public async createGraphForContext(
#Body('context') contextData: CreateContextDto,
#Query()
contextQuery: CreateContextQuery,
) {
const before = Date.now();
const { context } = await this.contextService.createContext(contextData);
const graph = await this.contextService.getGraphOfContext(
context.id,
contextQuery.maxnodes,
contextQuery.addstats,
);
}
context.query.ts:
import { ApiProperty } from '#nestjs/swagger';
export class CreateContextQuery {
#ApiProperty({
description: 'Maximum number of nodes to show on the graph',
})
maxnodes;
#ApiProperty({
description: 'Include graph statistics',
})
addstats;
constructor(maxnodes = 3, addstats = false) {
this.maxnodes = maxnodes;
this.addstats = addstats;
}
}
So basically in your DTO, you can give default values.
export class CreateContextQuery {
#IsOptional()
#Type(() => Number)
#IsNumber()
#Min(0)
maxnodes?: number = 3;
#IsOptional()
#Type(() => Boolean)
#IsBoolean()
addstats?: boolean = false;
constructor(maxnodes = 3, addstats = false) {
this.maxnodes = maxnodes;
this.addstats = addstats;
}
}
// as you can see i am using validation too
And in your controller :
#Post('graph')
#UsePipes(new ValidationPipe({ transform: true }))
// you need to add this for tansformation
public async createGraphForContext(
#Body('context') contextData: CreateContextDto,
#Query()
contextQuery: CreateContextQuery,
) {
const before = Date.now();
const { context } = await this.contextService.createContext(contextData);
const graph = await this.contextService.getGraphOfContext(
context.id,
contextQuery.maxnodes,
contextQuery.addstats,
);
}
PS
Also if you want you can add custom decorators, in your case:
// add this decorator
export const GetContextQuery = createParamDecorator((_data: unknown, ctx: ExecutionContext): CreateContextDto => {
const request = ctx.switchToHttp().getRequest();
const query = request.query;
const maxnodes = parseInt(query.maxnodes) || 3;//default values here in case it fails to parse
const addstats = Boolean(query.addstats) || 0;
return { addstats, addstats };
});
and in your controller, you can call the decorator instead of #Query
just add your decorator #GetContextQuery() context: CreateContextDto, and now you do not need the UsePipes
What you receive in the query param is a plain object. You can achieve what you want putting a pipe in your query param and applying a class transform to instantiate the class.
Read this: https://docs.nestjs.com/pipes#providing-defaults
contextQuery isn't an instance of CreateContextQuery because, without any configuration, Nest won't call new CreateContextQuery any time. This is why you end up using pipes (read this https://docs.nestjs.com/techniques/validation#transform-payload-objects)

Return String from Future Object

I am trying to fetch html page via localproxy to parse and get urls from it. I can't find any library which works without Future in Dart. So i have difficulty returning String from a Future Object. Below is full code of dart file.
import 'package:universal_html/driver.dart';
import 'package:universal_html/prefer_universal/html.dart';
String proxyUrl='http://localhost/php-proxy/index.php?q=';
String hallTicketUrl='http://www.ignou.ac.in/ignou/studentzone/results/6';
Future<String> getList()async{
final driver = HtmlDriver();
await driver.setDocumentFromUri(Uri.parse(proxyUrl+hallTicketUrl));
final items = driver.document.querySelectorAll('.middleconten2column a');
Element urls=Element.tag('urls');
items.forEach((item)=>urls.append(Element.tag('url')
..setAttribute('href',item.getAttribute('href'))
..text=item.text
)
);
print('${items.length} items found');
return Future.value(urls.outerHtml);
}
String Handler(String app){
switch(app){
case 'list': return getList() as String;
}
return "";
}
main(){
print(Handler('list'));
}
I think you have misunderstood what the async keyword does to methods. When a method are marked as async it will always automatically returns a Future of something. That is the reason why you need to specify e.g. Future<String> as the return type.
But because the creation are done "automatically" you don't really need to do the following:
return Future.value(urls.outerHtml);
But can just do:
return urls.outerHtml;
After this your have discovered the problem with using asynchronous programming where everything in your call stack needs to be marked as async since you have an await somewhere in your code.
I have fixed your code so it now hope it works correctly with all the future stuff:
import 'package:universal_html/driver.dart';
import 'package:universal_html/prefer_universal/html.dart';
String proxyUrl = 'http://localhost/php-proxy/index.php?q=';
String hallTicketUrl = 'http://www.ignou.ac.in/ignou/studentzone/results/6';
Future<String> getList() async {
final driver = HtmlDriver();
await driver.setDocumentFromUri(Uri.parse(proxyUrl + hallTicketUrl));
final items = driver.document.querySelectorAll('.middleconten2column a');
Element urls = Element.tag('urls');
items.forEach((item) => urls.append(Element.tag('url')
..setAttribute('href', item.getAttribute('href'))
..text = item.text));
print('${items.length} items found');
return urls.outerHtml;
}
Future<String> Handler(String app) async {
switch (app) {
case 'list':
return await getList();
}
return "";
}
main() async {
print(await Handler('list'));
}

Symbol to string with JSON.stringify

I need to convert a Symbol to string in order to create a unique key in Redis, but I can't.
I've already tried to use Object.toString(obj) and String(obj) but I get errors or [Object] resultsĀ”.
This is the controller
const name = req.params.name;
let obj;
obj.data.name = {
[Op.like]: '%' + name + '%'
};
}
This is redis controller where I use stringify. I use obj as a parameter.
const hashed = crypto.createHmac('sha256', secretHashKey)
.update(JSON.stringify(obj))
.digest('hex');
I expect an output based on my parameter 'obj' but now it's not getting it so I can't create unique keys for different values.
Maybe a little bit too late, but I hope that somebody else find this useful.
I was looking for something exactly as you: use with Sequelize in a Redis cache.
Mine is TypeScript, convert to JavaScript just by removing the typings.
export function JsonStringifyWithSymbols(object: any, clean?: boolean): string {
return JSON.stringify(object, (_, value) => {
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
const props = [...Object.getOwnPropertyNames(value), ...Object.getOwnPropertySymbols(value)];
const replacement: Record<string, any> = {};
for (const k of props) {
if (typeof k === 'symbol') {
replacement[`Symbol:${Symbol.keyFor(k)}`] = value[k];
} else {
replacement[k] = value[k];
}
}
return replacement;
}
return value;
});
}
If you're meaning these Symbols you can't convert them to a string.
They're created to be unique and "unreversable", so you can use them also for keep more "secure" various properties or methods. Example:
const a = Symbol('a')
class Foobar {
constructor (_a) {
this[a] = _a
}
}
const foobar = new Foobar('aaa')
console.log(foobar) // output: Foobar { [Symbol(a)]: 'aaa' }
const fake = Symbol('a')
foobar[fake] = 'fake'
console.log(foobar) // output: Foobar { [Symbol(a)]: 'aaa', [Symbol(a)]: 'fake' }
You can't corrupt the original one, unless you have the original Symbol.
Another example (info about the JSON.stringify here):
const a = Symbol('a')
const foobar = {}
foobar[a] = 'aaa'
console.log(foobar) // output: { [Symbol(a)]: 'aaa' }
console.log(JSON.stringify(foobar)) // output: {}
const fake = Symbol('a')
foobar[fake] = 'fake'
console.log(foobar) // output: { [Symbol(a)]: 'aaa', [Symbol(a)]: 'fake' }
Hope these info will help you.

how can i assign query result of sequelize to a property of a object

I defined a object A in which i need to store some objects.
How can i assign the query result to the property of the object.
Here is my example code.
function A(Job){
this.job = Job;
this.location ={};
models.locations.findOne({
where:{
job_id:this.job.id
}
}).then(function(location){
//what should i do here to store it in A.locaiton
}
}
Hold the reference of this into a that property (or any other name you prefer) and then use that.location. For example:
function A(Job) {
var that = this;
that.job = Job;
that.location = {};
models.locations.findOne({
where: {
job_id: this.job.id
}
}).then(function (location) {
that.location = location;
});
}
Hope this helps.

Resources