@base44-preview/cli 0.0.50-pr.477.43f5b00 → 0.0.50-pr.481.773d5d4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -219926,7 +219926,8 @@ var theme = {
219926
219926
  bold: source_default.bold,
219927
219927
  dim: source_default.dim,
219928
219928
  error: source_default.red,
219929
- warn: source_default.yellow
219929
+ warn: source_default.yellow,
219930
+ info: source_default.cyan
219930
219931
  },
219931
219932
  format: {
219932
219933
  errorContext(ctx) {
@@ -235041,8 +235042,8 @@ var TOKEN_REFRESH_BUFFER_MS = 60 * 1000;
235041
235042
  var refreshPromise = null;
235042
235043
  async function readAuth() {
235043
235044
  try {
235044
- const parsed = await readJsonFile(getAuthFilePath());
235045
- const result = AuthDataSchema.safeParse(parsed);
235045
+ const authData = await readJsonFile(getAuthFilePath());
235046
+ const result = AuthDataSchema.safeParse(authData);
235046
235047
  if (!result.success) {
235047
235048
  throw new SchemaValidationError("Invalid authentication data", result.error, getAuthFilePath());
235048
235049
  }
@@ -243210,10 +243211,12 @@ var package_default = {
243210
243211
  "@types/ejs": "^3.1.5",
243211
243212
  "@types/express": "^5.0.6",
243212
243213
  "@types/json-schema": "^7.0.15",
243214
+ "@types/jsonwebtoken": "^9.0.10",
243213
243215
  "@types/lodash": "^4.17.24",
243214
243216
  "@types/multer": "^2.0.0",
243215
243217
  "@types/node": "^22.10.5",
243216
243218
  "@vercel/detect-agent": "^1.1.0",
243219
+ outdent: "^0.8.0",
243217
243220
  chalk: "^5.6.2",
243218
243221
  chokidar: "^5.0.0",
243219
243222
  commander: "^12.1.0",
@@ -243228,6 +243231,7 @@ var package_default = {
243228
243231
  globby: "^16.1.0",
243229
243232
  "http-proxy-middleware": "^3.0.5",
243230
243233
  "json-schema-to-typescript": "^15.0.4",
243234
+ jsonwebtoken: "^9.0.3",
243231
243235
  json5: "^2.2.3",
243232
243236
  ky: "^1.14.2",
243233
243237
  lodash: "^4.17.23",
@@ -243243,6 +243247,7 @@ var package_default = {
243243
243247
  typescript: "^5.7.2",
243244
243248
  vitest: "^4.0.16",
243245
243249
  yaml: "^2.8.2",
243250
+ qs: "^6.12.3",
243246
243251
  zod: "^4.3.5"
243247
243252
  },
243248
243253
  engines: {
@@ -252755,9 +252760,12 @@ function getTypesCommand() {
252755
252760
  return new Command("types").description("Manage TypeScript type generation").addCommand(getTypesGenerateCommand());
252756
252761
  }
252757
252762
 
252763
+ // src/cli/commands/dev.ts
252764
+ import process21 from "node:process";
252765
+
252758
252766
  // src/cli/dev/dev-server/main.ts
252759
252767
  var import_cors = __toESM(require_lib4(), 1);
252760
- var import_express5 = __toESM(require_express(), 1);
252768
+ var import_express6 = __toESM(require_express(), 1);
252761
252769
  import { dirname as dirname16, join as join23 } from "node:path";
252762
252770
 
252763
252771
  // ../../node_modules/get-port/index.js
@@ -252931,6 +252939,7 @@ function createDevLogger() {
252931
252939
 
252932
252940
  // src/cli/dev/dev-server/function-manager.ts
252933
252941
  import { spawn as spawn2 } from "node:child_process";
252942
+ import { pathToFileURL } from "node:url";
252934
252943
  var READY_TIMEOUT = 30000;
252935
252944
 
252936
252945
  class FunctionManager {
@@ -253010,7 +253019,7 @@ class FunctionManager {
253010
253019
  const process21 = spawn2("deno", ["run", "--allow-all", this.wrapperPath], {
253011
253020
  env: {
253012
253021
  ...globalThis.process.env,
253013
- FUNCTION_PATH: func.entryPath,
253022
+ FUNCTION_PATH: pathToFileURL(func.entryPath).href,
253014
253023
  FUNCTION_PORT: String(port),
253015
253024
  FUNCTION_NAME: func.name
253016
253025
  },
@@ -253089,9 +253098,13 @@ function createFunctionRouter(manager, logger2) {
253089
253098
  on: {
253090
253099
  proxyReq: (proxyReq, req) => {
253091
253100
  const xAppId = req.headers["x-app-id"];
253101
+ const authorization = req.headers.authorization;
253092
253102
  if (xAppId) {
253093
253103
  proxyReq.setHeader("Base44-App-Id", xAppId);
253094
253104
  }
253105
+ if (authorization) {
253106
+ proxyReq.setHeader("Base44-Service-Authorization", authorization);
253107
+ }
253095
253108
  proxyReq.setHeader("Base44-Api-Url", `${req.protocol}://${req.headers.host}`);
253096
253109
  },
253097
253110
  error: (err, _req, res) => {
@@ -253316,7 +253329,9 @@ class Validator {
253316
253329
  }
253317
253330
 
253318
253331
  // src/cli/dev/dev-server/db/database.ts
253332
+ var PRIVATE_COLLECTION_PREFIX = "$";
253319
253333
  var USER_COLLECTION = "user";
253334
+ var PRIVATE_USER_COLLECTION = PRIVATE_COLLECTION_PREFIX + USER_COLLECTION;
253320
253335
 
253321
253336
  class Database {
253322
253337
  collections = new Map;
@@ -253338,6 +253353,7 @@ class Database {
253338
253353
  this.schemas.set(USER_COLLECTION, this.buildUserSchema(userEntity));
253339
253354
  const collection = new import_nedb.default;
253340
253355
  this.collections.set(USER_COLLECTION, collection);
253356
+ this.collections.set(PRIVATE_USER_COLLECTION, new import_nedb.default);
253341
253357
  const userInfo = await readAuth();
253342
253358
  const now = getNowISOTimestamp();
253343
253359
  await collection.insertAsync({
@@ -253378,8 +253394,13 @@ class Database {
253378
253394
  getCollection(name2) {
253379
253395
  return this.collections.get(this.normalizeName(name2));
253380
253396
  }
253397
+ getSchema(entityName) {
253398
+ return this.schemas.get(this.normalizeName(entityName));
253399
+ }
253381
253400
  getCollectionNames() {
253382
- return Array.from(this.collections.keys());
253401
+ return Array.from(this.collections.keys()).filter((name2) => {
253402
+ return !name2.startsWith(PRIVATE_COLLECTION_PREFIX);
253403
+ });
253383
253404
  }
253384
253405
  dropAll() {
253385
253406
  for (const collection of this.collections.values()) {
@@ -253443,15 +253464,339 @@ function broadcastEntityEvent(io6, appId, entityName, event) {
253443
253464
  });
253444
253465
  }
253445
253466
 
253446
- // src/cli/dev/dev-server/routes/entities/entities-router.ts
253447
- var import_express3 = __toESM(require_express(), 1);
253448
-
253449
- // src/cli/dev/dev-server/routes/entities/entities-user-router.ts
253467
+ // src/cli/dev/dev-server/routes/auth-router.ts
253450
253468
  var import_express2 = __toESM(require_express(), 1);
253451
253469
  var import_jsonwebtoken = __toESM(require_jsonwebtoken(), 1);
253452
- function createUserRouter(db2, logger2) {
253470
+ import { randomInt } from "node:crypto";
253471
+ var LOCAL_DEV_SECRET = "LOCAL_DEV_SECRET";
253472
+ var generateCode = () => {
253473
+ return randomInt(1e5, 1e6).toString();
253474
+ };
253475
+ var createJwtToken = (email3) => {
253476
+ return import_jsonwebtoken.default.sign({ sub: email3 }, LOCAL_DEV_SECRET, {
253477
+ expiresIn: "360d"
253478
+ });
253479
+ };
253480
+ var LoginBody = object({ email: email2(), password: string2() });
253481
+ var VerifyOtpBody = object({ email: email2(), otp_code: string2() });
253482
+ function createAuthRouter(db2, logger2) {
253453
253483
  const router = import_express2.Router({ mergeParams: true });
253454
253484
  const parseBody = import_express2.json();
253485
+ router.post("/login", parseBody, async (req, res) => {
253486
+ const { email: email3, password } = LoginBody.parse(req.body);
253487
+ const result = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: email3 });
253488
+ if (result) {
253489
+ const privateUserData = await db2.getCollection(PRIVATE_USER_COLLECTION)?.findOneAsync({ email: email3 });
253490
+ if (result.role === "admin" || privateUserData?.password === password) {
253491
+ res.json({
253492
+ access_token: createJwtToken(email3),
253493
+ success: true,
253494
+ user: {}
253495
+ });
253496
+ } else {
253497
+ res.status(400).json({
253498
+ detail: "Invalid email or password",
253499
+ error_type: "HTTPException",
253500
+ message: "Invalid email or password",
253501
+ request_id: null,
253502
+ traceback: ""
253503
+ });
253504
+ }
253505
+ return;
253506
+ }
253507
+ res.status(401).json({ error: "Unauthorized" });
253508
+ });
253509
+ router.post("/register", parseBody, async (req, res) => {
253510
+ const { email: email3, password } = LoginBody.parse(req.body);
253511
+ if ((password || "").length < 8) {
253512
+ res.status(400).json({
253513
+ detail: "Password must be at least 8 characters long",
253514
+ error_type: "HTTPException",
253515
+ message: "Password must be at least 8 characters long",
253516
+ request_id: null,
253517
+ traceback: ""
253518
+ });
253519
+ return;
253520
+ }
253521
+ const result = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: email3 });
253522
+ if (result) {
253523
+ res.status(400).json({
253524
+ detail: "A user with this email already exists",
253525
+ error_type: "HTTPException",
253526
+ message: "A user with this email already exists",
253527
+ request_id: null,
253528
+ traceback: ""
253529
+ });
253530
+ return;
253531
+ }
253532
+ const privateUserCollection = db2.getCollection(PRIVATE_USER_COLLECTION);
253533
+ const privateUserData = await privateUserCollection?.findOneAsync({
253534
+ email: email3
253535
+ });
253536
+ const otpCode = generateCode();
253537
+ const id2 = privateUserData ? privateUserData.id : nanoid3();
253538
+ if (!privateUserData) {
253539
+ await privateUserCollection?.insertAsync({
253540
+ id: id2,
253541
+ email: email3,
253542
+ otpCode,
253543
+ password,
253544
+ createdAt: Date.now()
253545
+ });
253546
+ } else {
253547
+ await privateUserCollection?.updateAsync({
253548
+ email: email3
253549
+ }, {
253550
+ $set: {
253551
+ otpCode,
253552
+ createdAt: Date.now()
253553
+ }
253554
+ });
253555
+ }
253556
+ logger2.log(theme.styles.info(`
253557
+ In order to complete registration use this verification code: ${otpCode}
253558
+ `));
253559
+ res.json({
253560
+ id: id2,
253561
+ message: "Registration successful. Please check your email for the verification code.",
253562
+ otp_expires_in_minutes: 10
253563
+ });
253564
+ });
253565
+ router.post("/verify-otp", parseBody, async (req, res) => {
253566
+ const { email: email3, otp_code } = VerifyOtpBody.parse(req.body);
253567
+ const privateUserCollection = db2.getCollection(PRIVATE_USER_COLLECTION);
253568
+ const privateUserData = await privateUserCollection?.findOneAsync({
253569
+ email: email3
253570
+ });
253571
+ if (privateUserData && privateUserData.otpCode === otp_code) {
253572
+ if (+Date.now() - privateUserData.createdAt < 10 * 60 * 1000) {
253573
+ await privateUserCollection?.updateAsync({
253574
+ email: email3
253575
+ }, {
253576
+ $unset: { otpCode: true }
253577
+ });
253578
+ const collection = db2.getCollection(USER_COLLECTION);
253579
+ const now = getNowISOTimestamp();
253580
+ const nameFromEmailMatch = /^([^@]+)/.exec(email3);
253581
+ const fullName = nameFromEmailMatch ? nameFromEmailMatch[1] : email3;
253582
+ await collection?.insertAsync({
253583
+ id: privateUserData.id,
253584
+ email: email3,
253585
+ full_name: fullName,
253586
+ is_service: false,
253587
+ is_verified: true,
253588
+ disabled: null,
253589
+ role: "user",
253590
+ collaborator_role: "editor",
253591
+ created_date: now,
253592
+ updated_date: now
253593
+ });
253594
+ res.json({
253595
+ id: privateUserData.id,
253596
+ access_token: createJwtToken(email3),
253597
+ message: "Email verified successfully. You are now logged in.",
253598
+ success: true
253599
+ });
253600
+ } else {
253601
+ res.status(400).json({
253602
+ detail: "Verification code has expired",
253603
+ error_type: "HTTPException",
253604
+ message: "Verification code has expired",
253605
+ request_id: null,
253606
+ traceback: ""
253607
+ });
253608
+ }
253609
+ } else {
253610
+ const appId = req.params.appId;
253611
+ res.status(500).json({
253612
+ detail: `{'email': '${email3}', 'app_id': '${appId}}'} -> Object not found`,
253613
+ error_type: "ObjectNotFoundError",
253614
+ message: `{'email': '${email3}', 'app_id': '${appId}}'} -> Object not found`,
253615
+ request_id: null,
253616
+ traceback: ""
253617
+ });
253618
+ }
253619
+ });
253620
+ return router;
253621
+ }
253622
+
253623
+ // src/cli/dev/dev-server/routes/entities/entities-router.ts
253624
+ var import_express4 = __toESM(require_express(), 1);
253625
+ var import_jsonwebtoken3 = __toESM(require_jsonwebtoken(), 1);
253626
+
253627
+ // src/cli/dev/dev-server/db/rls.ts
253628
+ function resolveTemplate(value, user) {
253629
+ return value.replace(/\{\{user\.([\w.]+)\}\}/g, (_match, path18) => {
253630
+ if (path18.startsWith("data.")) {
253631
+ return String(user[path18.slice(5)] ?? "");
253632
+ }
253633
+ return String(user[path18] ?? "");
253634
+ });
253635
+ }
253636
+ function getRecordField(key2, record2) {
253637
+ if (key2.startsWith("data.")) {
253638
+ return record2[key2.slice(5)];
253639
+ }
253640
+ return record2[key2];
253641
+ }
253642
+ function evaluateOperator(recordValue, operator) {
253643
+ for (const [op2, opValue] of Object.entries(operator)) {
253644
+ switch (op2) {
253645
+ case "$in":
253646
+ if (!Array.isArray(opValue) || !opValue.includes(recordValue))
253647
+ return false;
253648
+ break;
253649
+ case "$nin":
253650
+ if (Array.isArray(opValue) && opValue.includes(recordValue))
253651
+ return false;
253652
+ break;
253653
+ case "$ne":
253654
+ if (recordValue === opValue)
253655
+ return false;
253656
+ break;
253657
+ case "$all":
253658
+ if (!Array.isArray(recordValue) || !Array.isArray(opValue))
253659
+ return false;
253660
+ if (!opValue.every((v10) => recordValue.includes(v10)))
253661
+ return false;
253662
+ break;
253663
+ }
253664
+ }
253665
+ return true;
253666
+ }
253667
+ function evaluateUserCondition(condition, user) {
253668
+ for (const [key2, expected] of Object.entries(condition)) {
253669
+ const userValue = key2.startsWith("data.") ? user[key2.slice(5)] : user[key2];
253670
+ if (typeof expected === "object" && expected !== null) {
253671
+ if (!evaluateOperator(userValue, expected))
253672
+ return false;
253673
+ } else {
253674
+ if (userValue !== expected)
253675
+ return false;
253676
+ }
253677
+ }
253678
+ return true;
253679
+ }
253680
+ function evaluateCondition(condition, record2, user) {
253681
+ for (const [key2, value] of Object.entries(condition)) {
253682
+ if (key2 === "user_condition") {
253683
+ if (!evaluateUserCondition(value, user))
253684
+ return false;
253685
+ continue;
253686
+ }
253687
+ if (key2 === "$or") {
253688
+ const conditions = value;
253689
+ if (!conditions.some((c8) => evaluateCondition(c8, record2, user)))
253690
+ return false;
253691
+ continue;
253692
+ }
253693
+ if (key2 === "$and") {
253694
+ const conditions = value;
253695
+ if (!conditions.every((c8) => evaluateCondition(c8, record2, user)))
253696
+ return false;
253697
+ continue;
253698
+ }
253699
+ if (key2 === "$nor") {
253700
+ const conditions = value;
253701
+ if (conditions.some((c8) => evaluateCondition(c8, record2, user)))
253702
+ return false;
253703
+ continue;
253704
+ }
253705
+ const recordValue = getRecordField(key2, record2);
253706
+ const resolvedValue = typeof value === "string" ? resolveTemplate(value, user) : value;
253707
+ if (typeof resolvedValue === "object" && resolvedValue !== null) {
253708
+ if (!evaluateOperator(recordValue, resolvedValue))
253709
+ return false;
253710
+ } else {
253711
+ if (recordValue !== resolvedValue)
253712
+ return false;
253713
+ }
253714
+ }
253715
+ return true;
253716
+ }
253717
+ function checkRLS(rule, record2, user) {
253718
+ if (rule === undefined)
253719
+ return true;
253720
+ if (typeof rule === "boolean")
253721
+ return rule;
253722
+ if (!user)
253723
+ return false;
253724
+ return evaluateCondition(rule, record2, user);
253725
+ }
253726
+ function applyFLS(record2, schema10, user, operation) {
253727
+ const result = {};
253728
+ for (const [key2, value] of Object.entries(record2)) {
253729
+ const rule = schema10.properties[key2]?.rls?.[operation];
253730
+ if (!rule || checkRLS(rule, record2, user)) {
253731
+ result[key2] = value;
253732
+ }
253733
+ }
253734
+ return result;
253735
+ }
253736
+
253737
+ // src/cli/dev/dev-server/db/entity-queries.ts
253738
+ function parseSort(sort) {
253739
+ if (!sort) {
253740
+ return;
253741
+ }
253742
+ if (sort.startsWith("-")) {
253743
+ return { [sort.slice(1)]: -1 };
253744
+ }
253745
+ return { [sort]: 1 };
253746
+ }
253747
+ function parseFields(fields) {
253748
+ if (!fields) {
253749
+ return;
253750
+ }
253751
+ const projection = {};
253752
+ for (const field of fields.split(",")) {
253753
+ const trimmed = field.trim();
253754
+ if (trimmed) {
253755
+ projection[trimmed] = 1;
253756
+ }
253757
+ }
253758
+ return Object.keys(projection).length > 0 ? projection : undefined;
253759
+ }
253760
+ var queryEntity = async (collection, reqQuery) => {
253761
+ const { sort, limit, skip: skip2, fields, q: q13 } = reqQuery;
253762
+ let query = {};
253763
+ if (q13 && typeof q13 === "string") {
253764
+ try {
253765
+ query = JSON.parse(q13);
253766
+ } catch {
253767
+ throw new InvalidInputError("Invalid query parameter 'q'");
253768
+ }
253769
+ }
253770
+ let cursor3 = collection.findAsync(query);
253771
+ const sortObj = parseSort(sort);
253772
+ if (sortObj) {
253773
+ cursor3 = cursor3.sort(sortObj);
253774
+ }
253775
+ if (skip2) {
253776
+ const skipNum = Number.parseInt(skip2, 10);
253777
+ if (!Number.isNaN(skipNum)) {
253778
+ cursor3 = cursor3.skip(skipNum);
253779
+ }
253780
+ }
253781
+ if (limit) {
253782
+ const limitNum = Number.parseInt(limit, 10);
253783
+ if (!Number.isNaN(limitNum)) {
253784
+ cursor3 = cursor3.limit(limitNum);
253785
+ }
253786
+ }
253787
+ const projection = parseFields(fields);
253788
+ if (projection) {
253789
+ cursor3 = cursor3.projection(projection);
253790
+ }
253791
+ return cursor3;
253792
+ };
253793
+
253794
+ // src/cli/dev/dev-server/routes/entities/entities-user-router.ts
253795
+ var import_express3 = __toESM(require_express(), 1);
253796
+ var import_jsonwebtoken2 = __toESM(require_jsonwebtoken(), 1);
253797
+ function createUserRouter(db2, logger2) {
253798
+ const router = import_express3.Router({ mergeParams: true });
253799
+ const parseBody = import_express3.json();
253455
253800
  function withAuth(handler) {
253456
253801
  return async (req, res) => {
253457
253802
  const auth2 = req.headers.authorization;
@@ -253460,13 +253805,13 @@ function createUserRouter(db2, logger2) {
253460
253805
  return;
253461
253806
  }
253462
253807
  try {
253463
- const { payload } = import_jsonwebtoken.default.decode(auth2.replace("Bearer ", ""), { complete: true }) ?? {};
253464
- const result = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: payload?.sub });
253465
- if (!result) {
253808
+ const { payload } = import_jsonwebtoken2.default.decode(auth2.replace("Bearer ", ""), { complete: true }) ?? {};
253809
+ const currentUser = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: payload?.sub });
253810
+ if (!currentUser) {
253466
253811
  res.status(404).json({ error: "Unable to read data for the current user" });
253467
253812
  return;
253468
253813
  }
253469
- await handler(req, res, result);
253814
+ await handler(req, res, currentUser);
253470
253815
  } catch {
253471
253816
  res.status(401).json({ error: "Unauthorized" });
253472
253817
  }
@@ -253493,6 +253838,28 @@ function createUserRouter(db2, logger2) {
253493
253838
  ...req.body
253494
253839
  });
253495
253840
  }));
253841
+ router.get("/", withAuth(async (req, res, currentUser) => {
253842
+ const collection = db2.getCollection(USER_COLLECTION);
253843
+ if (!collection) {
253844
+ res.status(404).json({ error: `Entity "${USER_COLLECTION}" not found` });
253845
+ return;
253846
+ }
253847
+ try {
253848
+ if (currentUser.role === "admin") {
253849
+ const result = await queryEntity(collection, req.query);
253850
+ res.json(stripInternalFields(result));
253851
+ } else {
253852
+ res.json([stripInternalFields(currentUser)]);
253853
+ }
253854
+ } catch (error48) {
253855
+ if (error48 instanceof InvalidInputError) {
253856
+ res.status(400).json({ error: error48.message });
253857
+ } else {
253858
+ logger2.error(`Error in GET /${USER_COLLECTION}:`, error48);
253859
+ res.status(500).json({ error: "Internal server error" });
253860
+ }
253861
+ }
253862
+ }));
253496
253863
  router.post("/bulk", async (_req, res) => {
253497
253864
  res.json({});
253498
253865
  });
@@ -253542,31 +253909,9 @@ function createUserRouter(db2, logger2) {
253542
253909
  }
253543
253910
 
253544
253911
  // src/cli/dev/dev-server/routes/entities/entities-router.ts
253545
- function parseSort(sort) {
253546
- if (!sort) {
253547
- return;
253548
- }
253549
- if (sort.startsWith("-")) {
253550
- return { [sort.slice(1)]: -1 };
253551
- }
253552
- return { [sort]: 1 };
253553
- }
253554
- function parseFields(fields) {
253555
- if (!fields) {
253556
- return;
253557
- }
253558
- const projection = {};
253559
- for (const field of fields.split(",")) {
253560
- const trimmed = field.trim();
253561
- if (trimmed) {
253562
- projection[trimmed] = 1;
253563
- }
253564
- }
253565
- return Object.keys(projection).length > 0 ? projection : undefined;
253566
- }
253567
253912
  async function createEntityRoutes(db2, logger2, broadcast) {
253568
- const router = import_express3.Router({ mergeParams: true });
253569
- const parseBody = import_express3.json();
253913
+ const router = import_express4.Router({ mergeParams: true });
253914
+ const parseBody = import_express4.json();
253570
253915
  function withCollection(handler) {
253571
253916
  return async (req, res) => {
253572
253917
  const collection = db2.getCollection(req.params.entityName);
@@ -253574,7 +253919,13 @@ async function createEntityRoutes(db2, logger2, broadcast) {
253574
253919
  res.status(404).json({ error: `Entity "${req.params.entityName}" not found` });
253575
253920
  return;
253576
253921
  }
253577
- await handler(req, res, collection);
253922
+ let currentUser;
253923
+ try {
253924
+ const auth2 = req.headers.authorization || "";
253925
+ const { payload } = import_jsonwebtoken3.default.decode(auth2.replace("Bearer ", ""), { complete: true }) ?? {};
253926
+ currentUser = await db2.getCollection(USER_COLLECTION)?.findOneAsync({ email: payload?.sub });
253927
+ } catch {}
253928
+ await handler(req, res, collection, currentUser);
253578
253929
  };
253579
253930
  }
253580
253931
  function emit(appId, entityName, type, data) {
@@ -253594,7 +253945,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
253594
253945
  }
253595
253946
  const userRouter = createUserRouter(db2, logger2);
253596
253947
  router.use("/User", userRouter);
253597
- router.get("/:entityName/:id", withCollection(async (req, res, collection) => {
253948
+ router.get("/:entityName/:id", withCollection(async (req, res, collection, currentUser) => {
253598
253949
  const { entityName, id: id2 } = req.params;
253599
253950
  try {
253600
253951
  const doc2 = await collection.findOneAsync({ id: id2 });
@@ -253602,63 +253953,70 @@ async function createEntityRoutes(db2, logger2, broadcast) {
253602
253953
  res.status(404).json({ error: `Record with id "${id2}" not found` });
253603
253954
  return;
253604
253955
  }
253605
- res.json(stripInternalFields(doc2));
253956
+ const schema10 = db2.getSchema(entityName);
253957
+ if (!checkRLS(schema10?.rls?.read, doc2, currentUser)) {
253958
+ res.status(403).json({ error: "Permission denied" });
253959
+ return;
253960
+ }
253961
+ let result = stripInternalFields(doc2);
253962
+ if (schema10) {
253963
+ result = applyFLS(result, schema10, currentUser, "read");
253964
+ }
253965
+ res.json(result);
253606
253966
  } catch (error48) {
253607
253967
  logger2.error(`Error in GET /${entityName}/${id2}:`, error48);
253608
253968
  res.status(500).json({ error: "Internal server error" });
253609
253969
  }
253610
253970
  }));
253611
- router.get("/:entityName", withCollection(async (req, res, collection) => {
253971
+ router.get("/:entityName", withCollection(async (req, res, collection, currentUser) => {
253612
253972
  const { entityName } = req.params;
253613
253973
  try {
253614
- const { sort, limit, skip: skip2, fields, q: q13 } = req.query;
253615
- let query = {};
253616
- if (q13 && typeof q13 === "string") {
253617
- try {
253618
- query = JSON.parse(q13);
253619
- } catch {
253620
- res.status(400).json({ error: "Invalid query parameter 'q'" });
253621
- return;
253622
- }
253623
- }
253624
- let cursor3 = collection.findAsync(query);
253625
- const sortObj = parseSort(sort);
253626
- if (sortObj) {
253627
- cursor3 = cursor3.sort(sortObj);
253628
- }
253629
- if (skip2) {
253630
- const skipNum = Number.parseInt(skip2, 10);
253631
- if (!Number.isNaN(skipNum)) {
253632
- cursor3 = cursor3.skip(skipNum);
253633
- }
253974
+ const schema10 = db2.getSchema(entityName);
253975
+ if (schema10?.rls?.read === false) {
253976
+ res.json([]);
253977
+ return;
253634
253978
  }
253635
- if (limit) {
253636
- const limitNum = Number.parseInt(limit, 10);
253637
- if (!Number.isNaN(limitNum)) {
253638
- cursor3 = cursor3.limit(limitNum);
253639
- }
253979
+ let results = stripInternalFields(await queryEntity(collection, req.query));
253980
+ if (schema10?.rls?.read && schema10.rls.read !== true) {
253981
+ results = results.filter((doc2) => checkRLS(schema10.rls.read, doc2, currentUser));
253640
253982
  }
253641
- const projection = parseFields(fields);
253642
- if (projection) {
253643
- cursor3 = cursor3.projection(projection);
253983
+ if (schema10) {
253984
+ results = results.map((doc2) => applyFLS(doc2, schema10, currentUser, "read"));
253644
253985
  }
253645
- const docs = await cursor3;
253646
- res.json(stripInternalFields(docs));
253986
+ res.json(results);
253647
253987
  } catch (error48) {
253648
- logger2.error(`Error in GET /${entityName}:`, error48);
253649
- res.status(500).json({ error: "Internal server error" });
253988
+ if (error48 instanceof InvalidInputError) {
253989
+ res.status(400).json({ error: error48.message });
253990
+ } else {
253991
+ logger2.error(`Error in GET /${entityName}:`, error48);
253992
+ res.status(500).json({ error: "Internal server error" });
253993
+ }
253650
253994
  }
253651
253995
  }));
253652
- router.post("/:entityName", parseBody, withCollection(async (req, res, collection) => {
253996
+ router.post("/:entityName", parseBody, withCollection(async (req, res, collection, currentUser) => {
253653
253997
  const { appId, entityName } = req.params;
253654
253998
  try {
253655
253999
  const now = new Date().toISOString();
253656
254000
  const { _id, ...body } = req.body;
253657
- const filteredBody = db2.prepareRecord(entityName, body);
254001
+ const schema10 = db2.getSchema(entityName);
254002
+ if (!checkRLS(schema10?.rls?.create, {
254003
+ ...body,
254004
+ created_by: currentUser?.email,
254005
+ created_by_id: currentUser?.id
254006
+ }, currentUser)) {
254007
+ res.status(403).json({ error: "Permission denied" });
254008
+ return;
254009
+ }
254010
+ let filteredBody = db2.prepareRecord(entityName, body);
254011
+ if (schema10) {
254012
+ filteredBody = applyFLS(filteredBody, schema10, currentUser, "write");
254013
+ }
253658
254014
  db2.validate(entityName, filteredBody);
253659
254015
  const record2 = {
253660
254016
  ...filteredBody,
253661
254017
  id: nanoid3(),
254018
+ created_by: currentUser?.email,
254019
+ created_by_id: currentUser?.id,
253662
254020
  created_date: now,
253663
254021
  updated_date: now
253664
254022
  };
@@ -253674,7 +254032,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
253674
254032
  res.status(500).json({ error: "Internal server error" });
253675
254033
  }
253676
254034
  }));
253677
- router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection) => {
254035
+ router.post("/:entityName/bulk", parseBody, withCollection(async (req, res, collection, currentUser) => {
253678
254036
  const { appId, entityName } = req.params;
253679
254037
  if (!Array.isArray(req.body)) {
253680
254038
  res.status(400).json({ error: "Request body must be an array" });
@@ -253682,13 +254040,27 @@ async function createEntityRoutes(db2, logger2, broadcast) {
253682
254040
  }
253683
254041
  try {
253684
254042
  const now = new Date().toISOString();
254043
+ const schema10 = db2.getSchema(entityName);
253685
254044
  const records = [];
253686
254045
  for (const record2 of req.body) {
253687
- const filteredRecord = db2.prepareRecord(entityName, record2);
254046
+ if (!checkRLS(schema10?.rls?.create, {
254047
+ ...record2,
254048
+ created_by: currentUser?.email,
254049
+ created_by_id: currentUser?.id
254050
+ }, currentUser)) {
254051
+ res.status(403).json({ error: "Permission denied" });
254052
+ return;
254053
+ }
254054
+ let filteredRecord = db2.prepareRecord(entityName, record2);
254055
+ if (schema10) {
254056
+ filteredRecord = applyFLS(filteredRecord, schema10, currentUser, "write");
254057
+ }
253688
254058
  db2.validate(entityName, filteredRecord);
253689
254059
  records.push({
253690
254060
  ...filteredRecord,
253691
254061
  id: nanoid3(),
254062
+ created_by: currentUser?.email,
254063
+ created_by_id: currentUser?.id,
253692
254064
  created_date: now,
253693
254065
  updated_date: now
253694
254066
  });
@@ -253705,11 +254077,26 @@ async function createEntityRoutes(db2, logger2, broadcast) {
253705
254077
  res.status(500).json({ error: "Internal server error" });
253706
254078
  }
253707
254079
  }));
253708
- router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection) => {
254080
+ router.put("/:entityName/:id", parseBody, withCollection(async (req, res, collection, currentUser) => {
253709
254081
  const { appId, entityName, id: id2 } = req.params;
253710
254082
  const { id: _id, created_date: _created_date, ...body } = req.body;
253711
254083
  try {
253712
- const filteredBody = db2.prepareRecord(entityName, body, true);
254084
+ const schema10 = db2.getSchema(entityName);
254085
+ if (schema10?.rls?.update !== undefined) {
254086
+ const existing = await collection.findOneAsync({ id: id2 });
254087
+ if (!existing) {
254088
+ res.status(404).json({ error: `Record with id "${id2}" not found` });
254089
+ return;
254090
+ }
254091
+ if (!checkRLS(schema10.rls.update, existing, currentUser)) {
254092
+ res.status(403).json({ error: "Permission denied" });
254093
+ return;
254094
+ }
254095
+ }
254096
+ let filteredBody = db2.prepareRecord(entityName, body, true);
254097
+ if (schema10) {
254098
+ filteredBody = applyFLS(filteredBody, schema10, currentUser, "write");
254099
+ }
253713
254100
  db2.validate(entityName, filteredBody, true);
253714
254101
  const updateData = {
253715
254102
  ...filteredBody,
@@ -253732,30 +254119,48 @@ async function createEntityRoutes(db2, logger2, broadcast) {
253732
254119
  res.status(500).json({ error: "Internal server error" });
253733
254120
  }
253734
254121
  }));
253735
- router.delete("/:entityName/:id", withCollection(async (req, res, collection) => {
254122
+ router.delete("/:entityName/:id", withCollection(async (req, res, collection, currentUser) => {
253736
254123
  const { appId, entityName, id: id2 } = req.params;
253737
254124
  try {
253738
254125
  const doc2 = await collection.findOneAsync({ id: id2 });
253739
- const numRemoved = await collection.removeAsync({ id: id2 }, { multi: false });
253740
- if (numRemoved === 0) {
254126
+ if (!doc2) {
253741
254127
  res.status(404).json({ error: `Record with id "${id2}" not found` });
253742
254128
  return;
253743
254129
  }
253744
- if (doc2) {
253745
- emit(appId, entityName, "delete", stripInternalFields(doc2));
254130
+ const schema10 = db2.getSchema(entityName);
254131
+ if (!checkRLS(schema10?.rls?.delete, doc2, currentUser)) {
254132
+ res.status(403).json({ error: "Permission denied" });
254133
+ return;
253746
254134
  }
254135
+ await collection.removeAsync({ id: id2 }, { multi: false });
254136
+ emit(appId, entityName, "delete", stripInternalFields(doc2));
253747
254137
  res.json({ success: true });
253748
254138
  } catch (error48) {
253749
254139
  logger2.error(`Error in DELETE /${entityName}/${id2}:`, error48);
253750
254140
  res.status(500).json({ error: "Internal server error" });
253751
254141
  }
253752
254142
  }));
253753
- router.delete("/:entityName", parseBody, withCollection(async (req, res, collection) => {
254143
+ router.delete("/:entityName", parseBody, withCollection(async (req, res, collection, currentUser) => {
253754
254144
  const { entityName } = req.params;
253755
254145
  try {
253756
254146
  const query = req.body || {};
253757
- const numRemoved = await collection.removeAsync(query, { multi: true });
253758
- res.json({ success: true, deleted: numRemoved });
254147
+ const schema10 = db2.getSchema(entityName);
254148
+ const rlsDelete = schema10?.rls?.delete;
254149
+ if (rlsDelete !== undefined && rlsDelete !== true) {
254150
+ if (rlsDelete === false) {
254151
+ res.status(403).json({ error: "Permission denied" });
254152
+ return;
254153
+ }
254154
+ const docs = await collection.findAsync(query);
254155
+ const allowedIds = docs.filter((doc2) => checkRLS(rlsDelete, doc2, currentUser)).map((doc2) => doc2.id);
254156
+ const numRemoved = await collection.removeAsync({ id: { $in: allowedIds } }, { multi: true });
254157
+ res.json({ success: true, deleted: numRemoved });
254158
+ } else {
254159
+ const numRemoved = await collection.removeAsync(query, {
254160
+ multi: true
254161
+ });
254162
+ res.json({ success: true, deleted: numRemoved });
254163
+ }
253759
254164
  } catch (error48) {
253760
254165
  logger2.error(`Error in DELETE /${entityName}:`, error48);
253761
254166
  res.status(500).json({ error: "Internal server error" });
@@ -253765,7 +254170,7 @@ async function createEntityRoutes(db2, logger2, broadcast) {
253765
254170
  }
253766
254171
 
253767
254172
  // src/cli/dev/dev-server/routes/integrations.ts
253768
- var import_express4 = __toESM(require_express(), 1);
254173
+ var import_express5 = __toESM(require_express(), 1);
253769
254174
  var import_multer = __toESM(require_multer(), 1);
253770
254175
  import { createHash, randomUUID as randomUUID4 } from "node:crypto";
253771
254176
  import fs28 from "node:fs";
@@ -253774,8 +254179,8 @@ function createFileToken(fileUri) {
253774
254179
  return createHash("sha256").update(fileUri).digest("hex");
253775
254180
  }
253776
254181
  function createIntegrationRoutes(mediaFilesDir, baseUrl, remoteProxy, logger2) {
253777
- const router = import_express4.Router({ mergeParams: true });
253778
- const parseBody = import_express4.json();
254182
+ const router = import_express5.Router({ mergeParams: true });
254183
+ const parseBody = import_express5.json();
253779
254184
  const privateFilesDir = path18.join(mediaFilesDir, "private");
253780
254185
  fs28.mkdirSync(mediaFilesDir, { recursive: true });
253781
254186
  fs28.mkdirSync(privateFilesDir, { recursive: true });
@@ -253845,7 +254250,7 @@ function createIntegrationRoutes(mediaFilesDir, baseUrl, remoteProxy, logger2) {
253845
254250
  return router;
253846
254251
  }
253847
254252
  function createCustomIntegrationRoutes(remoteProxy, logger2) {
253848
- const router = import_express4.Router({ mergeParams: true });
254253
+ const router = import_express5.Router({ mergeParams: true });
253849
254254
  router.post("/:slug/:operationId", (req, res, next) => {
253850
254255
  logger2.warn(`"${req.originalUrl}" is not supported in local development, passing call to production`);
253851
254256
  req.url = req.originalUrl;
@@ -255558,7 +255963,7 @@ async function createDevServer(options8) {
255558
255963
  const port = userPort ?? await getPorts({ port: DEFAULT_PORT });
255559
255964
  const baseUrl = `http://localhost:${port}`;
255560
255965
  const { functions, entities, project: project2 } = await options8.loadResources();
255561
- const app = import_express5.default();
255966
+ const app = import_express6.default();
255562
255967
  const remoteProxy = import_http_proxy_middleware2.createProxyMiddleware({
255563
255968
  target: BASE44_APP_URL,
255564
255969
  changeOrigin: true
@@ -255590,6 +255995,8 @@ async function createDevServer(options8) {
255590
255995
  let emitEntityEvent = () => {};
255591
255996
  const entityRoutes = await createEntityRoutes(db2, devLogger, (...args) => emitEntityEvent(...args));
255592
255997
  app.use("/api/apps/:appId/entities", entityRoutes);
255998
+ const authRouter = createAuthRouter(db2, devLogger);
255999
+ app.use("/api/apps/:appId/auth", authRouter);
255593
256000
  const { path: mediaFilesDir } = await $dir();
255594
256001
  app.use("/media/private/:fileUri", (req, res, next) => {
255595
256002
  const { fileUri } = req.params;
@@ -255609,13 +256016,15 @@ async function createDevServer(options8) {
255609
256016
  }
255610
256017
  next();
255611
256018
  });
255612
- app.use("/media", import_express5.default.static(mediaFilesDir));
256019
+ app.use("/media", import_express6.default.static(mediaFilesDir));
255613
256020
  const integrationRoutes = createIntegrationRoutes(mediaFilesDir, baseUrl, remoteProxy, devLogger);
255614
256021
  app.use("/api/apps/:appId/integration-endpoints", integrationRoutes);
255615
256022
  const customIntegrationRoutes = createCustomIntegrationRoutes(remoteProxy, devLogger);
255616
256023
  app.use("/api/apps/:appId/integrations/custom", customIntegrationRoutes);
255617
256024
  app.use((req, res, next) => {
255618
- devLogger.warn(`"${req.originalUrl}" is not supported in local development, passing call to production`);
256025
+ if (!req.originalUrl.endsWith("analytics/track/batch")) {
256026
+ devLogger.warn(`"${req.originalUrl}" is not supported in local development, passing call to production`);
256027
+ }
255619
256028
  remoteProxy(req, res, next);
255620
256029
  });
255621
256030
  const server = await new Promise((resolve8, reject) => {
@@ -255686,6 +256095,7 @@ async function devAction({ log }, options8) {
255686
256095
  const { port: resolvedPort } = await createDevServer({
255687
256096
  log,
255688
256097
  port,
256098
+ cwd: process21.cwd(),
255689
256099
  denoWrapperPath: getDenoWrapperPath(),
255690
256100
  loadResources: async () => {
255691
256101
  const { functions, entities, project: project2 } = await readProjectConfig();
@@ -260193,4 +260603,4 @@ export {
260193
260603
  CLIExitError
260194
260604
  };
260195
260605
 
260196
- //# debugId=CBBBAC71F12A75D664756E2164756E21
260606
+ //# debugId=FF5639434B8FD38C64756E2164756E21