nuxt-users 1.1.3 → 1.1.5

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.
@@ -6,7 +6,7 @@ const formData = reactive({
6
6
  const message = ref("");
7
7
  const loading = ref(false);
8
8
  const isError = ref(false);
9
- const handleForgotPassword = async (data) => {
9
+ const handleForgotPassword = async () => {
10
10
  loading.value = true;
11
11
  message.value = "";
12
12
  isError.value = false;
@@ -17,8 +17,7 @@ const handleForgotPassword = async (data) => {
17
17
  "Content-Type": "application/json",
18
18
  "Accept": "application/json"
19
19
  },
20
- body: JSON.stringify({ email: data.email })
21
- // Use email from form data
20
+ body: JSON.stringify({ email: formData.email })
22
21
  });
23
22
  const responseData = await response.json();
24
23
  if (!response.ok) {
@@ -44,25 +43,24 @@ const handleForgotPassword = async (data) => {
44
43
  <template>
45
44
  <div>
46
45
  <h2>Forgot Password</h2>
47
- <FormKit
48
- v-slot="{ disabled }"
49
- type="form"
50
- :actions="false"
51
- @submit="handleForgotPassword"
52
- >
53
- <FormKit
54
- v-model="formData.email"
55
- type="email"
56
- name="email"
57
- label="Email"
58
- placeholder="your@email.com"
59
- validation="required|email"
60
- />
61
- <FormKit
46
+ <form @submit.prevent="handleForgotPassword">
47
+ <div class="form-group">
48
+ <label for="email">Email</label>
49
+ <input
50
+ id="email"
51
+ v-model="formData.email"
52
+ type="email"
53
+ name="email"
54
+ placeholder="your@email.com"
55
+ required
56
+ >
57
+ </div>
58
+ <button
62
59
  type="submit"
63
- :label="loading ? 'Sending...' : 'Send Password Reset Link'"
64
- :disabled="Boolean(disabled) || loading"
65
- />
60
+ :disabled="loading"
61
+ >
62
+ {{ loading ? "Sending..." : "Send Password Reset Link" }}
63
+ </button>
66
64
  <p
67
65
  v-if="message"
68
66
  :class="{ error: isError, success: !isError }"
@@ -70,10 +68,10 @@ const handleForgotPassword = async (data) => {
70
68
  >
71
69
  {{ message }}
72
70
  </p>
73
- </FormKit>
71
+ </form>
74
72
  </div>
75
73
  </template>
76
74
 
77
75
  <style scoped>
78
- .error{color:red}.success{color:green}.form-message{font-size:.9rem;margin-top:1em}:deep(.formkit-outer){margin-bottom:1em}:deep(.formkit-label){display:block;font-weight:700;margin-bottom:.25em}:deep(.formkit-input input[type=email]),:deep(.formkit-input input[type=password]){border:1px solid var(--color-border-dark);border-radius:4px;box-sizing:border-box;padding:.75em;width:100%}:deep(.formkit-input input[type=submit]){background-color:var(--color-gray-800);border:none;border-radius:4px;color:#fff;cursor:pointer;padding:.75em 1.5em}:deep(.formkit-input input[type=submit]:disabled){background-color:var(--color-gray-500);cursor:not-allowed}:deep(.formkit-messages){list-style:none;margin-top:.25em;padding:0}:deep(.formkit-message){color:red;font-size:.8rem}
76
+ .error{color:red}.success{color:green}.form-message{font-size:.9rem;margin-top:1em}.form-group{margin-bottom:1em}label{display:block;font-weight:700;margin-bottom:.25em}input[type=email]{border:1px solid var(--color-border-dark);border-radius:4px;box-sizing:border-box;padding:.75em;width:100%}button[type=submit]{background-color:var(--color-gray-800);border:none;border-radius:4px;color:#fff;cursor:pointer;padding:.75em 1.5em}button[type=submit]:disabled{background-color:var(--color-gray-500);cursor:not-allowed}
79
77
  </style>
@@ -8,16 +8,21 @@ const props = defineProps({
8
8
  const emit = defineEmits(["success", "error", "submit"]);
9
9
  const isLoading = ref(false);
10
10
  const error = ref("");
11
- const handleSubmit = async (formData) => {
11
+ const formData = ref({
12
+ email: "",
13
+ password: "",
14
+ rememberMe: false
15
+ });
16
+ const handleSubmit = async () => {
12
17
  isLoading.value = true;
13
18
  error.value = "";
14
19
  try {
15
- emit("submit", formData);
20
+ emit("submit", formData.value);
16
21
  const response = await $fetch(props.apiEndpoint, {
17
22
  method: "POST",
18
23
  body: {
19
- email: formData.email,
20
- password: formData.password
24
+ email: formData.value.email,
25
+ password: formData.value.password
21
26
  }
22
27
  });
23
28
  emit("success", response.user);
@@ -36,11 +41,7 @@ const handleSubmit = async (formData) => {
36
41
 
37
42
  <template>
38
43
  <div class="login-form-container">
39
- <FormKit
40
- type="form"
41
- :actions="false"
42
- @submit="handleSubmit"
43
- >
44
+ <form @submit.prevent="handleSubmit">
44
45
  <!-- Header slot -->
45
46
  <slot name="header">
46
47
  <div class="login-header">
@@ -55,40 +56,51 @@ const handleSubmit = async (formData) => {
55
56
 
56
57
  <!-- Email field -->
57
58
  <slot name="email-field">
58
- <FormKit
59
- type="email"
60
- name="email"
61
- label="Email"
62
- placeholder="Enter your email"
63
- validation="required|email"
64
- />
59
+ <div class="form-group">
60
+ <label for="email">Email</label>
61
+ <input
62
+ id="email"
63
+ v-model="formData.email"
64
+ type="email"
65
+ name="email"
66
+ placeholder="Enter your email"
67
+ required
68
+ >
69
+ </div>
65
70
  </slot>
66
71
 
67
72
  <!-- Password field -->
68
73
  <slot name="password-field">
69
- <FormKit
70
- type="password"
71
- name="password"
72
- label="Password"
73
- placeholder="Enter your password"
74
- validation="required|length:6"
75
- />
74
+ <div class="form-group">
75
+ <label for="password">Password</label>
76
+ <input
77
+ id="password"
78
+ v-model="formData.password"
79
+ type="password"
80
+ name="password"
81
+ placeholder="Enter your password"
82
+ required
83
+ minlength="6"
84
+ >
85
+ </div>
76
86
  </slot>
77
87
 
78
88
  <!-- Remember me slot -->
79
89
  <slot name="remember-me">
80
90
  <div class="remember-me">
81
- <FormKit
91
+ <input
92
+ id="rememberMe"
93
+ v-model="formData.rememberMe"
82
94
  type="checkbox"
83
95
  name="rememberMe"
84
- label="Remember me"
85
- />
96
+ >
97
+ <label for="rememberMe">Remember me</label>
86
98
  </div>
87
99
  </slot>
88
100
 
89
101
  <!-- Submit button slot -->
90
102
  <slot name="submit-button">
91
- <FormKit
103
+ <button
92
104
  type="submit"
93
105
  :disabled="isLoading"
94
106
  >
@@ -97,7 +109,7 @@ const handleSubmit = async (formData) => {
97
109
  class="loading-spinner"
98
110
  />
99
111
  {{ isLoading ? "Signing in..." : "Sign In" }}
100
- </FormKit>
112
+ </button>
101
113
  </slot>
102
114
 
103
115
  <!-- Footer slot -->
@@ -124,10 +136,10 @@ const handleSubmit = async (formData) => {
124
136
  {{ error }}
125
137
  </div>
126
138
  </slot>
127
- </FormKit>
139
+ </form>
128
140
  </div>
129
141
  </template>
130
142
 
131
143
  <style scoped>
132
- .login-form-container{background:var(--color-bg-primary);border:1px solid var(--color-border);border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);margin:0 auto;max-width:400px;padding:2em;width:100%}:deep(.formkit-form){display:flex;flex-direction:column;gap:1.5rem}.login-header{margin-bottom:1em;text-align:center}.login-title{color:var(--color-gray-800);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.login-subtitle{color:var(--color-gray-500);font-size:.875rem;line-height:1.5;margin:0}:deep(.formkit-inner){--fk-border-radius:8px;--fk-border-color:var(--color-border-light);--fk-border-color-focus:var(--color-primary);--fk-bg-color:var(--color-bg-secondary);--fk-bg-color-focus:var(--color-bg-primary);--fk-text-color:var(--color-gray-700);--fk-text-color-placeholder:var(--color-gray-400);--fk-font-size:0.875rem;--fk-padding:0.75em 1em;--fk-transition:all 0.2s ease-in-out}:deep(.formkit-inner .formkit-input){background-color:var(--fk-bg-color);border:1px solid var(--fk-border-color);border-radius:var(--fk-border-radius);box-sizing:border-box;color:var(--fk-text-color);font-size:var(--fk-font-size);padding:var(--fk-padding);transition:var(--fk-transition);width:100%}:deep(.formkit-inner .formkit-input:focus){background-color:var(--fk-bg-color-focus);border-color:var(--fk-border-color-focus);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}:deep(.formkit-inner .formkit-label){color:var(--color-gray-700);display:block;font-size:.875rem;font-weight:500;margin-bottom:.5em}:deep(.formkit-messages){font-size:.75rem;list-style:none;padding:0}:deep(.formkit-message){margin-top:.25em}:deep(.formkit-message[data-message-type=validation]){color:var(--color-error-alt)}:deep(.formkit-message[data-message-type=error]){color:var(--color-error)}:deep(.formkit-wrapper:has(input[type=checkbox])){align-items:center;display:flex;gap:.5rem}:deep(.formkit-input[type=checkbox]){margin:0;width:auto}:deep(.formkit-label:has(input[type=checkbox])){color:var(--color-gray-700);font-size:.875rem;margin:0}:deep(button[type=submit]){align-items:center;background-color:var(--color-primary);border:1px solid var(--color-primary);border-radius:8px;color:var(--color-white);cursor:pointer;display:flex;font-size:.875rem;font-weight:500;gap:.5rem;justify-content:center;min-height:44px;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}:deep(button[type=submit]:hover:not(:disabled)){background-color:var(--color-primary-dark);border-color:var(--color-primary-dark)}:deep(button[type=submit]:disabled){cursor:not-allowed;opacity:.6}.loading-spinner{animation:spin 1s linear infinite;border:2px solid var(--color-white);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.login-footer{margin-top:1em;text-align:center}.forgot-password{margin:0}.forgot-link{color:var(--color-primary);font-size:.875rem;text-decoration:none;transition:color .2s ease-in-out}.forgot-link:hover{color:var(--color-primary-dark);text-decoration:underline}.error-message{background-color:var(--color-error-light);border:1px solid var(--color-error-border);border-radius:8px;color:var(--color-error);font-size:.875rem;margin-top:1em;padding:.75em 1em;text-align:center}@media (max-width:480px){.login-form-container{margin:1em;padding:1.5em}.login-title{font-size:1.5rem}}
144
+ .login-form-container{background:var(--color-bg-primary);border:1px solid var(--color-border);border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);margin:0 auto;max-width:400px;padding:2em;width:100%}form{gap:1.5rem}.form-group,form{display:flex;flex-direction:column}label{display:block;font-weight:500;margin-bottom:.5em}input[type=email],input[type=password],label{color:var(--color-gray-700);font-size:.875rem}input[type=email],input[type=password]{background-color:var(--color-bg-secondary);border:1px solid var(--color-border-light);border-radius:8px;box-sizing:border-box;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}input[type=email]:focus,input[type=password]:focus{background-color:var(--color-bg-primary);border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}.login-header{margin-bottom:1em;text-align:center}.login-title{color:var(--color-gray-800);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.login-subtitle{color:var(--color-gray-500);font-size:.875rem;line-height:1.5;margin:0}.remember-me{align-items:center;display:flex;gap:.5rem}input[type=checkbox]{margin:0;width:auto}.remember-me label{color:var(--color-gray-700);font-size:.875rem;margin:0}button[type=submit]{align-items:center;background-color:var(--color-primary);border:1px solid var(--color-primary);border-radius:8px;color:var(--color-white);cursor:pointer;display:flex;font-size:.875rem;font-weight:500;gap:.5rem;justify-content:center;min-height:44px;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}button[type=submit]:hover:not(:disabled){background-color:var(--color-primary-dark);border-color:var(--color-primary-dark)}button[type=submit]:disabled{cursor:not-allowed;opacity:.6}.loading-spinner{animation:spin 1s linear infinite;border:2px solid var(--color-white);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.login-footer{margin-top:1em;text-align:center}.forgot-password{margin:0}.forgot-link{color:var(--color-primary);font-size:.875rem;text-decoration:none;transition:color .2s ease-in-out}.forgot-link:hover{color:var(--color-primary-dark);text-decoration:underline}.error-message{background-color:var(--color-error-light);border:1px solid var(--color-error-border);border-radius:8px;color:var(--color-error);font-size:.875rem;margin-top:1em;padding:.75em 1em;text-align:center}@media (max-width:480px){.login-form-container{margin:1em;padding:1.5em}.login-title{font-size:1.5rem}}
133
145
  </style>
@@ -1,21 +1,21 @@
1
1
  import type { LoginFormData, LoginFormProps, User } from '../../types.js';
2
- declare var __VLS_9: {}, __VLS_11: {}, __VLS_18: {}, __VLS_25: {}, __VLS_32: {}, __VLS_39: {}, __VLS_41: {
2
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_9: {}, __VLS_11: {}, __VLS_13: {
3
3
  error: string;
4
4
  };
5
5
  type __VLS_Slots = {} & {
6
- header?: (props: typeof __VLS_9) => any;
6
+ header?: (props: typeof __VLS_1) => any;
7
7
  } & {
8
- 'email-field'?: (props: typeof __VLS_11) => any;
8
+ 'email-field'?: (props: typeof __VLS_3) => any;
9
9
  } & {
10
- 'password-field'?: (props: typeof __VLS_18) => any;
10
+ 'password-field'?: (props: typeof __VLS_5) => any;
11
11
  } & {
12
- 'remember-me'?: (props: typeof __VLS_25) => any;
12
+ 'remember-me'?: (props: typeof __VLS_7) => any;
13
13
  } & {
14
- 'submit-button'?: (props: typeof __VLS_32) => any;
14
+ 'submit-button'?: (props: typeof __VLS_9) => any;
15
15
  } & {
16
- footer?: (props: typeof __VLS_39) => any;
16
+ footer?: (props: typeof __VLS_11) => any;
17
17
  } & {
18
- 'error-message'?: (props: typeof __VLS_41) => any;
18
+ 'error-message'?: (props: typeof __VLS_13) => any;
19
19
  };
20
20
  declare const __VLS_component: import("vue").DefineComponent<LoginFormProps, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
21
21
  error: (error: string) => any;
@@ -27,12 +27,17 @@ onMounted(() => {
27
27
  }
28
28
  });
29
29
  const canSubmit = computed(() => !!tokenFromUrl.value && !!emailFromUrl.value && !formInvalidDueToToken.value);
30
- const handleResetPassword = async (data) => {
30
+ const handleResetPassword = async () => {
31
31
  if (!canSubmit.value) {
32
32
  message.value = "Token or email is missing or invalid. Cannot reset password.";
33
33
  isError.value = true;
34
34
  return;
35
35
  }
36
+ if (formData.password !== formData.password_confirm) {
37
+ message.value = "Passwords do not match.";
38
+ isError.value = true;
39
+ return;
40
+ }
36
41
  loading.value = true;
37
42
  message.value = "";
38
43
  isError.value = false;
@@ -46,10 +51,8 @@ const handleResetPassword = async (data) => {
46
51
  body: JSON.stringify({
47
52
  token: tokenFromUrl.value,
48
53
  email: emailFromUrl.value,
49
- password: data.password,
50
- // Use password from form data
51
- password_confirmation: data.password_confirm
52
- // Use confirmation from form data
54
+ password: formData.password,
55
+ password_confirmation: formData.password_confirm
53
56
  })
54
57
  });
55
58
  const responseData = await response.json();
@@ -80,33 +83,37 @@ const handleResetPassword = async (data) => {
80
83
  <template>
81
84
  <div>
82
85
  <h2>Reset Password</h2>
83
- <FormKit
84
- v-slot="{ disabled }"
85
- v-model="formData"
86
- type="form"
87
- :actions="false"
88
- @submit="handleResetPassword"
89
- >
90
- <FormKit
91
- type="password"
92
- name="password"
93
- label="New Password"
94
- validation="required|length:8"
95
- placeholder="Enter new password"
96
- />
97
- <FormKit
98
- type="password"
99
- name="password_confirm"
100
- label="Confirm New Password"
101
- placeholder="Confirm new password"
102
- validation="required|confirm"
103
- validation-label="Password confirmation"
104
- />
105
- <FormKit
86
+ <form @submit.prevent="handleResetPassword">
87
+ <div class="form-group">
88
+ <label for="password">New Password</label>
89
+ <input
90
+ id="password"
91
+ v-model="formData.password"
92
+ type="password"
93
+ name="password"
94
+ placeholder="Enter new password"
95
+ required
96
+ minlength="8"
97
+ >
98
+ </div>
99
+ <div class="form-group">
100
+ <label for="password_confirm">Confirm New Password</label>
101
+ <input
102
+ id="password_confirm"
103
+ v-model="formData.password_confirm"
104
+ type="password"
105
+ name="password_confirm"
106
+ placeholder="Confirm new password"
107
+ required
108
+ minlength="8"
109
+ >
110
+ </div>
111
+ <button
106
112
  type="submit"
107
- :label="loading ? 'Resetting...' : 'Reset Password'"
108
- :disabled="Boolean(disabled) || loading || formInvalidDueToToken"
109
- />
113
+ :disabled="loading || formInvalidDueToToken"
114
+ >
115
+ {{ loading ? "Resetting..." : "Reset Password" }}
116
+ </button>
110
117
  <p
111
118
  v-if="message"
112
119
  :class="{ error: isError, success: !isError }"
@@ -114,10 +121,10 @@ const handleResetPassword = async (data) => {
114
121
  >
115
122
  {{ message }}
116
123
  </p>
117
- </FormKit>
124
+ </form>
118
125
  </div>
119
126
  </template>
120
127
 
121
128
  <style scoped>
122
- .error{color:red}.success{color:green}.form-message{font-size:.9rem;margin-top:1em}:deep(.formkit-outer){margin-bottom:1em}:deep(.formkit-label){display:block;font-weight:700;margin-bottom:.25em}:deep(.formkit-input input[type=email]),:deep(.formkit-input input[type=password]){border:1px solid var(--color-border-dark);border-radius:4px;box-sizing:border-box;padding:.75em;width:100%}:deep(.formkit-input input[type=submit]){background-color:var(--color-gray-800);border:none;border-radius:4px;color:#fff;cursor:pointer;padding:.75em 1.5em}:deep(.formkit-input input[type=submit]:disabled){background-color:var(--color-gray-500);cursor:not-allowed}:deep(.formkit-messages){list-style:none;margin-top:.25em;padding:0}:deep(.formkit-message){color:red;font-size:.8rem}
129
+ .error{color:red}.success{color:green}.form-message{font-size:.9rem;margin-top:1em}.form-group{margin-bottom:1em}label{display:block;font-weight:700;margin-bottom:.25em}input[type=password]{border:1px solid var(--color-border-dark);border-radius:4px;box-sizing:border-box;padding:.75em;width:100%}button[type=submit]{background-color:var(--color-gray-800);border:none;border-radius:4px;color:#fff;cursor:pointer;padding:.75em 1.5em}button[type=submit]:disabled{background-color:var(--color-gray-500);cursor:not-allowed}
123
130
  </style>
@@ -1,5 +1,5 @@
1
1
  import { defineNuxtPlugin, useRuntimeConfig } from "#app";
2
- import { checkTableExists } from "../utils/db";
2
+ import { checkTableExists } from "../utils";
3
3
  export default defineNuxtPlugin(async (_nuxtApp) => {
4
4
  if (import.meta.client) {
5
5
  return;
@@ -2,7 +2,7 @@ import { createError, defineEventHandler, readBody, setCookie } from "h3";
2
2
  import bcrypt from "bcrypt";
3
3
  import crypto from "node:crypto";
4
4
  import { useRuntimeConfig } from "#imports";
5
- import { useDb } from "../../../utils/db";
5
+ import { useDb } from "../../../utils";
6
6
  export default defineEventHandler(async (event) => {
7
7
  const body = await readBody(event);
8
8
  const { email, password } = body;
@@ -1,8 +1,7 @@
1
1
  import { createTransport } from "nodemailer";
2
2
  import crypto from "node:crypto";
3
3
  import bcrypt from "bcrypt";
4
- import { findUserByEmail, updateUserPassword } from "../../../utils/user";
5
- import { useDb } from "../../../utils/db";
4
+ import { findUserByEmail, updateUserPassword, useDb } from "../../../utils";
6
5
  const TOKEN_EXPIRATION_HOURS = 1;
7
6
  export const sendPasswordResetLink = async (email, options) => {
8
7
  const user = await findUserByEmail(email, options);
@@ -0,0 +1,58 @@
1
+ type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
2
+ type DatabaseConfig = {
3
+ path?: string;
4
+ host?: string;
5
+ port?: number;
6
+ user?: string;
7
+ password?: string;
8
+ database?: string;
9
+ };
10
+ interface ModuleOptions {
11
+ connector?: {
12
+ name: DatabaseType;
13
+ options: DatabaseConfig;
14
+ };
15
+ tables: {
16
+ migrations: string;
17
+ users: string;
18
+ personalAccessTokens: string;
19
+ passwordResetTokens: string;
20
+ };
21
+ /**
22
+ * Mailer configuration options for sending emails (e.g., password resets)
23
+ * Uses nodemailer
24
+ */
25
+ mailer?: MailerOptions;
26
+ /**
27
+ * Base URL for password reset links
28
+ * @default 'http://localhost:3000' // Example, will be set in module defaults
29
+ */
30
+ passwordResetBaseUrl?: string;
31
+ /**
32
+ * Skip database checks during module setup to prevent hanging
33
+ * @default false
34
+ */
35
+ skipDatabaseChecks?: boolean;
36
+ }
37
+ interface MailerOptions {
38
+ host: string;
39
+ port: number;
40
+ secure?: boolean;
41
+ auth: {
42
+ user: string;
43
+ pass: string;
44
+ };
45
+ defaults?: {
46
+ from: string;
47
+ };
48
+ }
49
+ interface User {
50
+ id: number;
51
+ email: string;
52
+ name: string;
53
+ password: string;
54
+ created_at: string;
55
+ updated_at: string;
56
+ }
57
+
58
+ export type { ModuleOptions as M, User as U };
@@ -0,0 +1,58 @@
1
+ type DatabaseType = 'sqlite' | 'mysql' | 'postgresql';
2
+ type DatabaseConfig = {
3
+ path?: string;
4
+ host?: string;
5
+ port?: number;
6
+ user?: string;
7
+ password?: string;
8
+ database?: string;
9
+ };
10
+ interface ModuleOptions {
11
+ connector?: {
12
+ name: DatabaseType;
13
+ options: DatabaseConfig;
14
+ };
15
+ tables: {
16
+ migrations: string;
17
+ users: string;
18
+ personalAccessTokens: string;
19
+ passwordResetTokens: string;
20
+ };
21
+ /**
22
+ * Mailer configuration options for sending emails (e.g., password resets)
23
+ * Uses nodemailer
24
+ */
25
+ mailer?: MailerOptions;
26
+ /**
27
+ * Base URL for password reset links
28
+ * @default 'http://localhost:3000' // Example, will be set in module defaults
29
+ */
30
+ passwordResetBaseUrl?: string;
31
+ /**
32
+ * Skip database checks during module setup to prevent hanging
33
+ * @default false
34
+ */
35
+ skipDatabaseChecks?: boolean;
36
+ }
37
+ interface MailerOptions {
38
+ host: string;
39
+ port: number;
40
+ secure?: boolean;
41
+ auth: {
42
+ user: string;
43
+ pass: string;
44
+ };
45
+ defaults?: {
46
+ from: string;
47
+ };
48
+ }
49
+ interface User {
50
+ id: number;
51
+ email: string;
52
+ name: string;
53
+ password: string;
54
+ created_at: string;
55
+ updated_at: string;
56
+ }
57
+
58
+ export type { ModuleOptions as M, User as U };