@xk2800/nextjs-template 0.8.0-beta.4 → 0.8.0-beta.6

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.
Files changed (71) hide show
  1. package/components/dashboard/admin/feedbackTestForm.tsx +55 -0
  2. package/components/dashboard/admin/systemSettingsForm.tsx +28 -0
  3. package/components/dashboard/errorFeedback.tsx +65 -0
  4. package/components/dashboard/feedbackDialog.tsx +53 -11
  5. package/dist/activity/logger.js +12 -0
  6. package/dist/activity/logger.js.map +1 -1
  7. package/dist/activity/logger.mjs +3 -3
  8. package/dist/activity/queries.d.mts +1 -1
  9. package/dist/activity/queries.d.ts +1 -1
  10. package/dist/activity/queries.js +12 -0
  11. package/dist/activity/queries.js.map +1 -1
  12. package/dist/activity/queries.mjs +2 -2
  13. package/dist/admin/queries.js +12 -0
  14. package/dist/admin/queries.js.map +1 -1
  15. package/dist/admin/queries.mjs +2 -2
  16. package/dist/api/feedback.d.mts +15 -1
  17. package/dist/api/feedback.d.ts +15 -1
  18. package/dist/api/feedback.js +90 -3
  19. package/dist/api/feedback.js.map +1 -1
  20. package/dist/api/feedback.mjs +38 -9
  21. package/dist/api/feedback.mjs.map +1 -1
  22. package/dist/auth/helpers.js +13 -0
  23. package/dist/auth/helpers.js.map +1 -1
  24. package/dist/auth/helpers.mjs +5 -5
  25. package/dist/auth/index.js +13 -0
  26. package/dist/auth/index.js.map +1 -1
  27. package/dist/auth/index.mjs +5 -5
  28. package/dist/{chunk-IFZGEDMN.mjs → chunk-3MGEAGHU.mjs} +5 -4
  29. package/dist/chunk-3MGEAGHU.mjs.map +1 -0
  30. package/dist/chunk-KAEA35OC.mjs +76 -0
  31. package/dist/chunk-KAEA35OC.mjs.map +1 -0
  32. package/dist/{chunk-G3B7NC5B.mjs → chunk-VQ5B4MA7.mjs} +4 -4
  33. package/dist/{chunk-G3B7NC5B.mjs.map → chunk-VQ5B4MA7.mjs.map} +1 -1
  34. package/dist/{chunk-LKPF3JCV.mjs → chunk-WZDAMCUX.mjs} +3 -3
  35. package/dist/{chunk-LKPF3JCV.mjs.map → chunk-WZDAMCUX.mjs.map} +1 -1
  36. package/dist/{chunk-GS33BWOL.mjs → chunk-XWVAJXAG.mjs} +6 -6
  37. package/dist/{chunk-GS33BWOL.mjs.map → chunk-XWVAJXAG.mjs.map} +1 -1
  38. package/dist/{chunk-YPKOQOTA.mjs → chunk-ZQCALTKB.mjs} +16 -4
  39. package/dist/chunk-ZQCALTKB.mjs.map +1 -0
  40. package/dist/db/index.d.mts +1 -1
  41. package/dist/db/index.d.ts +1 -1
  42. package/dist/db/index.js +12 -0
  43. package/dist/db/index.js.map +1 -1
  44. package/dist/db/index.mjs +2 -2
  45. package/dist/db/schema.d.mts +1 -1
  46. package/dist/db/schema.d.ts +1 -1
  47. package/dist/db/schema.js +12 -0
  48. package/dist/db/schema.js.map +1 -1
  49. package/dist/db/schema.mjs +1 -1
  50. package/dist/errors.d.mts +29 -0
  51. package/dist/errors.d.ts +29 -0
  52. package/dist/errors.js +365 -0
  53. package/dist/errors.js.map +1 -0
  54. package/dist/errors.mjs +7 -0
  55. package/dist/errors.mjs.map +1 -0
  56. package/dist/{schema-CZnQXmhF.d.mts → schema-BNSBWNjK.d.mts} +95 -2
  57. package/dist/{schema-CZnQXmhF.d.ts → schema-BNSBWNjK.d.ts} +95 -2
  58. package/dist/sessions/queries.js +12 -0
  59. package/dist/sessions/queries.js.map +1 -1
  60. package/dist/sessions/queries.mjs +2 -2
  61. package/dist/settings/queries.d.mts +2 -2
  62. package/dist/settings/queries.d.ts +2 -2
  63. package/dist/settings/queries.js +13 -0
  64. package/dist/settings/queries.js.map +1 -1
  65. package/dist/settings/queries.mjs +3 -3
  66. package/dist/users/queries.js +12 -0
  67. package/dist/users/queries.js.map +1 -1
  68. package/dist/users/queries.mjs +2 -2
  69. package/package.json +9 -1
  70. package/dist/chunk-IFZGEDMN.mjs.map +0 -1
  71. package/dist/chunk-YPKOQOTA.mjs.map +0 -1
@@ -20,6 +20,7 @@ var passkey = require('@better-auth/passkey');
20
20
  var api = require('better-auth/api');
