Skip to content

Commit

Permalink
improve search API, add orderBy and orderDirection parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
sdumetz committed Feb 15, 2024
1 parent 340a284 commit 5bbd970
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 6 deletions.
11 changes: 8 additions & 3 deletions source/server/routes/api/v1/scenes/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export default async function getScenes(req :Request, res :Response){
match,
access,
limit,
offset
offset,
orderBy,
orderDirection,
} = req.query;

access = ((Array.isArray(access))?access : (access?[access]:undefined)) as any;
Expand Down Expand Up @@ -51,14 +53,17 @@ export default async function getScenes(req :Request, res :Response){
scenes = await Promise.all(scenesList.map(name=>vfs.getScene(name)));
}else{
/**@fixme ugly hach to bypass permissions when not performing a search */
scenes = await vfs.getScenes((u.isAdministrator && !access && !match)?undefined: u.uid, {
const requester_id = (u.isAdministrator && !access && !match)?undefined: u.uid;
scenes = await vfs.getScenes(requester_id, {
match: match as string,
orderBy: orderBy as any,
orderDirection: orderDirection as any,
access: access as AccessType[],
limit: limit? parseInt(limit as string): undefined,
offset: offset? parseInt(offset as string): undefined,
});
}

await (await import("node:timers/promises")).setTimeout(1000);
//canonicalize scenes' thumb names
scenes = scenes.map(s=>({...s, thumb: (s.thumb? new URL(encodeURI(path.join("/scenes/", s.name, s.thumb)), getHost(req)).toString() : undefined)}))

Expand Down
18 changes: 15 additions & 3 deletions source/server/vfs/Scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import BaseVfs from "./Base.js";
import { ItemEntry, Scene, SceneQuery } from "./types.js";



export default abstract class ScenesVfs extends BaseVfs{

async createScene(name :string):Promise<number>
Expand Down Expand Up @@ -87,14 +86,23 @@ export default abstract class ScenesVfs extends BaseVfs{
* get all scenes when called without params
* Search scenes with structured queries when called with filters
*/
async getScenes(user_id ?:number, {access, match, limit =10, offset = 0} :SceneQuery = {}) :Promise<Scene[]>{
async getScenes(user_id ?:number, {access, match, limit =10, offset = 0, orderBy="name", orderDirection="asc"} :SceneQuery = {}) :Promise<Scene[]>{

//Check various parameters compliance
if(Array.isArray(access) && access.find(a=>AccessTypes.indexOf(a) === -1)){
throw new BadRequestError(`Bad access type requested : ${access.join(", ")}`);
}

if(typeof limit !="number" || Number.isNaN(limit) || limit < 0) throw new BadRequestError(`When provided, limit must be a number`);
if(typeof offset != "number" || Number.isNaN(offset) || offset < 0) throw new BadRequestError(`When provided, offset must be a number`);

if(["asc", "desc"].indexOf(orderDirection.toLowerCase()) === -1) throw new BadRequestError(`Invalid orderDirection: ${orderDirection}`);
if(["ctime", "mtime", "name"].indexOf(orderBy.toLowerCase()) === -1) throw new BadRequestError(`Invalid orderBy: ${orderBy}`);

let with_filter = typeof user_id === "number" || match;

const sortString = (orderBy == "name")? "LOWER(scene_name)": orderBy;

let likeness = "";
let mParams :Record<string, string> = {};

Expand Down Expand Up @@ -155,6 +163,7 @@ export default abstract class ScenesVfs extends BaseVfs{
last_docs.fk_scene_id = documents.fk_scene_id
AND last_docs.generation = documents.generation
)
SELECT
IFNULL(mtime, scenes.ctime) as mtime,
scenes.ctime AS ctime,
Expand All @@ -172,9 +181,11 @@ export default abstract class ScenesVfs extends BaseVfs{
"any", json_extract(scenes.access, '$.1'),
"default", json_extract(scenes.access, '$.0')
) AS access
FROM scenes
LEFT JOIN last_docs AS document ON fk_scene_id = scene_id
LEFT JOIN json_tree(document.metas) AS thumb ON thumb.fullkey LIKE "$[_].images[_]" AND json_extract(thumb.value, '$.quality') = 'Thumb'
${with_filter? "WHERE true": ""}
${typeof user_id === "number"? `AND
COALESCE(
Expand All @@ -185,8 +196,9 @@ export default abstract class ScenesVfs extends BaseVfs{
`:""}
${(access?.length)? `AND json_extract(scenes.access, '$.' || $user_id) IN (${ access.map(s=>`'${s}'`).join(", ") })`:""}
${likeness}
GROUP BY scene_id
ORDER BY LOWER(scene_name) ASC
ORDER BY ${sortString} ${orderDirection.toUpperCase()}
LIMIT $offset, $limit
`, {
...mParams,
Expand Down
2 changes: 2 additions & 0 deletions source/server/vfs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,6 @@ export interface SceneQuery {
match ?:string;
offset ?:number;
limit ?:number;
orderBy ?:"ctime"|"mtime"|"name";
orderDirection ?:"asc"|"desc";
}
16 changes: 16 additions & 0 deletions source/server/vfs/vfs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,22 @@ describe("Vfs", function(){

});

describe("ordering", function(){
it("rejects bad orderBy key", async function(){
await expect(vfs.getScenes(0, {orderBy: "bad" as any})).to.be.rejectedWith("Invalid orderBy: bad");
})
it("rejects bad orderDirection key", async function(){
await expect(vfs.getScenes(0, {orderDirection: "bad" as any})).to.be.rejectedWith("Invalid orderDirection: bad");
});
it("can order by name descending", async function(){
for(let i = 0; i < 10; i++){
await vfs.createScene(`${i}_scene`);
}
const scenes = await vfs.getScenes(0, {orderBy: "name", orderDirection: "desc"});
expect(scenes.map(s=>s.name)).to.deep.equal([9,8,7,6,5,4,3,2,1,0].map(n=>n+"_scene"));
});
});

describe("pagination", function(){
it("rejects bad LIMIT", async function(){
let fixtures = [-1, "10", null];
Expand Down

0 comments on commit 5bbd970

Please sign in to comment.