miolo 3.0.0-beta.181 → 3.0.0-beta.182

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miolo",
3
- "version": "3.0.0-beta.181",
3
+ "version": "3.0.0-beta.182",
4
4
  "description": "all-in-one koa-based server",
5
5
  "author": "Donato Lorenzo <donato@afialapis.com>",
6
6
  "contributors": [
@@ -45,12 +45,12 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@babel/plugin-proposal-decorators": "^7.29.0",
48
- "@babel/preset-env": "^7.29.2",
48
+ "@babel/preset-env": "^7.29.3",
49
49
  "@babel/preset-react": "^7.28.5",
50
- "@dotenvx/dotenvx": "^1.61.4",
50
+ "@dotenvx/dotenvx": "^1.64.0",
51
51
  "@koa/bodyparser": "^6.1.0",
52
52
  "@koa/cors": "^5.0.0",
53
- "@koa/router": "^15.4.0",
53
+ "@koa/router": "^15.5.0",
54
54
  "@maxmind/geoip2-node": "^6.3.4",
55
55
  "@rollup/plugin-alias": "^6.0.0",
56
56
  "@rollup/plugin-babel": "^7.0.0",
@@ -82,8 +82,8 @@
82
82
  "koa-ratelimit": "^6.0.0",
83
83
  "koa-session": "^7.0.2",
84
84
  "koa-static": "^5.0.0",
85
- "nanoid": "^5.1.9",
86
- "nodemailer": "^8.0.5",
85
+ "nanoid": "^5.1.11",
86
+ "nodemailer": "^8.0.7",
87
87
  "passport-google-oauth20": "^2.0.0",
88
88
  "passport-local": "^1.0.0",
89
89
  "rollup": "^4.60.2",
@@ -94,7 +94,7 @@
94
94
  "statuses": "^2.0.2",
95
95
  "tailwindcss": "^4.2.4",
96
96
  "tinguir": "^0.0.7",
97
- "vite": "^8.0.9",
97
+ "vite": "^8.0.10",
98
98
  "winston": "^3.19.0",
99
99
  "winston-daily-rotate-file": "^5.0.0",
100
100
  "yargs-parser": "^22.0.0"
@@ -23,6 +23,13 @@ export default function make_config_defaults() {
23
23
 
24
24
  static: {
25
25
  favicon: root("src/static/img/favicon.ico"),
26
+ headers: {
27
+ "/sw.js": {
28
+ "Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"
29
+ }
30
+ //"/index.html": { "Cache-Control": "no-cache, max-age=0" },
31
+ //"/": { "Cache-Control": "no-cache, max-age=0" }
32
+ },
26
33
  folders: {
27
34
  "/build": root("build"),
28
35
  "/static": root("src/static"),
@@ -66,7 +66,7 @@ const init_basic_auth_middleware = (app, options) => {
66
66
  return unauth_err()
67
67
  }
68
68
 
69
- const user = await auth_user(aaccount.username, aaccount.password, app.context.miolo)
69
+ const user = await auth_user(aaccount.username, aaccount.password, ctx)
70
70
 
71
71
  if (user === false || user === undefined || user === null) {
72
72
  return unauth_err()
@@ -4,7 +4,7 @@ import koa_mount from "koa-mount"
4
4
  import koa_serve from "koa-static"
5
5
 
6
6
  const init_static_middleware = (app, config) => {
7
- const { favicon, folders } = config
7
+ const { favicon, folders, headers } = config
8
8
 
9
9
  if (favicon && existsSync(favicon)) {
10
10
  app.context.miolo.logger.debug(`[static] Serving favicon from -${favicon}-`)
@@ -15,9 +15,25 @@ const init_static_middleware = (app, config) => {
15
15
  )
16
16
  }
17
17
 
18
+ // Do not cache some specific files
19
+ app.use(async (ctx, next) => {
20
+ for (const [fpath, fheaders] of Object.entries(headers)) {
21
+ if (ctx.path === fpath) {
22
+ app.context.miolo.logger.info(
23
+ `[static] Setting headers for -${fpath}- ${Object.keys(fheaders).join(", ")}`
24
+ )
25
+ for (const [key, value] of Object.entries(fheaders)) {
26
+ ctx.set(key, value)
27
+ }
28
+ break
29
+ }
30
+ }
31
+ await next()
32
+ })
33
+
18
34
  for (const [froute, fpath] of Object.entries(folders)) {
19
35
  if (fpath && existsSync(fpath)) {
20
- app.context.miolo.logger.debug(`[static] Mounting static folder ${froute} => -${fpath}-`)
36
+ app.context.miolo.logger.info(`[static] Mounting static folder ${froute} => -${fpath}-`)
21
37
  app.use(koa_mount(froute, koa_serve(fpath, { index: false })))
22
38
  } else {
23
39
  app.context.miolo.logger.warn(
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
3
3
  "files": {
4
4
  "includes": [
5
5
  "**/*",
@@ -43,10 +43,10 @@
43
43
  "farrapa": "^3.0.0-beta.10",
44
44
  "intre": "^3.0.0-beta.4",
45
45
  "joi": "^18.1.2",
46
- "lucide-react": "^1.8.0",
47
- "miolo-cli": "^3.0.0-beta.181",
46
+ "lucide-react": "^1.14.0",
47
+ "miolo-cli": "^3.0.0-beta.182",
48
48
  "miolo-model": "file:../miolo-model",
49
- "miolo-react": "^3.0.0-beta.181",
49
+ "miolo-react": "^3.0.0-beta.182",
50
50
  "next-themes": "^0.4.6",
51
51
  "radix-ui": "^1.4.3",
52
52
  "react": "^19.2.5",
@@ -60,8 +60,8 @@
60
60
  "tw-animate-css": "^1.4.0"
61
61
  },
62
62
  "devDependencies": {
63
- "@biomejs/biome": "2.4.12",
64
- "miolo": "^3.0.0-beta.181",
63
+ "@biomejs/biome": "2.4.14",
64
+ "miolo": "^3.0.0-beta.182",
65
65
  "sass-embedded": "^1.99.0"
66
66
  },
67
67
  "overrides": {
@@ -15,3 +15,19 @@ hydrateRoot(
15
15
  </BrowserRouter>
16
16
  </AppBrowser>
17
17
  )
18
+
19
+ // Comprobamos si el navegador del usuario soporta Service Workers
20
+ if ("serviceWorker" in navigator) {
21
+ // Esperamos a que la página cargue completamente para no afectar el rendimiento inicial
22
+ window.addEventListener("load", () => {
23
+ // Apuntamos a la URL pública donde Miolo está sirviendo el archivo
24
+ navigator.serviceWorker
25
+ .register("/sw.js")
26
+ .then((registration) => {
27
+ console.log("Service Worker registrado con éxito. Scope:", registration.scope)
28
+ })
29
+ .catch((error) => {
30
+ console.error("Fallo al registrar el Service Worker:", error)
31
+ })
32
+ })
33
+ }
@@ -1,5 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
+
3
4
  <head>
4
5
  <meta charset="utf-8">
5
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -9,12 +10,20 @@
9
10
  <meta name="keywords" content="miolo-sample" />
10
11
  <meta name="author" content="devel@afialapis.com" />
11
12
 
12
- <!-- Touch Icons - iOS and Android 2.1+ 180x180 pixels in size. -->
13
+ <!-- Touch Icons - iOS and Android 2.1+ 180x180 pixels in size. -->
13
14
  <!--<link rel="apple-touch-icon-precomposed" href="/favicon.ico"/>-->
14
15
  <!-- Firefox, Chrome, Safari, IE 11+ and Opera. 196x196 pixels in size. -->
15
- <link rel="icon" href="/favicon.ico"/>
16
+ <link rel="icon" href="/favicon.ico" />
17
+
18
+
19
+
20
+ <!-- PWA -->
21
+ <link rel="manifest" href="/manifest.json">
22
+ <meta name="theme-color" content="#fb64b6">
23
+
16
24
  </head>
17
25
 
18
26
  <body>
19
27
  </body>
20
- </html>
28
+
29
+ </html>
@@ -15,8 +15,11 @@ export default function Security() {
15
15
  const resp = await fetcher.post("/user/chpwd", params)
16
16
  if (resp.ok === true) {
17
17
  toast.success(resp.data.msg)
18
+ } else {
19
+ toast.error(resp.error)
18
20
  }
19
- return { ok: true, error: undefined }
21
+
22
+ return { ok: resp.ok, error: resp?.error }
20
23
  } catch (e) {
21
24
  return { ok: false, error: `Error al modificar la contraseña: ${e}` }
22
25
  }
@@ -1,11 +1,13 @@
1
1
  import { useCallback, useState } from "react"
2
2
  import useSessionContext from "#cli/context/session/useSessionContext.mjs"
3
+ import useUIContext from "#cli/context/ui/useUIContext.mjs"
3
4
  import TodoList from "#ns/models/TodoList.mjs"
4
5
  import TodosContext from "./TodosContext.jsx"
5
6
 
6
7
  const TodosProvider = ({ children }) => {
7
8
  // const [status, setStatus] = useState("loaded")
8
9
  const { useSsrData, fetcher, authenticated } = useSessionContext()
10
+ const { toast } = useUIContext()
9
11
  const [useCrud, setUseCrud] = useState(true)
10
12
 
11
13
  const {
@@ -17,17 +19,16 @@ const TodosProvider = ({ children }) => {
17
19
  loader: useCallback(
18
20
  async (_context, fetcher) => {
19
21
  //setStatus("loading")
20
- let data
21
- if (useCrud) {
22
- const res = await fetcher.read("/crud/todo")
23
- data = res.data
24
- } else {
25
- data = await fetcher.get("/api/todo/list")
22
+ const res = useCrud ? await fetcher.read("/crud/todo") : await fetcher.get("/api/todo/list")
23
+
24
+ if (!res.ok) {
25
+ toast.error(`Error loading todos: ${res.error}`)
26
26
  }
27
+ const data = res?.data || []
27
28
  //setStatus("loaded")
28
29
  return new TodoList(data.sort((a, b) => b.created_at - a.created_at))
29
30
  },
30
- [useCrud]
31
+ [useCrud, toast]
31
32
  )
32
33
  })
33
34
 
@@ -40,20 +41,22 @@ const TodosProvider = ({ children }) => {
40
41
  done: false
41
42
  }
42
43
 
43
- if (useCrud) {
44
- const { data: todoId } = await fetcher.upsave("crud/todo", todoObject)
45
- todoObject.id = todoId
46
- } else {
47
- const { data } = await fetcher.post("/api/todo/upsave", todoObject)
48
- todoObject.id = data.id
44
+ const res = useCrud
45
+ ? await fetcher.upsave("crud/todo", todoObject)
46
+ : await fetcher.post("/api/todo/upsave", todoObject)
47
+
48
+ if (!res.ok) {
49
+ toast.error(`Error adding todo: ${res.error}`)
49
50
  }
50
51
 
52
+ todoObject.id = res?.data?.id
53
+
51
54
  setTodoList([todoObject, ...todoList])
52
55
  }
53
56
 
54
57
  addIt()
55
58
  },
56
- [fetcher, todoList, setTodoList, useCrud]
59
+ [fetcher, todoList, setTodoList, useCrud, toast]
57
60
  )
58
61
 
59
62
  const toggleTodo = useCallback(
@@ -65,19 +68,21 @@ const TodosProvider = ({ children }) => {
65
68
 
66
69
  setTodoList(nTodoList)
67
70
 
68
- if (useCrud) {
69
- await fetcher.upsave("crud/todo", nTodoList[selectedTodoIndex])
70
- } else {
71
- await fetcher.post("/api/todo/toggle", {
72
- id: todoId,
73
- done: nTodoList[selectedTodoIndex].done
74
- })
71
+ const res = useCrud
72
+ ? await fetcher.upsave("crud/todo", nTodoList[selectedTodoIndex])
73
+ : await fetcher.post("/api/todo/toggle", {
74
+ id: todoId,
75
+ done: nTodoList[selectedTodoIndex].done
76
+ })
77
+
78
+ if (!res.ok) {
79
+ toast.error(`Error toggling todo: ${res.error}`)
75
80
  }
76
81
  }
77
82
 
78
83
  toggleIt()
79
84
  },
80
- [fetcher, todoList, setTodoList, useCrud]
85
+ [fetcher, todoList, setTodoList, useCrud, toast]
81
86
  )
82
87
 
83
88
  const removeTodo = useCallback(
@@ -90,38 +95,41 @@ const TodosProvider = ({ children }) => {
90
95
 
91
96
  setTodoList(nTodoList)
92
97
 
93
- if (useCrud) {
94
- await fetcher.remove("crud/todo", todoId)
98
+ const res = useCrud
99
+ ? await fetcher.remove("crud/todo", todoId)
100
+ : await fetcher.post("api/todo/delete", { id: todoId })
101
+
102
+ if (!res.ok) {
103
+ toast.error(`Error removing todo: ${res.error}`)
95
104
  } else {
96
- await fetcher.post("api/todo/delete", { id: todoId })
105
+ toast.info(`Todo removed successfully`)
97
106
  }
98
107
  },
99
- [fetcher, todoList, setTodoList, useCrud]
108
+ [fetcher, todoList, setTodoList, useCrud, toast]
100
109
  )
101
110
 
102
111
  const checkLastHours = useCallback(
103
112
  async ({ hours }) => {
104
- const {
105
- data: { count }
106
- } = await fetcher.get("api/todo/last_hours", { hours })
107
- alert(`You have added ${count} todos in the last ${hours} hours`)
113
+ const res = await fetcher.get("api/todo/last_hours", { hours })
114
+ if (res.ok === true) {
115
+ toast.info(`You have added ${res?.data?.count} todos in the last ${hours} hours`)
116
+ } else {
117
+ toast.error(`Error checking last hours: ${res.error}`)
118
+ }
108
119
  },
109
- [fetcher]
120
+ [fetcher, toast]
110
121
  )
111
122
 
112
123
  const insertFakeTodo = useCallback(async () => {
113
- const {
114
- ok,
115
- error,
116
- data: { id }
117
- } = await fetcher.post("api/todo/fake", { done: true })
118
- if (!ok) {
119
- alert(`Error adding fake todo: ${error}`)
124
+ const res = await fetcher.post("api/todo/fake", { done: true })
125
+
126
+ if (!res.ok) {
127
+ toast.error(`Error adding fake todo: ${res.error}`)
120
128
  } else {
121
- alert(`Fake todo added with id ${id}`)
129
+ toast.info(`Fake todo added with id ${res?.data?.id}`)
122
130
  }
123
131
  refreshTodoList()
124
- }, [fetcher, refreshTodoList])
132
+ }, [fetcher, refreshTodoList, toast])
125
133
 
126
134
  return (
127
135
  <TodosContext.Provider
@@ -1,9 +1,9 @@
1
1
  import { db_auth_user } from "#server/db/io/users/auth.mjs"
2
2
 
3
- const local_auth_user = async (username, password, miolo) => {
4
- const [user, _msg] = await db_auth_user(miolo, username, password)
3
+ const local_auth_user = async (username, password, ctx) => {
4
+ const [user, msg] = await db_auth_user(ctx.miolo, username, password)
5
5
 
6
- return user
6
+ return [user, msg]
7
7
  }
8
8
 
9
9
  export default {
@@ -5,7 +5,7 @@ export async function r_todo_list(ctx, params) {
5
5
  try {
6
6
  ctx.miolo.logger.info(`[r_todo_list] Reading todo list`)
7
7
 
8
- const res = await db_todo_read(ctx, params)
8
+ const res = await db_todo_read(ctx, { filter: {}, options: {} })
9
9
 
10
10
  ctx.miolo.logger.info(`[r_todo_list] Read todo list (${res.length})`)
11
11
  return { ok: true, data: res }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "miolo-sample",
3
+ "short_name": "miolo-sample",
4
+ "start_url": "/",
5
+ "display": "standalone",
6
+ "background_color": "#ffffff",
7
+ "theme_color": "#fb64b6",
8
+ "description": "A simple PWA for miolo-sample",
9
+ "icons": [
10
+ {
11
+ "src": "/favicon.ico",
12
+ "sizes": "30x30",
13
+ "type": "image/x-icon"
14
+ },
15
+ {
16
+ "src": "/static/img/miolo_logo.png",
17
+ "sizes": "600x600",
18
+ "type": "image/png"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,79 @@
1
+ const CACHE_NAME = "miolo-sample-cache-v1"
2
+
3
+ // 1. EVENTO INSTALL: Ocurre la primera vez que el usuario entra.
4
+ // Aquí cacheamos el "App Shell" (lo básico para que la app abra sin red).
5
+ self.addEventListener("install", (event) => {
6
+ /*console.log(`[miolo][sw] installing`)
7
+ event.waitUntil(
8
+ caches.open(CACHE_NAME).then((cache) => {
9
+ console.log(`[miolo][sw] installing - caching main`)
10
+ // Rutas estáticas clave (ajusta según cómo sirva Miolo tus archivos)
11
+ return cache.addAll([
12
+ "/"
13
+ // "/index.html",
14
+ // "/icon-192x192.png"
15
+ // Si sabes las URLs de tu JS/CSS, añádelas aquí.
16
+ ])
17
+ })
18
+ )
19
+ self.skipWaiting()*/
20
+ })
21
+
22
+ // 2. EVENTO ACTIVATE: Limpia cachés antiguas si cambias el CACHE_NAME
23
+ self.addEventListener("activate", (event) => {
24
+ console.log(`[miolo][sw] activating`)
25
+ event.waitUntil(
26
+ caches.keys().then((cacheNames) => {
27
+ const oldCaches = cacheNames.filter((name) => name !== CACHE_NAME)
28
+ console.log(`[miolo][sw] activating - cleaning old caches ${oldCaches}`)
29
+ return Promise.all(oldCaches.map((name) => caches.delete(name)))
30
+ })
31
+ )
32
+ self.clients.claim()
33
+ })
34
+
35
+ // 3. EVENTO FETCH: El núcleo de tu estrategia
36
+ self.addEventListener("fetch", (event) => {
37
+ /*console.log(`[miolo][sw] fetching`)
38
+ // ESTRATEGIA PARA LLAMADAS POST
39
+ if (event.request.method === "POST" || event.request.method === "PUT") {
40
+ event.respondWith(
41
+ fetch(event.request).catch(() => {
42
+ console.log(`[miolo][sw] fetching - fetch failed, returning 503 error`)
43
+ // Si el fetch falla (porque no hay red), devolvemos un error controlado 503
44
+ return new Response(
45
+ JSON.stringify({ error: "Estás sin conexión. No se pueden guardar los cambios." }),
46
+ {
47
+ headers: { "Content-Type": "application/json" },
48
+ status: 503,
49
+ statusText: "Service Unavailable"
50
+ }
51
+ )
52
+ })
53
+ )
54
+ return // Cortamos aquí para los POST
55
+ }
56
+
57
+ // ESTRATEGIA PARA LLAMADAS GET (Network First, fallback to Cache)
58
+ // Intenta ir a internet siempre para tener los resultados frescos.
59
+ // Si falla, tira de lo que tenga guardado en caché.
60
+ event.respondWith(
61
+ fetch(event.request)
62
+ .then((networkResponse) => {
63
+ console.log(`[miolo][sw] fetching - fetch ok, caching it`)
64
+ // Guardamos una copia en la caché para la próxima vez que se quede offline
65
+ const responseClone = networkResponse.clone()
66
+ caches.open(CACHE_NAME).then((cache) => {
67
+ // No cacheamos extensiones raras del navegador
68
+ if (event.request.url.startsWith("http")) {
69
+ cache.put(event.request, responseClone)
70
+ }
71
+ })
72
+ return networkResponse
73
+ })
74
+ .catch(() => {
75
+ // Si no hay internet, devuelve la versión guardada en caché
76
+ return caches.match(event.request)
77
+ })
78
+ )*/
79
+ })
@@ -1,2 +0,0 @@
1
- User-agent: *
2
- Disallow: /