Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion apps/api/src/food-search.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import test from "node:test";
import { pickRecentFoods, pickSearchResults } from "./food-search.js";
import { catalogWhere, pickRecentFoods, pickSearchResults } from "./food-search.js";

const food = (id: string, barcode: string, name: string, ownerId: string, verified = false) => ({
id,
Expand Down Expand Up @@ -61,3 +61,25 @@ test("recent foods are distinct, newest first, with the last amount", () => {
{ foodId: "b", lastAmount: 30 },
]);
});

test("catalog lists every published food, or only the viewer's own", () => {
assert.deepEqual(catalogWhere("me", { scope: "published", status: "all", q: "" }), {
AND: [{ published: true }],
});
assert.deepEqual(catalogWhere("me", { scope: "mine", status: "all", q: "" }), {
AND: [{ ownerId: "me" }],
});
});

test("catalog narrows by verification status and search text", () => {
assert.deepEqual(catalogWhere("me", { scope: "published", status: "unverified", q: "milk" }), {
AND: [
{ published: true },
{ verifiedAt: null },
{ OR: [{ name: { contains: "milk" } }, { barcode: { startsWith: "milk" } }] },
],
});
assert.deepEqual(catalogWhere("me", { scope: "published", status: "verified", q: "" }).AND[1], {
verifiedAt: { not: null },
});
});
21 changes: 21 additions & 0 deletions apps/api/src/food-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,24 @@ export function pickRecentFoods(
}
return result;
}

export const CATALOG_PAGE_SIZE = 30;

export type CatalogScope = "published" | "mine";
export type CatalogStatus = "all" | "verified" | "unverified";

/**
* Filter for the food catalog: every published food, or only the viewer's own
* foods (published or not). Optionally narrowed to verified/unverified and to
* a name or barcode search.
*/
export function catalogWhere(
viewerId: string,
{ scope, status, q }: { scope: CatalogScope; status: CatalogStatus; q: string },
) {
const and: object[] = [scope === "mine" ? { ownerId: viewerId } : { published: true }];
if (status === "verified") and.push({ verifiedAt: { not: null } });
if (status === "unverified") and.push({ verifiedAt: null });
if (q) and.push({ OR: [{ name: { contains: q } }, { barcode: { startsWith: q } }] });
return { AND: and };
}
24 changes: 22 additions & 2 deletions apps/api/src/routes/foods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { currentUser, requireUser } from "../auth.js";
import { prisma } from "../db.js";
import { resolveBarcode } from "../domain.js";
import { asyncRoute, HttpError } from "../http.js";
import { pickRecentFoods, pickSearchResults } from "../food-search.js";
import { CATALOG_PAGE_SIZE, catalogWhere, pickRecentFoods, pickSearchResults } from "../food-search.js";
import { num, presentFood } from "../present.js";
import { requirePermission } from "../rbac.js";
import { foodSchema, foodUpdateSchema } from "../schemas.js";
import { catalogQuerySchema, foodSchema, foodUpdateSchema } from "../schemas.js";

export const foodsRouter = Router();
foodsRouter.use(requireUser);
Expand Down Expand Up @@ -35,6 +35,26 @@ foodsRouter.get(
}),
);

// Must stay above "/:id" or "catalog" would be read as a food id.
foodsRouter.get(
"/catalog",
requirePermission("foods:read"),
asyncRoute(async (req, res) => {
const user = currentUser(res);
const query = catalogQuerySchema.parse(req.query);
const rows = await prisma.food.findMany({
where: catalogWhere(user.id, query),
orderBy: [{ name: "asc" }, { id: "asc" }],
skip: query.offset,
take: CATALOG_PAGE_SIZE + 1,
});
res.json({
foods: rows.slice(0, CATALOG_PAGE_SIZE).map((food) => presentFood(food, user.id)),
hasMore: rows.length > CATALOG_PAGE_SIZE,
});
}),
);

foodsRouter.get(
"/recent",
requirePermission("foods:read"),
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,10 @@ export const fastingSchema = z.object({
.max(3)
.default([0]),
});

export const catalogQuerySchema = z.object({
scope: z.enum(["published", "mine"]).default("published"),
status: z.enum(["all", "verified", "unverified"]).default("all"),
q: z.string().trim().max(64).default(""),
offset: z.coerce.number().int().min(0).max(100000).default(0),
});
2 changes: 2 additions & 0 deletions apps/mobile/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ analyzer:
exclude:
- build/**
- ios/**
# Vendored plugin (patched iOS code); not ours to lint.
- packages/**

linter:
# The lint rules applied to this project can be customized in the
Expand Down
Binary file added apps/mobile/assets/tessdata/eng.traineddata
Binary file not shown.
Binary file added apps/mobile/assets/tessdata/heb.traineddata
Binary file not shown.
6 changes: 6 additions & 0 deletions apps/mobile/assets/tessdata_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"files": [
"eng.traineddata",
"heb.traineddata"
]
}
1 change: 1 addition & 0 deletions apps/mobile/ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
1 change: 1 addition & 0 deletions apps/mobile/ios/Flutter/Release.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
43 changes: 43 additions & 0 deletions apps/mobile/ios/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '15.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}

def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end

File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
use_frameworks!

flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end

post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
29 changes: 29 additions & 0 deletions apps/mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
PODS:
- Flutter (1.0.0)
- flutter_tesseract_ocr (0.3.4):
- Flutter
- SwiftyTesseract
- SwiftyTesseract (3.1.3)

DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_tesseract_ocr (from `.symlinks/plugins/flutter_tesseract_ocr/ios`)

SPEC REPOS:
trunk:
- SwiftyTesseract

EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_tesseract_ocr:
:path: ".symlinks/plugins/flutter_tesseract_ocr/ios"

SPEC CHECKSUMS:
Flutter: 71a624a5bc0c04062bf19101d501e466baf2fb47
flutter_tesseract_ocr: fffe5de044967e12d184feaded7fabfb546b6042
SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb

PODFILE CHECKSUM: 3ba3a73b1b12adcf321a227a745625fe0888cc82

COCOAPODS: 1.17.0
Loading
Loading