21
21
  var uaParserJs = require('ua-parser-js');
22
22
  var geoip = require('geoip-lite');
23
+ var crypto = require('crypto');
23
24
 
24
25
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
25
26
 
@@ -210,6 +211,7 @@ __export(schema_exports, {
210
211
  activityLogs: () => activityLogs,
211
212
  authThrottle: () => authThrottle,
212
213
  deviceFingerprints: () => deviceFingerprints,
214
+ errorRefs: () => errorRefs,
213
215
  passkeys: () => passkeys,
214
216
  sessions: () => sessions,
215
217
  systemSettings: () => systemSettings,
@@ -392,6 +394,9 @@ var systemSettings = pgCore.pgTable("system_settings", {
392
394
  authEnableEmailPassword: pgCore.boolean("authEnableEmailPassword").notNull().default(true),
393
395
  authEnableOneTap: pgCore.boolean("authEnableOneTap").notNull().default(false),
394
396
  enableSessionRevocation: pgCore.boolean("enableSessionRevocation").notNull().default(true),
397
+ // Attach the error a user hit to feedback they send from it (see
398
+ // lib/feedback-route.ts). Off = only what the user typed is sent.
399
+ feedbackAttachErrors: pgCore.boolean("feedbackAttachErrors").notNull().default(true),
395
400
  updatedAt: pgCore.timestamp("updatedAt").notNull().defaultNow(),
396
401
  // Who last changed a setting. This row doesn't "belong" to that admin the
397
402
  // way sessions/activityLogs belong to their user, so deleting that admin's
@@ -400,6 +405,14 @@ var systemSettings = pgCore.pgTable("system_settings", {
400
405
  }, (table) => [
401
406
  pgCore.check("system_settings_singleton", drizzleOrm.sql`${table.id} = 'default'`)
402
407
  ]);
408
+ var errorRefs = pgCore.pgTable("error_refs", {
409
+ ref: pgCore.text("ref").primaryKey(),
410
+ message: pgCore.text("message").notNull(),
411
+ detail: pgCore.text("detail").notNull(),
412
+ createdAt: pgCore.timestamp("createdAt").notNull().defaultNow()
413
+ }, (table) => [
414
+ pgCore.index("error_refs_created_at_idx").on(table.createdAt)
415
+ ]);
403
416
  var envSchema = zod.z.object({
404
417
  NODE_ENV: zod.z.enum(["development", "production", "test"]).default("development"),
405
418
  PORT: zod.z.string().default("3000").transform(Number),
@@ -602,6 +615,7 @@ function fallbackSettings() {
602
615
  authEnableEmailPassword: config.AUTH_ENABLE_EMAIL_PASSWORD,
603
616
  authEnableOneTap: config.AUTH_ENABLE_ONE_TAP,
604
617
  enableSessionRevocation: config.ENABLE_SESSION_REVOCATION,
618
+ feedbackAttachErrors: true,
605
619
  updatedAt: /* @__PURE__ */ new Date(0),
606
620
  updatedBy: null
607
621
  };
@@ -1039,15 +1053,61 @@ var auth = new Proxy({}, {
1039
1053
  return Reflect.getOwnPropertyDescriptor(getDefaultAuth(), prop);
1040
1054
  }
1041
1055
  });
1056
+ var REF_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
1057
+ var REF_PATTERN = /\(ref: ([2-9A-HJ-NP-Z]{6})\)/;
1058
+ var DETAIL_MAX_LENGTH = 8e3;
1059
+ var RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
1060
+ function describeError(error) {
1061
+ if (error instanceof Error) return error.stack || `${error.name}: ${error.message}`;
1062
+ try {
1063
+ return JSON.stringify(error, null, 2);
1064
+ } catch (e) {
1065
+ return String(error);
1066
+ }
1067
+ }
1068
+ function storeDetail(ref, message, error) {
1069
+ const save = async () => {
1070
+ try {
1071
+ await db.insert(errorRefs).values({
1072
+ ref,
1073
+ message,
1074
+ detail: describeError(error).slice(0, DETAIL_MAX_LENGTH)
1075
+ });
1076
+ await db.delete(errorRefs).where(drizzleOrm.lt(errorRefs.createdAt, new Date(Date.now() - RETENTION_MS)));
1077
+ } catch (storeError) {
1078
+ console.error(`[error ref ${ref}] could not store error detail`, storeError);
1079
+ }
1080
+ };
1081
+ try {
1082
+ server.after(save);
1083
+ } catch (e) {
1084
+ void save();
1085
+ }
1086
+ }
1087
+ async function lookupErrorDetail(text2) {
1088
+ var _a, _b;
1089
+ const ref = (_a = text2.match(REF_PATTERN)) == null ? void 0 : _a[1];
1090
+ if (!ref) return null;
1091
+ const [row] = await db.select({ detail: errorRefs.detail }).from(errorRefs).where(drizzleOrm.eq(errorRefs.ref, ref)).limit(1);
1092
+ return (_b = row == null ? void 0 : row.detail) != null ? _b : null;
1093
+ }
1094
+ function unexpectedErrorMessage(error, message = "Something went wrong. Please try again.") {
1095
+ const ref = Array.from(crypto.randomBytes(6), (b) => REF_ALPHABET[b % REF_ALPHABET.length]).join("");
1096
+ console.error(`[error ref ${ref}] ${message}`, error);
1097
+ storeDetail(ref, message, error);
1098
+ return `${message} (ref: ${ref})`;
1099
+ }
1042
1100
 
1043
1101
  // lib/feedback-route.ts
1044
1102
  var feedbackSchema = zod.z.object({
1045
1103
  type: zod.z.enum(["bug", "feedback"]),
1046
1104
  // Keep in sync with FEEDBACK_MAX_LENGTH in components/dashboard/feedbackDialog.tsx.
1047
1105
  message: zod.z.string().trim().min(1).max(2e3),
1048
- page: zod.z.string().max(300).optional()
1106
+ page: zod.z.string().max(300).optional(),
1107
+ // The error the user reported from (FeedbackDialog's errorDetails).
1108
+ error: zod.z.string().max(2e3).optional()
1049
1109
  });
1050
- var COOLDOWN_MS = 3e4;
1110
+ var COOLDOWN_MS = 0;
1051
1111
  var lastSentAt = /* @__PURE__ */ new Map();
1052
1112
  async function POST(request) {
1053
1113
  var _a;
@@ -1073,6 +1133,8 @@ async function POST(request) {
1073
1133
  }
1074
1134
  lastSentAt.set(session.user.id, now);
1075
1135
  const { type, message, page } = parsed.data;
1136
+ const errorDetails = parsed.data.error && (await getSystemSettings()).feedbackAttachErrors ? parsed.data.error : void 0;
1137
+ const technicalDetail = errorDetails ? await lookupErrorDetail(errorDetails) : null;
1076
1138
  const { user } = session;
1077
1139
  const title = type === "bug" ? "\u{1F41E} Bug report" : "\u{1F4AC} Feedback";
1078
1140
  const userAgent = request.headers.get("user-agent") || "unknown";
@@ -1093,6 +1155,8 @@ async function POST(request) {
1093
1155
  fields: [
1094
1156
  { name: "User", value: `${user.name} (${user.email})`.slice(0, 1024) },
1095
1157
  { name: "Page", value: page || "unknown", inline: true },
1158
+ ...errorDetails ? [{ name: "Error", value: errorDetails.slice(0, 1024) }] : [],
1159
+ ...technicalDetail ? [{ name: "Technical details", value: "```\n" + technicalDetail.slice(0, 1e3) + "\n```" }] : [],
1096
1160
  { name: "User agent", value: userAgent.slice(0, 1024) }
1097
1161
  ],
1098
1162
  timestamp: new Date(now).toISOString()
@@ -1114,7 +1178,11 @@ async function POST(request) {
1114
1178
 
1115
1179
  ---
1116
1180
  User: ${user.name} <${user.email}>
1117
- Page: ${page || "unknown"}
1181
+ Page: ${page || "unknown"}${errorDetails ? `
1182
+ Error: ${errorDetails}` : ""}${technicalDetail ? `
1183
+
1184
+ Technical details:
1185
+ ${technicalDetail}` : ""}
1118
1186
  User agent: ${userAgent}`
1119
1187
  })).then(({ error }) => {
1120
1188
  if (error) throw new Error(error.message);
@@ -1131,7 +1199,26 @@ User agent: ${userAgent}`
1131
1199
  }
1132
1200
  return server.NextResponse.json({ success: true });
1133
1201
  }
1202
+ async function testErrorPOST(request) {
1203
+ if (process.env.NODE_ENV !== "development") {
1204
+ return server.NextResponse.json({ error: "Not found" }, { status: 404 });
1205
+ }
1206
+ const session = await auth.api.getSession({ headers: await headers.headers() });
1207
+ if (!(session == null ? void 0 : session.user)) {
1208
+ return server.NextResponse.json({ error: "Unauthorized" }, { status: 401 });
1209
+ }
1210
+ const body = await request.json().catch(() => null);
1211
+ if (typeof (body == null ? void 0 : body.text) === "string" && body.text.trim() === "!error") {
1212
+ return server.NextResponse.json({ error: "Test error from the feedback test form" }, { status: 400 });
1213
+ }
1214
+ if (typeof (body == null ? void 0 : body.text) === "string" && body.text.trim() === "!crash") {
1215
+ const crash = new Error("Test crash: DB connection to postgres://admin:hunter2@db.internal failed");
1216
+ return server.NextResponse.json({ error: unexpectedErrorMessage(crash) }, { status: 500 });
1217
+ }
1218
+ return server.NextResponse.json({ success: true });
1219
+ }
1134
1220
 
1135
1221
  exports.POST = POST;
1222
+ exports.testErrorPOST = testErrorPOST;
1136
1223
  //# sourceMappingURL=feedback.js.map
1137
1224
  //# sourceMappingURL=feedback.js.map