Local API: Add /fulltext endpoints (#5004)

This commit is contained in:
Abe Jellinek 2025-01-28 00:58:12 -05:00 • committed by GitHub
parent 6cdae37e5b
commit a7fff8cada
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 1 deletions

View file

@ -741,6 +741,71 @@ Zotero.Server.Endpoints["/api/users/:userID/items/:itemKey/file/view/url"] = Zot
Zotero.Server.Endpoints["/api/groups/:groupID/items/:itemKey/file/view/url"] = Zotero.Server.LocalAPI.ItemFile;
Zotero.Server.LocalAPI.ItemFullText = class extends LocalAPIEndpoint {
supportedMethods = ['GET'];
async run({ pathParams, libraryID }) {
let item = await Zotero.Items.getByLibraryAndKeyAsync(libraryID, pathParams.itemKey);
if (!item || !item.isFileAttachment() || !Zotero.Fulltext.isCachedMIMEType(item.attachmentContentType)) {
return _404;
}
let file = Zotero.Fulltext.getItemCacheFile(item);
if (!file.exists()) {
return _404;
}
let { indexedPages, totalPages, indexedChars, totalChars, version } = await Zotero.DB.rowQueryAsync(
"SELECT indexedPages, totalPages, indexedChars, totalChars, version FROM fulltextItems WHERE itemID=?",
item.id
);
return [
200,
{
'Content-Type': 'application/json',
'Last-Modified-Version': version,
},
JSON.stringify(
{
content: await Zotero.File.getContentsAsync(file),
indexedPages: indexedPages ?? undefined,
totalPages: totalPages ?? undefined,
indexedChars: indexedChars ?? undefined,
totalChars: totalChars ?? undefined,
},
null,
4
)
];
}
};
Zotero.Server.Endpoints["/api/users/:userID/items/:itemKey/fulltext"] = Zotero.Server.LocalAPI.ItemFullText;
Zotero.Server.Endpoints["/api/groups/:groupID/items/:itemKey/fulltext"] = Zotero.Server.LocalAPI.ItemFullText;
Zotero.Server.LocalAPI.FullText = class extends LocalAPIEndpoint {
supportedMethods = ['GET'];
async run({ searchParams, libraryID }) {
let since = parseInt(searchParams.get('since'));
if (Number.isNaN(since)) {
return [400, 'text/plain', `Invalid 'since' value '${searchParams.get('since')}'`];
}
let rows = await Zotero.DB.queryAsync(
"SELECT I.key, FI.version "
+ "FROM fulltextItems FI JOIN items I USING (itemID) "
+ "WHERE libraryID=? AND FI.version>?",
[libraryID, since]
);
let obj = {};
for (let row of rows) {
obj[row.key] = row.version;
}
return [200, 'application/json', JSON.stringify(obj, null, 4)];
}
};
Zotero.Server.Endpoints["/api/users/:userID/fulltext"] = Zotero.Server.LocalAPI.FullText;
Zotero.Server.Endpoints["/api/groups/:groupID/fulltext"] = Zotero.Server.LocalAPI.FullText;
Zotero.Server.LocalAPI.Searches = class extends LocalAPIEndpoint {
supportedMethods = ['GET'];

View file

@ -142,6 +142,25 @@ describe("Local API Server", function () {
{ responseType: 'text', followRedirects: false });
assert.isTrue(request.getResponseHeader('Location').startsWith('file:'));
});
it("should return full-text data from /fulltext", async function () {
let { response } = await apiGet(`/users/0/items/${subcollectionAttachment.key}/fulltext`);
assert.deepEqual(response, {
content: 'Zotero [zoh-TAIR-oh] is a free, easy-to-use tool to help you collect, organize, cite, and share your research sources.',
indexedPages: 1,
totalPages: 1,
});
});
it("should return a 404 from /fulltext when attachment has no full-text content", async function () {
let tempAttachment = await importFileAttachment('empty.pdf');
let { response } = await apiGet(`/users/0/items/${tempAttachment.key}/fulltext`, {
successCodes: [404],
responseType: 'text'
});
assert.equal(response, 'Not found');
await tempAttachment.eraseTx();
});
});
describe("?itemType", function () {
@ -280,7 +299,14 @@ describe("Local API Server", function () {
assert.equal(response[0].meta.numItems, 1);
});
});
describe("<userOrGroupPrefix>/fulltext?since=", function () {
it("should return items with full-text content", async function () {
let { response } = await apiGet('/users/0/fulltext?since=0');
assert.deepEqual(Object.entries(response), [[subcollectionAttachment.key, subcollectionAttachment.version]]);
});
});
describe("/groups/<groupID>", function () {
it("should return 404 for unknown group", async function () {
let { response } = await apiGet(