Subir archivos a "/"
This commit is contained in:
29
App.svelte
Normal file
29
App.svelte
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { LoginForm, Dashboard } from './lib/components';
|
||||||
|
import { authStore } from './lib/stores';
|
||||||
|
|
||||||
|
const auth = $derived($authStore);
|
||||||
|
|
||||||
|
function handleLoginSuccess() {
|
||||||
|
console.log('Login exitoso!', auth.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogout() {
|
||||||
|
authStore.logout();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !auth.isAuthenticated}
|
||||||
|
<!-- Mostrar formulario de login si no está autenticado -->
|
||||||
|
<div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 py-12 px-4">
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<LoginForm onSuccess={handleLoginSuccess} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<!-- Mostrar Dashboard cuando está autenticado -->
|
||||||
|
<Dashboard
|
||||||
|
username={auth.user?.username || 'Usuario'}
|
||||||
|
onLogout={handleLogout}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
136
CHANGELOG.md
Normal file
136
CHANGELOG.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [v1.1.2](https://github.com/ljharb/function-bind/compare/v1.1.1...v1.1.2) - 2023-10-12
|
||||||
|
|
||||||
|
### Merged
|
||||||
|
|
||||||
|
- Point to the correct file [`#16`](https://github.com/ljharb/function-bind/pull/16)
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [Tests] migrate tests to Github Actions [`4f8b57c`](https://github.com/ljharb/function-bind/commit/4f8b57c02f2011fe9ae353d5e74e8745f0988af8)
|
||||||
|
- [Tests] remove `jscs` [`90eb2ed`](https://github.com/ljharb/function-bind/commit/90eb2edbeefd5b76cd6c3a482ea3454db169b31f)
|
||||||
|
- [meta] update `.gitignore` [`53fcdc3`](https://github.com/ljharb/function-bind/commit/53fcdc371cd66634d6e9b71c836a50f437e89fed)
|
||||||
|
- [Tests] up to `node` `v11.10`, `v10.15`, `v9.11`, `v8.15`, `v6.16`, `v4.9`; use `nvm install-latest-npm`; run audit script in tests [`1fe8f6e`](https://github.com/ljharb/function-bind/commit/1fe8f6e9aed0dfa8d8b3cdbd00c7f5ea0cd2b36e)
|
||||||
|
- [meta] add `auto-changelog` [`1921fcb`](https://github.com/ljharb/function-bind/commit/1921fcb5b416b63ffc4acad051b6aad5722f777d)
|
||||||
|
- [Robustness] remove runtime dependency on all builtins except `.apply` [`f743e61`](https://github.com/ljharb/function-bind/commit/f743e61aa6bb2360358c04d4884c9db853d118b7)
|
||||||
|
- Docs: enable badges; update wording [`503cb12`](https://github.com/ljharb/function-bind/commit/503cb12d998b5f91822776c73332c7adcd6355dd)
|
||||||
|
- [readme] update badges [`290c5db`](https://github.com/ljharb/function-bind/commit/290c5dbbbda7264efaeb886552a374b869a4bb48)
|
||||||
|
- [Tests] switch to nyc for coverage [`ea360ba`](https://github.com/ljharb/function-bind/commit/ea360ba907fc2601ed18d01a3827fa2d3533cdf8)
|
||||||
|
- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape` [`cae5e9e`](https://github.com/ljharb/function-bind/commit/cae5e9e07a5578dc6df26c03ee22851ce05b943c)
|
||||||
|
- [meta] add `funding` field; create FUNDING.yml [`c9f4274`](https://github.com/ljharb/function-bind/commit/c9f4274aa80ea3aae9657a3938fdba41a3b04ca6)
|
||||||
|
- [Tests] fix eslint errors from #15 [`f69aaa2`](https://github.com/ljharb/function-bind/commit/f69aaa2beb2fdab4415bfb885760a699d0b9c964)
|
||||||
|
- [actions] fix permissions [`99a0cd9`](https://github.com/ljharb/function-bind/commit/99a0cd9f3b5bac223a0d572f081834cd73314be7)
|
||||||
|
- [meta] use `npmignore` to autogenerate an npmignore file [`f03b524`](https://github.com/ljharb/function-bind/commit/f03b524ca91f75a109a5d062f029122c86ecd1ae)
|
||||||
|
- [Dev Deps] update `@ljharb/eslint‑config`, `eslint`, `tape` [`7af9300`](https://github.com/ljharb/function-bind/commit/7af930023ae2ce7645489532821e4fbbcd7a2280)
|
||||||
|
- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `covert`, `tape` [`64a9127`](https://github.com/ljharb/function-bind/commit/64a9127ab0bd331b93d6572eaf6e9971967fc08c)
|
||||||
|
- [Tests] use `aud` instead of `npm audit` [`e75069c`](https://github.com/ljharb/function-bind/commit/e75069c50010a8fcce2a9ce2324934c35fdb4386)
|
||||||
|
- [Dev Deps] update `@ljharb/eslint-config`, `aud`, `tape` [`d03555c`](https://github.com/ljharb/function-bind/commit/d03555ca59dea3b71ce710045e4303b9e2619e28)
|
||||||
|
- [meta] add `safe-publish-latest` [`9c8f809`](https://github.com/ljharb/function-bind/commit/9c8f8092aed027d7e80c94f517aa892385b64f09)
|
||||||
|
- [Dev Deps] update `@ljharb/eslint-config`, `tape` [`baf6893`](https://github.com/ljharb/function-bind/commit/baf6893e27f5b59abe88bc1995e6f6ed1e527397)
|
||||||
|
- [meta] create SECURITY.md [`4db1779`](https://github.com/ljharb/function-bind/commit/4db17799f1f28ae294cb95e0081ca2b591c3911b)
|
||||||
|
- [Tests] add `npm run audit` [`c8b38ec`](https://github.com/ljharb/function-bind/commit/c8b38ec40ed3f85dabdee40ed4148f1748375bc2)
|
||||||
|
- Revert "Point to the correct file" [`05cdf0f`](https://github.com/ljharb/function-bind/commit/05cdf0fa205c6a3c5ba40bbedd1dfa9874f915c9)
|
||||||
|
|
||||||
|
## [v1.1.1](https://github.com/ljharb/function-bind/compare/v1.1.0...v1.1.1) - 2017-08-28
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- [Tests] up to `node` `v8`; newer npm breaks on older node; fix scripts [`817f7d2`](https://github.com/ljharb/function-bind/commit/817f7d28470fdbff8ef608d4d565dd4d1430bc5e)
|
||||||
|
- [Dev Deps] update `eslint`, `jscs`, `tape`, `@ljharb/eslint-config` [`854288b`](https://github.com/ljharb/function-bind/commit/854288b1b6f5c555f89aceb9eff1152510262084)
|
||||||
|
- [Dev Deps] update `tape`, `jscs`, `eslint`, `@ljharb/eslint-config` [`83e639f`](https://github.com/ljharb/function-bind/commit/83e639ff74e6cd6921285bccec22c1bcf72311bd)
|
||||||
|
- Only apps should have lockfiles [`5ed97f5`](https://github.com/ljharb/function-bind/commit/5ed97f51235c17774e0832e122abda0f3229c908)
|
||||||
|
- Use a SPDX-compliant “license” field. [`5feefea`](https://github.com/ljharb/function-bind/commit/5feefea0dc0193993e83e5df01ded424403a5381)
|
||||||
|
|
||||||
|
## [v1.1.0](https://github.com/ljharb/function-bind/compare/v1.0.2...v1.1.0) - 2016-02-14
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Update `eslint`, `tape`; use my personal shared `eslint` config [`9c9062a`](https://github.com/ljharb/function-bind/commit/9c9062abbe9dd70b59ea2c3a3c3a81f29b457097)
|
||||||
|
- Add `npm run eslint` [`dd96c56`](https://github.com/ljharb/function-bind/commit/dd96c56720034a3c1ffee10b8a59a6f7c53e24ad)
|
||||||
|
- [New] return the native `bind` when available. [`82186e0`](https://github.com/ljharb/function-bind/commit/82186e03d73e580f95ff167e03f3582bed90ed72)
|
||||||
|
- [Dev Deps] update `tape`, `jscs`, `eslint`, `@ljharb/eslint-config` [`a3dd767`](https://github.com/ljharb/function-bind/commit/a3dd76720c795cb7f4586b0544efabf8aa107b8b)
|
||||||
|
- Update `eslint` [`3dae2f7`](https://github.com/ljharb/function-bind/commit/3dae2f7423de30a2d20313ddb1edc19660142fe9)
|
||||||
|
- Update `tape`, `covert`, `jscs` [`a181eee`](https://github.com/ljharb/function-bind/commit/a181eee0cfa24eb229c6e843a971f36e060a2f6a)
|
||||||
|
- [Tests] up to `node` `v5.6`, `v4.3` [`964929a`](https://github.com/ljharb/function-bind/commit/964929a6a4ddb36fb128de2bcc20af5e4f22e1ed)
|
||||||
|
- Test up to `io.js` `v2.1` [`2be7310`](https://github.com/ljharb/function-bind/commit/2be7310f2f74886a7124ca925be411117d41d5ea)
|
||||||
|
- Update `tape`, `jscs`, `eslint`, `@ljharb/eslint-config` [`45f3d68`](https://github.com/ljharb/function-bind/commit/45f3d6865c6ca93726abcef54febe009087af101)
|
||||||
|
- [Dev Deps] update `tape`, `jscs` [`6e1340d`](https://github.com/ljharb/function-bind/commit/6e1340d94642deaecad3e717825db641af4f8b1f)
|
||||||
|
- [Tests] up to `io.js` `v3.3`, `node` `v4.1` [`d9bad2b`](https://github.com/ljharb/function-bind/commit/d9bad2b778b1b3a6dd2876087b88b3acf319f8cc)
|
||||||
|
- Update `eslint` [`935590c`](https://github.com/ljharb/function-bind/commit/935590caa024ab356102e4858e8fc315b2ccc446)
|
||||||
|
- [Dev Deps] update `jscs`, `eslint`, `@ljharb/eslint-config` [`8c9a1ef`](https://github.com/ljharb/function-bind/commit/8c9a1efd848e5167887aa8501857a0940a480c57)
|
||||||
|
- Test on `io.js` `v2.2` [`9a3a38c`](https://github.com/ljharb/function-bind/commit/9a3a38c92013aed6e108666e7bd40969b84ac86e)
|
||||||
|
- Run `travis-ci` tests on `iojs` and `node` v0.12; speed up builds; allow 0.8 failures. [`69afc26`](https://github.com/ljharb/function-bind/commit/69afc2617405b147dd2a8d8ae73ca9e9283f18b4)
|
||||||
|
- [Dev Deps] Update `tape`, `eslint` [`36c1be0`](https://github.com/ljharb/function-bind/commit/36c1be0ab12b45fe5df6b0fdb01a5d5137fd0115)
|
||||||
|
- Update `tape`, `jscs` [`98d8303`](https://github.com/ljharb/function-bind/commit/98d8303cd5ca1c6b8f985469f86b0d44d7d45f6e)
|
||||||
|
- Update `jscs` [`9633a4e`](https://github.com/ljharb/function-bind/commit/9633a4e9fbf82051c240855166e468ba8ba0846f)
|
||||||
|
- Update `tape`, `jscs` [`c80ef0f`](https://github.com/ljharb/function-bind/commit/c80ef0f46efc9791e76fa50de4414092ac147831)
|
||||||
|
- Test up to `io.js` `v3.0` [`7e2c853`](https://github.com/ljharb/function-bind/commit/7e2c8537d52ab9cf5a655755561d8917684c0df4)
|
||||||
|
- Test on `io.js` `v2.4` [`5a199a2`](https://github.com/ljharb/function-bind/commit/5a199a27ba46795ba5eaf0845d07d4b8232895c9)
|
||||||
|
- Test on `io.js` `v2.3` [`a511b88`](https://github.com/ljharb/function-bind/commit/a511b8896de0bddf3b56862daa416c701f4d0453)
|
||||||
|
- Fixing a typo from 822b4e1938db02dc9584aa434fd3a45cb20caf43 [`732d6b6`](https://github.com/ljharb/function-bind/commit/732d6b63a9b33b45230e630dbcac7a10855d3266)
|
||||||
|
- Update `jscs` [`da52a48`](https://github.com/ljharb/function-bind/commit/da52a4886c06d6490f46ae30b15e4163ba08905d)
|
||||||
|
- Lock covert to v1.0.0. [`d6150fd`](https://github.com/ljharb/function-bind/commit/d6150fda1e6f486718ebdeff823333d9e48e7430)
|
||||||
|
|
||||||
|
## [v1.0.2](https://github.com/ljharb/function-bind/compare/v1.0.1...v1.0.2) - 2014-10-04
|
||||||
|
|
||||||
|
## [v1.0.1](https://github.com/ljharb/function-bind/compare/v1.0.0...v1.0.1) - 2014-10-03
|
||||||
|
|
||||||
|
### Merged
|
||||||
|
|
||||||
|
- make CI build faster [`#3`](https://github.com/ljharb/function-bind/pull/3)
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Using my standard jscs.json [`d8ee94c`](https://github.com/ljharb/function-bind/commit/d8ee94c993eff0a84cf5744fe6a29627f5cffa1a)
|
||||||
|
- Adding `npm run lint` [`7571ab7`](https://github.com/ljharb/function-bind/commit/7571ab7dfdbd99b25a1dbb2d232622bd6f4f9c10)
|
||||||
|
- Using consistent indentation [`e91a1b1`](https://github.com/ljharb/function-bind/commit/e91a1b13a61e99ec1e530e299b55508f74218a95)
|
||||||
|
- Updating jscs [`7e17892`](https://github.com/ljharb/function-bind/commit/7e1789284bc629bc9c1547a61c9b227bbd8c7a65)
|
||||||
|
- Using consistent quotes [`c50b57f`](https://github.com/ljharb/function-bind/commit/c50b57fcd1c5ec38320979c837006069ebe02b77)
|
||||||
|
- Adding keywords [`cb94631`](https://github.com/ljharb/function-bind/commit/cb946314eed35f21186a25fb42fc118772f9ee00)
|
||||||
|
- Directly export a function expression instead of using a declaration, and relying on hoisting. [`5a33c5f`](https://github.com/ljharb/function-bind/commit/5a33c5f45642de180e0d207110bf7d1843ceb87c)
|
||||||
|
- Naming npm URL and badge in README; use SVG [`2aef8fc`](https://github.com/ljharb/function-bind/commit/2aef8fcb79d54e63a58ae557c4e60949e05d5e16)
|
||||||
|
- Naming deps URLs in README [`04228d7`](https://github.com/ljharb/function-bind/commit/04228d766670ee45ca24e98345c1f6a7621065b5)
|
||||||
|
- Naming travis-ci URLs in README; using SVG [`62c810c`](https://github.com/ljharb/function-bind/commit/62c810c2f54ced956cd4d4ab7b793055addfe36e)
|
||||||
|
- Make sure functions are invoked correctly (also passing coverage tests) [`2b289b4`](https://github.com/ljharb/function-bind/commit/2b289b4dfbf037ffcfa4dc95eb540f6165e9e43a)
|
||||||
|
- Removing the strict mode pragmas; they make tests fail. [`1aa701d`](https://github.com/ljharb/function-bind/commit/1aa701d199ddc3782476e8f7eef82679be97b845)
|
||||||
|
- Adding myself as a contributor [`85fd57b`](https://github.com/ljharb/function-bind/commit/85fd57b0860e5a7af42de9a287f3f265fc6d72fc)
|
||||||
|
- Adding strict mode pragmas [`915b08e`](https://github.com/ljharb/function-bind/commit/915b08e084c86a722eafe7245e21db74aa21ca4c)
|
||||||
|
- Adding devDeps URLs to README [`4ccc731`](https://github.com/ljharb/function-bind/commit/4ccc73112c1769859e4ca3076caf4086b3cba2cd)
|
||||||
|
- Fixing the description. [`a7a472c`](https://github.com/ljharb/function-bind/commit/a7a472cf649af515c635cf560fc478fbe48999c8)
|
||||||
|
- Using a function expression instead of a function declaration. [`b5d3e4e`](https://github.com/ljharb/function-bind/commit/b5d3e4ea6aaffc63888953eeb1fbc7ff45f1fa14)
|
||||||
|
- Updating tape [`f086be6`](https://github.com/ljharb/function-bind/commit/f086be6029fb56dde61a258c1340600fa174d1e0)
|
||||||
|
- Updating jscs [`5f9bdb3`](https://github.com/ljharb/function-bind/commit/5f9bdb375ab13ba48f30852aab94029520c54d71)
|
||||||
|
- Updating jscs [`9b409ba`](https://github.com/ljharb/function-bind/commit/9b409ba6118e23395a4e5d83ef39152aab9d3bfc)
|
||||||
|
- Run coverage as part of tests. [`8e1b6d4`](https://github.com/ljharb/function-bind/commit/8e1b6d459f047d1bd4fee814e01247c984c80bd0)
|
||||||
|
- Run linter as part of tests [`c1ca83f`](https://github.com/ljharb/function-bind/commit/c1ca83f832df94587d09e621beba682fabfaa987)
|
||||||
|
- Updating covert [`701e837`](https://github.com/ljharb/function-bind/commit/701e83774b57b4d3ef631e1948143f43a72f4bb9)
|
||||||
|
|
||||||
|
## [v1.0.0](https://github.com/ljharb/function-bind/compare/v0.2.0...v1.0.0) - 2014-08-09
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Make sure old and unstable nodes don't fail Travis [`27adca3`](https://github.com/ljharb/function-bind/commit/27adca34a4ab6ad67b6dfde43942a1b103ce4d75)
|
||||||
|
- Fixing an issue when the bound function is called as a constructor in ES3. [`e20122d`](https://github.com/ljharb/function-bind/commit/e20122d267d92ce553859b280cbbea5d27c07731)
|
||||||
|
- Adding `npm run coverage` [`a2e29c4`](https://github.com/ljharb/function-bind/commit/a2e29c4ecaef9e2f6cd1603e868c139073375502)
|
||||||
|
- Updating tape [`b741168`](https://github.com/ljharb/function-bind/commit/b741168b12b235b1717ff696087645526b69213c)
|
||||||
|
- Upgrading tape [`63631a0`](https://github.com/ljharb/function-bind/commit/63631a04c7fbe97cc2fa61829cc27246d6986f74)
|
||||||
|
- Updating tape [`363cb46`](https://github.com/ljharb/function-bind/commit/363cb46dafb23cb3e347729a22f9448051d78464)
|
||||||
|
|
||||||
|
## v0.2.0 - 2014-03-23
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
- Updating test coverage to match es5-shim. [`aa94d44`](https://github.com/ljharb/function-bind/commit/aa94d44b8f9d7f69f10e060db7709aa7a694e5d4)
|
||||||
|
- initial [`942ee07`](https://github.com/ljharb/function-bind/commit/942ee07e94e542d91798137bc4b80b926137e066)
|
||||||
|
- Setting the bound function's length properly. [`079f46a`](https://github.com/ljharb/function-bind/commit/079f46a2d3515b7c0b308c2c13fceb641f97ca25)
|
||||||
|
- Ensuring that some older browsers will throw when given a regex. [`36ac55b`](https://github.com/ljharb/function-bind/commit/36ac55b87f460d4330253c92870aa26fbfe8227f)
|
||||||
|
- Removing npm scripts that don't have dependencies [`9d2be60`](https://github.com/ljharb/function-bind/commit/9d2be600002cb8bc8606f8f3585ad3e05868c750)
|
||||||
|
- Updating tape [`297a4ac`](https://github.com/ljharb/function-bind/commit/297a4acc5464db381940aafb194d1c88f4e678f3)
|
||||||
|
- Skipping length tests for now. [`d9891ea`](https://github.com/ljharb/function-bind/commit/d9891ea4d2aaffa69f408339cdd61ff740f70565)
|
||||||
|
- don't take my tea [`dccd930`](https://github.com/ljharb/function-bind/commit/dccd930bfd60ea10cb178d28c97550c3bc8c1e07)
|
||||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# Instalar dependencias del sistema
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
postgresql-client \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copiar requirements
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Instalar dependencias de Python
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copiar el código de la aplicación
|
||||||
|
COPY ./app /code/app
|
||||||
|
COPY ./alembic /code/alembic
|
||||||
|
COPY ./alembic.ini /code/alembic.ini
|
||||||
|
COPY ./migrate.sh /code/migrate.sh
|
||||||
|
RUN sed -i 's/\r$//' /code/migrate.sh && chmod +x /code/migrate.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Exponer el puerto
|
||||||
|
EXPOSE 8009
|
||||||
|
|
||||||
|
# Comando para ejecutar la aplicación
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8009", "--reload"]
|
||||||
8
Dockerfile.dev
Normal file
8
Dockerfile.dev
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
CMD ["npm", "run", "dev", "--", "--host"]
|
||||||
29
Dockerfile.prod
Normal file
29
Dockerfile.prod
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Etapa de construcción
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copiar archivos de dependencias
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN apk update && apk add --no-cache ca-certificates wget && update-ca-certificates
|
||||||
|
RUN npm config set strict-ssl false
|
||||||
|
# Instalar todas las dependencias para construcción
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copiar el resto del código
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Crear archivo .env
|
||||||
|
RUN echo "VITE_API_BASE_URL=http://localhost:8009" > .env
|
||||||
|
|
||||||
|
# Construir la aplicación
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Etapa de producción
|
||||||
|
FROM nginx:alpine AS production
|
||||||
|
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
66
HISTORY.md
Normal file
66
HISTORY.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
2.7.0 / 2017-09-13
|
||||||
|
==================
|
||||||
|
|
||||||
|
* feat: support fs.copyFile (#58)
|
||||||
|
|
||||||
|
2.6.0 / 2016-11-22
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Added fdatasync to fs api (#46)
|
||||||
|
|
||||||
|
2.5.0 / 2016-11-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* feat: support fs.mkdtemp
|
||||||
|
|
||||||
|
2.4.0 / 2016-03-23
|
||||||
|
==================
|
||||||
|
|
||||||
|
* add `fs.truncate()` [#34](https://github.com/normalize/mz/pull/34)
|
||||||
|
|
||||||
|
2.3.1 / 2016-02-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* update `any-promise@v1`
|
||||||
|
|
||||||
|
2.3.0 / 2016-01-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* feat(package): switch to `any-promise` to support more promise engines
|
||||||
|
|
||||||
|
2.2.0 / 2016-01-24
|
||||||
|
==================
|
||||||
|
|
||||||
|
* feat(package): add index.js to files
|
||||||
|
|
||||||
|
2.1.0 / 2015-10-15
|
||||||
|
==================
|
||||||
|
|
||||||
|
* support for readline library
|
||||||
|
|
||||||
|
2.0.0 / 2015-05-24
|
||||||
|
==================
|
||||||
|
|
||||||
|
* support callbacks as well
|
||||||
|
|
||||||
|
1.2.0 / 2014-12-16
|
||||||
|
==================
|
||||||
|
|
||||||
|
* refactor promisification to `thenify` and `thenify-all`
|
||||||
|
|
||||||
|
1.1.0 / 2014-11-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* use `graceful-fs` if available
|
||||||
|
|
||||||
|
1.0.1 / 2014-08-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* don't use `bluebird.promisify()` - unnecessarily wraps runtime errors, causing issues
|
||||||
|
|
||||||
|
1.0.0 / 2014-06-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* use `bluebird` by default if found
|
||||||
|
* support node 0.8
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2016, Jon Schlinkert
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
20
LICENSE-MIT.txt
Normal file
20
LICENSE-MIT.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright Mathias Bynens <https://mathiasbynens.be/>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2021 Vercel, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
201
LICENSE.txt
Normal file
201
LICENSE.txt
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
175
MIGRATIONS.md
Normal file
175
MIGRATIONS.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# Guía de Migraciones con Alembic
|
||||||
|
|
||||||
|
Este proyecto usa Alembic para manejar las migraciones de base de datos.
|
||||||
|
|
||||||
|
## 📋 Comandos Útiles
|
||||||
|
|
||||||
|
### Dentro del contenedor Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Acceder al contenedor
|
||||||
|
docker exec -it control_mve_api bash
|
||||||
|
|
||||||
|
# Crear una nueva migración automáticamente
|
||||||
|
alembic revision --autogenerate -m "descripción del cambio"
|
||||||
|
|
||||||
|
# Aplicar migraciones pendientes
|
||||||
|
alembic upgrade head
|
||||||
|
|
||||||
|
# Revertir última migración
|
||||||
|
alembic downgrade -1
|
||||||
|
|
||||||
|
# Ver historial de migraciones
|
||||||
|
alembic history
|
||||||
|
|
||||||
|
# Ver estado actual
|
||||||
|
alembic current
|
||||||
|
```
|
||||||
|
|
||||||
|
### Desde el host (sin entrar al contenedor)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Crear nueva migración
|
||||||
|
docker exec -it control_mve_api alembic revision --autogenerate -m "descripción"
|
||||||
|
|
||||||
|
# Aplicar migraciones
|
||||||
|
docker exec -it control_mve_api alembic upgrade head
|
||||||
|
|
||||||
|
# Ver historial
|
||||||
|
docker exec -it control_mve_api alembic history
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Flujo de Trabajo
|
||||||
|
|
||||||
|
### 1. Modificar modelos
|
||||||
|
|
||||||
|
Edita tus modelos en `app/models/`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Ejemplo: agregar un nuevo campo
|
||||||
|
class Cliente(Base):
|
||||||
|
__tablename__ = "clientes"
|
||||||
|
# ... campos existentes ...
|
||||||
|
nuevo_campo = Column(String(100))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Generar migración automática
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it control_mve_api alembic revision --autogenerate -m "agregar campo nuevo_campo a clientes"
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto creará un archivo en `alembic/versions/` con los cambios detectados.
|
||||||
|
|
||||||
|
### 3. Revisar la migración generada
|
||||||
|
|
||||||
|
**IMPORTANTE**: Siempre revisa el archivo generado antes de aplicarlo.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ver el archivo más reciente
|
||||||
|
ls -lt alembic/versions/ | head -n 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Aplicar la migración
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it control_mve_api alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Verificar que se aplicó
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it control_mve_api alembic current
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Migración Inicial
|
||||||
|
|
||||||
|
La migración inicial ya se ejecuta automáticamente cuando levantas los contenedores gracias al script `migrate.sh`.
|
||||||
|
|
||||||
|
Si necesitas recrear la migración inicial:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Eliminar migraciones existentes
|
||||||
|
rm alembic/versions/*.py
|
||||||
|
|
||||||
|
# Crear migración inicial
|
||||||
|
docker exec -it control_mve_api alembic revision --autogenerate -m "migración inicial"
|
||||||
|
|
||||||
|
# Aplicarla
|
||||||
|
docker exec -it control_mve_api alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Ejemplos Comunes
|
||||||
|
|
||||||
|
### Agregar un nuevo modelo
|
||||||
|
|
||||||
|
1. Crear archivo en `app/models/nueva_entidad.py`
|
||||||
|
2. Importarlo en `app/models/__init__.py`
|
||||||
|
3. Generar migración: `alembic revision --autogenerate -m "agregar modelo NuevaEntidad"`
|
||||||
|
4. Revisar y aplicar: `alembic upgrade head`
|
||||||
|
|
||||||
|
### Modificar un campo existente
|
||||||
|
|
||||||
|
1. Editar el modelo
|
||||||
|
2. Generar migración: `alembic revision --autogenerate -m "modificar campo X"`
|
||||||
|
3. **Revisar migración** - Alembic no siempre detecta todos los cambios
|
||||||
|
4. Aplicar: `alembic upgrade head`
|
||||||
|
|
||||||
|
### Crear migración manual
|
||||||
|
|
||||||
|
Si necesitas hacer cambios que Alembic no detecta automáticamente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it control_timbres_api alembic revision -m "mi cambio manual"
|
||||||
|
```
|
||||||
|
|
||||||
|
Luego edita el archivo generado en `alembic/versions/`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.execute("UPDATE clientes SET estado = 'activo' WHERE estado IS NULL")
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ Buenas Prácticas
|
||||||
|
|
||||||
|
1. **Siempre revisa** las migraciones autogeneradas antes de aplicarlas
|
||||||
|
2. **Prueba en desarrollo** antes de aplicar en producción
|
||||||
|
3. **Haz backup** de la base de datos antes de migraciones importantes
|
||||||
|
4. **Escribe downgrades** funcionales para poder revertir cambios
|
||||||
|
5. **Commits pequeños** - una migración por cambio lógico
|
||||||
|
6. **Nombres descriptivos** para las migraciones
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### La migración no detecta mis cambios
|
||||||
|
|
||||||
|
- Verifica que importaste el modelo en `app/models/__init__.py`
|
||||||
|
- Asegúrate de que `alembic/env.py` importa todos los modelos
|
||||||
|
- Reinicia el contenedor: `docker-compose restart api`
|
||||||
|
|
||||||
|
### Error: "Target database is not up to date"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ver qué migraciones faltan
|
||||||
|
docker exec -it control_mve_api alembic current
|
||||||
|
docker exec -it control_mve_api alembic history
|
||||||
|
|
||||||
|
# Aplicar pendientes
|
||||||
|
docker exec -it control_mve_api alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quiero empezar de cero
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CUIDADO: Esto borra todos los datos
|
||||||
|
docker-compose down -v
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Recursos
|
||||||
|
|
||||||
|
- [Documentación oficial de Alembic](https://alembic.sqlalchemy.org/)
|
||||||
|
- [Tutorial de Alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
|
||||||
195
README.md
Normal file
195
README.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# ARIA Query
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Programmatic access to the [WAI-ARIA 1.2 Roles Model](https://www.w3.org/TR/wai-aria-1.2/#roles).
|
||||||
|
This package tracks the W3C Recommendation (last update: 6 June 2023).
|
||||||
|
|
||||||
|
CDN URL: <https://unpkg.com/aria-query>
|
||||||
|
|
||||||
|
## Building the `src/etc` files
|
||||||
|
|
||||||
|
The files under `src/etc` are generated by the `breakUpAriaJSON` script.
|
||||||
|
|
||||||
|
To change them, edit the file `scripts/roles.json` then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node ./scripts/breakUpAriaJSON.js
|
||||||
|
git add scripts/roles.json src/etc
|
||||||
|
```
|
||||||
|
|
||||||
|
It should work with Node version 6.11.2 or later.
|
||||||
|
|
||||||
|
## Utilities
|
||||||
|
|
||||||
|
### Interface
|
||||||
|
|
||||||
|
These methods are available on each export from the module. The typing here in the documentation is pseudo-typed. Each export will have its own specific types for each method signature.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{|
|
||||||
|
entries: () => Array<$Item>,
|
||||||
|
get: (key: $Key) => ?$Value,
|
||||||
|
has: (key: $Key) => boolean,
|
||||||
|
keys: () => Array<$Key>,
|
||||||
|
values: () => Array<$Value>,
|
||||||
|
|};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Roles
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { roles } from 'aria-query';
|
||||||
|
```
|
||||||
|
|
||||||
|
A map of role names to the role definition. For example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let alertRole = roles.get('alert');
|
||||||
|
/**
|
||||||
|
* Value of alertRole
|
||||||
|
* {
|
||||||
|
* "requiredProps": {},
|
||||||
|
* "props": {
|
||||||
|
* "aria-atomic": "true",
|
||||||
|
* "aria-busy": null,
|
||||||
|
* "aria-controls": null,
|
||||||
|
* "aria-current": null,
|
||||||
|
* "aria-describedby": null,
|
||||||
|
* "aria-details": null,
|
||||||
|
* "aria-disabled": null,
|
||||||
|
* "aria-dropeffect": null,
|
||||||
|
* "aria-errormessage": null,
|
||||||
|
* "aria-expanded": null,
|
||||||
|
* "aria-flowto": null,
|
||||||
|
* "aria-grabbed": null,
|
||||||
|
* "aria-haspopup": null,
|
||||||
|
* "aria-hidden": null,
|
||||||
|
* "aria-invalid": null,
|
||||||
|
* "aria-keyshortcuts": null,
|
||||||
|
* "aria-label": null,
|
||||||
|
* "aria-labelledby": null,
|
||||||
|
* "aria-live": "assertive",
|
||||||
|
* "aria-owns": null,
|
||||||
|
* "aria-relevant": null,
|
||||||
|
* "aria-roledescription": null
|
||||||
|
* },
|
||||||
|
* "abstract": false,
|
||||||
|
* "childrenPresentational": false,
|
||||||
|
* "baseConcepts": [],
|
||||||
|
* "relatedConcepts": [ {
|
||||||
|
* "module": "XForms",
|
||||||
|
* "concept": {
|
||||||
|
* "name": "alert"
|
||||||
|
* }
|
||||||
|
* }],
|
||||||
|
* "superClass": [["roletype", "structure", "section"]]
|
||||||
|
* }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Elements to Roles
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { elementRoles } from 'aria-query';
|
||||||
|
```
|
||||||
|
|
||||||
|
HTML Elements with inherent roles are mapped to those roles. In the case of an element like `<input>`, the element often requires a `type` attribute to map to an ARIA role.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
[
|
||||||
|
[ '{"name": "article"}', [ 'article' ] ],
|
||||||
|
[ '{"name": "button"}', [ 'button' ] ],
|
||||||
|
[ '{"name": "td"}', [ 'cell', 'gridcell' ] ],
|
||||||
|
[ '{"name": "input", "attributes": [ {"name": "type", "value": "checkbox"}] }', [ 'checkbox' ] ],
|
||||||
|
[ '{"name": "th"}', [ 'columnheader' ] ],
|
||||||
|
[ '{"name": "select"}', [ 'combobox', 'listbox' ] ],
|
||||||
|
[ '{"name": "menuitem"}', [ 'command', 'menuitem' ] ],
|
||||||
|
[ '{"name": "dd"}', [ 'definition' ] ],
|
||||||
|
[ '{"name": "figure"}', [ 'figure' ] ],
|
||||||
|
[ '{"name": "form"}', [ 'form' ] ],
|
||||||
|
[ '{"name": "table"}', [ 'grid', 'table' ] ],
|
||||||
|
[ '{"name": "fieldset"}', [ 'group' ] ],
|
||||||
|
[ '{"name": "h1"}', [ 'heading' ] ],
|
||||||
|
[ '{"name": "h2"}', [ 'heading' ] ],
|
||||||
|
[ '{"name": "h3"}', [ 'heading' ] ],
|
||||||
|
[ '{"name": "h4"}', [ 'heading' ] ],
|
||||||
|
[ '{"name": "h5"}', [ 'heading' ] ],
|
||||||
|
[ '{"name": "h6"}', [ 'heading' ] ],
|
||||||
|
[ '{"name": "img"}', [ 'img' ] ],
|
||||||
|
[ '{"name": "a"}', [ 'link' ] ],
|
||||||
|
[ '{"name": "link"}', [ 'link' ] ],
|
||||||
|
[ '{"name": "ol"}', [ 'list' ] ],
|
||||||
|
[ '{"name": "ul"}', [ 'list' ] ],
|
||||||
|
[ '{"name": "li"}', [ 'listitem' ] ],
|
||||||
|
[ '{"name": "nav"}', [ 'navigation' ] ],
|
||||||
|
[ '{"name": "option"}', [ 'option' ] ],
|
||||||
|
[ '{"name": "input", "attributes": [ {"name": "type", "value": "radio"}] }', [ 'radio' ] ],
|
||||||
|
[ '{"name": "frame"}', [ 'region' ] ],
|
||||||
|
[ '{"name": "rel"}', [ 'roletype' ] ],
|
||||||
|
[ '{"name": "tr"}', [ 'row' ] ],
|
||||||
|
[ '{"name": "tbody"}', [ 'rowgroup' ] ],
|
||||||
|
[ '{"name": "tfoot"}', [ 'rowgroup' ] ],
|
||||||
|
[ '{"name": "thead"}', [ 'rowgroup' ] ],
|
||||||
|
[ '{"name": "th", "attributes": [ {"name": "scope", "value": "row"}] }', [ 'rowheader' ] ],
|
||||||
|
[ '{"name": "input", "attributes": [ {"name": "type", "value": "search"}] }', [ 'searchbox' ] ],
|
||||||
|
[ '{"name": "hr"}', [ 'separator' ] ],
|
||||||
|
[ '{"name": "dt"}', [ 'term' ] ],
|
||||||
|
[ '{"name": "dfn"}', [ 'term' ] ],
|
||||||
|
[ '{"name": "textarea"}', [ 'textbox' ] ],
|
||||||
|
[ '{"name": "input", "attributes": [ {"name": "type", "value": "text"}] }', [ 'textbox' ] ],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The map of elements to roles is keyed by an HTML concept. An HTML concept corresponds to the `baseConcepts` and `relatedConcepts` of an ARIA role. Concepts exist in the context of a `module`: HTML, XForms, Dublin Core, for example. The concept representation is an object literal with a name property (the element name) and an optional attributes array.
|
||||||
|
|
||||||
|
The roles are provided in a [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).
|
||||||
|
|
||||||
|
### Role to element
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { roleElements } from 'aria-query';
|
||||||
|
```
|
||||||
|
|
||||||
|
ARIA roles are mapped to the HTML Elements with the same inherent role. Some roles, such as `columnheader` are only mapped to an HTML element that expresses specific attributes. In the case of `<input>`, the element often requires a `type` attribute to map to an ARIA role.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
[
|
||||||
|
[ 'article', [ {"name": "article"} ] ],
|
||||||
|
[ 'button', [ {"name": "button"} ] ],
|
||||||
|
[ 'cell', [ {"name": "td"} ] ],
|
||||||
|
[ 'checkbox', [ {"name": "input", "attributes": [ {"name": "type", "value": "checkbox"}] } ] ],
|
||||||
|
[ 'columnheader', [ {"name": "th"} ] ],
|
||||||
|
[ 'combobox', [ {"name": "select"} ] ],
|
||||||
|
[ 'command', [ {"name": "menuitem"} ] ],
|
||||||
|
[ 'definition', [ {"name": "dd"}', '{"name": "dfn"} ] ],
|
||||||
|
[ 'figure', [ {"name": "figure"} ] ],
|
||||||
|
[ 'form', [ {"name": "form"} ] ],
|
||||||
|
[ 'grid', [ {"name": "table"} ] ],
|
||||||
|
[ 'gridcell', [ {"name": "td"} ] ],
|
||||||
|
[ 'group', [ {"name": "fieldset"} ] ],
|
||||||
|
[ 'heading', [ {"name": "h1"}', '{"name": "h2"}', '{"name": "h3"}', '{"name": "h4"}', '{"name": "h5"}', '{"name": "h6"} ] ],
|
||||||
|
[ 'img', [ {"name": "img"} ] ],
|
||||||
|
[ 'link', [ {"name": "a"}', '{"name": "link"} ] ],
|
||||||
|
[ 'list', [ {"name": "ol"}', '{"name": "ul"} ] ],
|
||||||
|
[ 'listbox', [ {"name": "select"} ] ],
|
||||||
|
[ 'listitem', [ {"name": "li"} ] ],
|
||||||
|
[ 'menuitem', [ {"name": "menuitem"} ] ],
|
||||||
|
[ 'navigation', [ {"name": "nav"} ] ],
|
||||||
|
[ 'option', [ {"name": "option"} ] ],
|
||||||
|
[ 'radio', [ {"name": "input", "attributes": [ {"name": "type", "value": "radio"}] } ] ],
|
||||||
|
[ 'region', [ {"name": "frame"} ] ],
|
||||||
|
[ 'roletype', [ {"name": "rel"} ] ],
|
||||||
|
[ 'row', [ {"name": "tr"} ] ],
|
||||||
|
[ 'rowgroup', [ {"name": "tbody"}', '{"name": "tfoot"}', '{"name": "thead"} ] ],
|
||||||
|
[ 'rowheader', [ {"name": "th", "attributes": [ {"name": "scope", "value": "row"}] }, {"name": "th", "attributes": [ {"name": "scope", "value": "rowgroup"}] } ] ],
|
||||||
|
[ 'searchbox', [ {"name": "input", "attributes": [ {"name": "type", "value": "search"}] } ] ],
|
||||||
|
[ 'separator', [ {"name": "hr"} ] ],
|
||||||
|
[ 'table', [ {"name": "table"} ] ],
|
||||||
|
[ 'term', [ {"name": "dt"} ] ],
|
||||||
|
[ 'textbox', [ {"name": "textarea"}', '{"name": "input", "attributes": [ {"name": "type", "value": "text"}] } ] ],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) 2021 A11yance
|
||||||
713
Readme.md
Normal file
713
Readme.md
Normal file
@@ -0,0 +1,713 @@
|
|||||||
|
# Commander.js
|
||||||
|
|
||||||
|
[](http://travis-ci.org/tj/commander.js)
|
||||||
|
[](https://www.npmjs.org/package/commander)
|
||||||
|
[](https://npmcharts.com/compare/commander?minimal=true)
|
||||||
|
[](https://packagephobia.now.sh/result?p=commander)
|
||||||
|
|
||||||
|
The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/commander-rb/commander).
|
||||||
|
|
||||||
|
Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
|
||||||
|
|
||||||
|
- [Commander.js](#commanderjs)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Declaring program variable](#declaring-program-variable)
|
||||||
|
- [Options](#options)
|
||||||
|
- [Common option types, boolean and value](#common-option-types-boolean-and-value)
|
||||||
|
- [Default option value](#default-option-value)
|
||||||
|
- [Other option types, negatable boolean and flag|value](#other-option-types-negatable-boolean-and-flagvalue)
|
||||||
|
- [Custom option processing](#custom-option-processing)
|
||||||
|
- [Required option](#required-option)
|
||||||
|
- [Version option](#version-option)
|
||||||
|
- [Commands](#commands)
|
||||||
|
- [Specify the argument syntax](#specify-the-argument-syntax)
|
||||||
|
- [Action handler (sub)commands](#action-handler-subcommands)
|
||||||
|
- [Git-style executable (sub)commands](#git-style-executable-subcommands)
|
||||||
|
- [Automated --help](#automated---help)
|
||||||
|
- [Custom help](#custom-help)
|
||||||
|
- [.usage and .name](#usage-and-name)
|
||||||
|
- [.outputHelp(cb)](#outputhelpcb)
|
||||||
|
- [.helpOption(flags, description)](#helpoptionflags-description)
|
||||||
|
- [.help(cb)](#helpcb)
|
||||||
|
- [Custom event listeners](#custom-event-listeners)
|
||||||
|
- [Bits and pieces](#bits-and-pieces)
|
||||||
|
- [Avoiding option name clashes](#avoiding-option-name-clashes)
|
||||||
|
- [TypeScript](#typescript)
|
||||||
|
- [Node options such as --harmony](#node-options-such-as---harmony)
|
||||||
|
- [Node debugging](#node-debugging)
|
||||||
|
- [Override exit handling](#override-exit-handling)
|
||||||
|
- [Examples](#examples)
|
||||||
|
- [License](#license)
|
||||||
|
- [Support](#support)
|
||||||
|
- [Commander for enterprise](#commander-for-enterprise)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install commander
|
||||||
|
```
|
||||||
|
|
||||||
|
## Declaring _program_ variable
|
||||||
|
|
||||||
|
Commander exports a global object which is convenient for quick programs.
|
||||||
|
This is used in the examples in this README for brevity.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
program.version('0.0.1');
|
||||||
|
```
|
||||||
|
|
||||||
|
For larger programs which may use commander in multiple ways, including unit testing, it is better to create a local Command object to use.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const commander = require('commander');
|
||||||
|
const program = new commander.Command();
|
||||||
|
program.version('0.0.1');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space.
|
||||||
|
|
||||||
|
The options can be accessed as properties on the Command object. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc. Multiple short flags may be combined as a single arg, for example `-abc` is equivalent to `-a -b -c`.
|
||||||
|
|
||||||
|
See also optional new behaviour to [avoid name clashes](#avoiding-option-name-clashes).
|
||||||
|
|
||||||
|
### Common option types, boolean and value
|
||||||
|
|
||||||
|
The two most used option types are a boolean flag, and an option which takes a value (declared using angle brackets). Both are `undefined` unless specified on command line.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.option('-d, --debug', 'output extra debugging')
|
||||||
|
.option('-s, --small', 'small pizza size')
|
||||||
|
.option('-p, --pizza-type <type>', 'flavour of pizza');
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
|
if (program.debug) console.log(program.opts());
|
||||||
|
console.log('pizza details:');
|
||||||
|
if (program.small) console.log('- small pizza size');
|
||||||
|
if (program.pizzaType) console.log(`- ${program.pizzaType}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pizza-options -d
|
||||||
|
{ debug: true, small: undefined, pizzaType: undefined }
|
||||||
|
pizza details:
|
||||||
|
$ pizza-options -p
|
||||||
|
error: option '-p, --pizza-type <type>' argument missing
|
||||||
|
$ pizza-options -ds -p vegetarian
|
||||||
|
{ debug: true, small: true, pizzaType: 'vegetarian' }
|
||||||
|
pizza details:
|
||||||
|
- small pizza size
|
||||||
|
- vegetarian
|
||||||
|
$ pizza-options --pizza-type=cheese
|
||||||
|
pizza details:
|
||||||
|
- cheese
|
||||||
|
```
|
||||||
|
|
||||||
|
`program.parse(arguments)` processes the arguments, leaving any args not consumed by the options as the `program.args` array.
|
||||||
|
|
||||||
|
### Default option value
|
||||||
|
|
||||||
|
You can specify a default value for an option which takes a value.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.option('-c, --cheese <type>', 'add the specified type of cheese', 'blue');
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
|
console.log(`cheese: ${program.cheese}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pizza-options
|
||||||
|
cheese: blue
|
||||||
|
$ pizza-options --cheese stilton
|
||||||
|
cheese: stilton
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other option types, negatable boolean and flag|value
|
||||||
|
|
||||||
|
You can specify a boolean option long name with a leading `no-` to set the option value to false when used.
|
||||||
|
Defined alone this also makes the option true by default.
|
||||||
|
|
||||||
|
If you define `--foo` first, adding `--no-foo` does not change the default value from what it would
|
||||||
|
otherwise be. You can specify a default boolean value for a boolean flag and it can be overridden on command line.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.option('--no-sauce', 'Remove sauce')
|
||||||
|
.option('--cheese <flavour>', 'cheese flavour', 'mozzarella')
|
||||||
|
.option('--no-cheese', 'plain with no cheese')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
const sauceStr = program.sauce ? 'sauce' : 'no sauce';
|
||||||
|
const cheeseStr = (program.cheese === false) ? 'no cheese' : `${program.cheese} cheese`;
|
||||||
|
console.log(`You ordered a pizza with ${sauceStr} and ${cheeseStr}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pizza-options
|
||||||
|
You ordered a pizza with sauce and mozzarella cheese
|
||||||
|
$ pizza-options --sauce
|
||||||
|
error: unknown option '--sauce'
|
||||||
|
$ pizza-options --cheese=blue
|
||||||
|
You ordered a pizza with sauce and blue cheese
|
||||||
|
$ pizza-options --no-sauce --no-cheese
|
||||||
|
You ordered a pizza with no sauce and no cheese
|
||||||
|
```
|
||||||
|
|
||||||
|
You can specify an option which functions as a flag but may also take a value (declared using square brackets).
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.option('-c, --cheese [type]', 'Add cheese with optional type');
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
|
if (program.cheese === undefined) console.log('no cheese');
|
||||||
|
else if (program.cheese === true) console.log('add cheese');
|
||||||
|
else console.log(`add cheese type ${program.cheese}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pizza-options
|
||||||
|
no cheese
|
||||||
|
$ pizza-options --cheese
|
||||||
|
add cheese
|
||||||
|
$ pizza-options --cheese mozzarella
|
||||||
|
add cheese type mozzarella
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom option processing
|
||||||
|
|
||||||
|
You may specify a function to do custom processing of option values. The callback function receives two parameters, the user specified value and the
|
||||||
|
previous value for the option. It returns the new value for the option.
|
||||||
|
|
||||||
|
This allows you to coerce the option value to the desired type, or accumulate values, or do entirely custom processing.
|
||||||
|
|
||||||
|
You can optionally specify the default/starting value for the option after the function.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
function myParseInt(value, dummyPrevious) {
|
||||||
|
// parseInt takes a string and an optional radix
|
||||||
|
return parseInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function increaseVerbosity(dummyValue, previous) {
|
||||||
|
return previous + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collect(value, previous) {
|
||||||
|
return previous.concat([value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function commaSeparatedList(value, dummyPrevious) {
|
||||||
|
return value.split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
program
|
||||||
|
.option('-f, --float <number>', 'float argument', parseFloat)
|
||||||
|
.option('-i, --integer <number>', 'integer argument', myParseInt)
|
||||||
|
.option('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0)
|
||||||
|
.option('-c, --collect <value>', 'repeatable value', collect, [])
|
||||||
|
.option('-l, --list <items>', 'comma separated list', commaSeparatedList)
|
||||||
|
;
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
|
if (program.float !== undefined) console.log(`float: ${program.float}`);
|
||||||
|
if (program.integer !== undefined) console.log(`integer: ${program.integer}`);
|
||||||
|
if (program.verbose > 0) console.log(`verbosity: ${program.verbose}`);
|
||||||
|
if (program.collect.length > 0) console.log(program.collect);
|
||||||
|
if (program.list !== undefined) console.log(program.list);
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ custom -f 1e2
|
||||||
|
float: 100
|
||||||
|
$ custom --integer 2
|
||||||
|
integer: 2
|
||||||
|
$ custom -v -v -v
|
||||||
|
verbose: 3
|
||||||
|
$ custom -c a -c b -c c
|
||||||
|
[ 'a', 'b', 'c' ]
|
||||||
|
$ custom --list x,y,z
|
||||||
|
[ 'x', 'y', 'z' ]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Required option
|
||||||
|
|
||||||
|
You may specify a required (mandatory) option using `.requiredOption`. The option must be specified on the command line, or by having a default value. The method is otherwise the same as `.option` in format, taking flags and description, and optional default value or custom processing.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.requiredOption('-c, --cheese <type>', 'pizza must have cheese');
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pizza
|
||||||
|
error: required option '-c, --cheese <type>' not specified
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version option
|
||||||
|
|
||||||
|
The optional `version` method adds handling for displaying the command version. The default option flags are `-V` and `--version`, and when present the command prints the version number and exits.
|
||||||
|
|
||||||
|
```js
|
||||||
|
program.version('0.0.1');
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./examples/pizza -V
|
||||||
|
0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
You may change the flags and description by passing additional parameters to the `version` method, using
|
||||||
|
the same syntax for flags as the `option` method. The version flags can be named anything, but a long name is required.
|
||||||
|
|
||||||
|
```js
|
||||||
|
program.version('0.0.1', '-v, --vers', 'output the current version');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
You can specify (sub)commands for your top-level command using `.command`. There are two ways these can be implemented: using an action handler attached to the command, or as a separate executable file (described in more detail later). In the first parameter to `.command` you specify the command name and any command arguments. The arguments may be `<required>` or `[optional]`, and the last argument may also be `variadic...`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Command implemented using action handler (description is supplied separately to `.command`)
|
||||||
|
// Returns new command for configuring.
|
||||||
|
program
|
||||||
|
.command('clone <source> [destination]')
|
||||||
|
.description('clone a repository into a newly created directory')
|
||||||
|
.action((source, destination) => {
|
||||||
|
console.log('clone command called');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Command implemented using separate executable file (description is second parameter to `.command`)
|
||||||
|
// Returns top-level command for adding more commands.
|
||||||
|
program
|
||||||
|
.command('start <service>', 'start named service')
|
||||||
|
.command('stop [service]', 'stop named service, or all if no name supplied');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specify the argument syntax
|
||||||
|
|
||||||
|
You use `.arguments` to specify the arguments for the top-level command, and for subcommands they are included in the `.command` call. Angled brackets (e.g. `<required>`) indicate required input. Square brackets (e.g. `[optional]`) indicate optional input.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version('0.1.0')
|
||||||
|
.arguments('<cmd> [env]')
|
||||||
|
.action(function (cmd, env) {
|
||||||
|
cmdValue = cmd;
|
||||||
|
envValue = env;
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
|
if (typeof cmdValue === 'undefined') {
|
||||||
|
console.error('no command given!');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('command:', cmdValue);
|
||||||
|
console.log('environment:', envValue || "no environment given");
|
||||||
|
```
|
||||||
|
|
||||||
|
The last argument of a command can be variadic, and only the last argument. To make an argument variadic you
|
||||||
|
append `...` to the argument name. For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version('0.1.0')
|
||||||
|
.command('rmdir <dir> [otherDirs...]')
|
||||||
|
.action(function (dir, otherDirs) {
|
||||||
|
console.log('rmdir %s', dir);
|
||||||
|
if (otherDirs) {
|
||||||
|
otherDirs.forEach(function (oDir) {
|
||||||
|
console.log('rmdir %s', oDir);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
```
|
||||||
|
|
||||||
|
The variadic argument is passed to the action handler as an array. (And this also applies to `program.args`.)
|
||||||
|
|
||||||
|
### Action handler (sub)commands
|
||||||
|
|
||||||
|
You can add options to a command that uses an action handler.
|
||||||
|
The action handler gets passed a parameter for each argument you declared, and one additional argument which is the
|
||||||
|
command object itself. This command argument has the values for the command-specific options added as properties.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('rm <dir>')
|
||||||
|
.option('-r, --recursive', 'Remove recursively')
|
||||||
|
.action(function (dir, cmdObj) {
|
||||||
|
console.log('remove ' + dir + (cmdObj.recursive ? ' recursively' : ''))
|
||||||
|
})
|
||||||
|
|
||||||
|
program.parse(process.argv)
|
||||||
|
```
|
||||||
|
|
||||||
|
You may supply an `async` action handler, in which case you call `.parseAsync` rather than `.parse`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
async function run() { /* code goes here */ }
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
program
|
||||||
|
.command('run')
|
||||||
|
.action(run);
|
||||||
|
await program.parseAsync(process.argv);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A command's options on the command line are validated when the command is used. Any unknown options will be reported as an error. However, if an action-based command does not define an action, then the options are not validated.
|
||||||
|
|
||||||
|
Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output.
|
||||||
|
|
||||||
|
### Git-style executable (sub)commands
|
||||||
|
|
||||||
|
When `.command()` is invoked with a description argument, this tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.
|
||||||
|
Commander will search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-subcommand`, like `pm-install`, `pm-search`.
|
||||||
|
You can specify a custom name with the `executableFile` configuration option.
|
||||||
|
|
||||||
|
You handle the options for an executable (sub)command in the executable, and don't declare them at the top-level.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// file: ./examples/pm
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version('0.1.0')
|
||||||
|
.command('install [name]', 'install one or more packages')
|
||||||
|
.command('search [query]', 'search with optional query')
|
||||||
|
.command('update', 'update installed packages', {executableFile: 'myUpdateSubCommand'})
|
||||||
|
.command('list', 'list packages installed', {isDefault: true})
|
||||||
|
.parse(process.argv);
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified.
|
||||||
|
Specifying a name with `executableFile` will override the default constructed name.
|
||||||
|
|
||||||
|
If the program is designed to be installed globally, make sure the executables have proper modes, like `755`.
|
||||||
|
|
||||||
|
## Automated --help
|
||||||
|
|
||||||
|
The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./examples/pizza --help
|
||||||
|
Usage: pizza [options]
|
||||||
|
|
||||||
|
An application for pizzas ordering
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-V, --version output the version number
|
||||||
|
-p, --peppers Add peppers
|
||||||
|
-P, --pineapple Add pineapple
|
||||||
|
-b, --bbq Add bbq sauce
|
||||||
|
-c, --cheese <type> Add the specified type of cheese (default: "marble")
|
||||||
|
-C, --no-cheese You do not want any cheese
|
||||||
|
-h, --help output usage information
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom help
|
||||||
|
|
||||||
|
You can display arbitrary `-h, --help` information
|
||||||
|
by listening for "--help". Commander will automatically
|
||||||
|
exit once you are done so that the remainder of your program
|
||||||
|
does not execute causing undesired behaviors, for example
|
||||||
|
in the following executable "stuff" will not output when
|
||||||
|
`--help` is used.
|
||||||
|
|
||||||
|
```js
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version('0.1.0')
|
||||||
|
.option('-f, --foo', 'enable some foo')
|
||||||
|
.option('-b, --bar', 'enable some bar')
|
||||||
|
.option('-B, --baz', 'enable some baz');
|
||||||
|
|
||||||
|
// must be before .parse() since
|
||||||
|
// node's emit() is immediate
|
||||||
|
|
||||||
|
program.on('--help', function(){
|
||||||
|
console.log('')
|
||||||
|
console.log('Examples:');
|
||||||
|
console.log(' $ custom-help --help');
|
||||||
|
console.log(' $ custom-help -h');
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
|
console.log('stuff');
|
||||||
|
```
|
||||||
|
|
||||||
|
Yields the following help output when `node script-name.js -h` or `node script-name.js --help` are run:
|
||||||
|
|
||||||
|
```Text
|
||||||
|
Usage: custom-help [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help output usage information
|
||||||
|
-V, --version output the version number
|
||||||
|
-f, --foo enable some foo
|
||||||
|
-b, --bar enable some bar
|
||||||
|
-B, --baz enable some baz
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$ custom-help --help
|
||||||
|
$ custom-help -h
|
||||||
|
```
|
||||||
|
|
||||||
|
### .usage and .name
|
||||||
|
|
||||||
|
These allow you to customise the usage description in the first line of the help. The name is otherwise
|
||||||
|
deduced from the (full) program arguments. Given:
|
||||||
|
|
||||||
|
```js
|
||||||
|
program
|
||||||
|
.name("my-command")
|
||||||
|
.usage("[global options] command")
|
||||||
|
```
|
||||||
|
|
||||||
|
The help will start with:
|
||||||
|
|
||||||
|
```Text
|
||||||
|
Usage: my-command [global options] command
|
||||||
|
```
|
||||||
|
|
||||||
|
### .outputHelp(cb)
|
||||||
|
|
||||||
|
Output help information without exiting.
|
||||||
|
Optional callback cb allows post-processing of help text before it is displayed.
|
||||||
|
|
||||||
|
If you want to display help by default (e.g. if no command was provided), you can use something like:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
const colors = require('colors');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version('0.1.0')
|
||||||
|
.command('getstream [url]', 'get stream URL')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
if (!process.argv.slice(2).length) {
|
||||||
|
program.outputHelp(make_red);
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_red(txt) {
|
||||||
|
return colors.red(txt); //display the help text in red on the console
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### .helpOption(flags, description)
|
||||||
|
|
||||||
|
Override the default help flags and description.
|
||||||
|
|
||||||
|
```js
|
||||||
|
program
|
||||||
|
.helpOption('-e, --HELP', 'read more information');
|
||||||
|
```
|
||||||
|
|
||||||
|
### .help(cb)
|
||||||
|
|
||||||
|
Output help information and exit immediately.
|
||||||
|
Optional callback cb allows post-processing of help text before it is displayed.
|
||||||
|
|
||||||
|
## Custom event listeners
|
||||||
|
|
||||||
|
You can execute custom actions by listening to command and option events.
|
||||||
|
|
||||||
|
```js
|
||||||
|
program.on('option:verbose', function () {
|
||||||
|
process.env.VERBOSE = this.verbose;
|
||||||
|
});
|
||||||
|
|
||||||
|
// error on unknown commands
|
||||||
|
program.on('command:*', function () {
|
||||||
|
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bits and pieces
|
||||||
|
|
||||||
|
### Avoiding option name clashes
|
||||||
|
|
||||||
|
The original and default behaviour is that the option values are stored
|
||||||
|
as properties on the program, and the action handler is passed a
|
||||||
|
command object with the options values stored as properties.
|
||||||
|
This is very convenient to code, but the downside is possible clashes with
|
||||||
|
existing properties of Command.
|
||||||
|
|
||||||
|
There are two new routines to change the behaviour, and the default behaviour may change in the future:
|
||||||
|
|
||||||
|
- `storeOptionsAsProperties`: whether to store option values as properties on command object, or store separately (specify false) and access using `.opts()`
|
||||||
|
- `passCommandToAction`: whether to pass command to action handler,
|
||||||
|
or just the options (specify false)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// file: ./examples/storeOptionsAsProperties.action.js
|
||||||
|
program
|
||||||
|
.storeOptionsAsProperties(false)
|
||||||
|
.passCommandToAction(false);
|
||||||
|
|
||||||
|
program
|
||||||
|
.name('my-program-name')
|
||||||
|
.option('-n,--name <name>');
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('show')
|
||||||
|
.option('-a,--action <action>')
|
||||||
|
.action((options) => {
|
||||||
|
console.log(options.action);
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
|
const programOptions = program.opts();
|
||||||
|
console.log(programOptions.name);
|
||||||
|
```
|
||||||
|
|
||||||
|
### TypeScript
|
||||||
|
|
||||||
|
The Commander package includes its TypeScript Definition file, but also requires the node types which you need to install yourself. e.g.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install commander
|
||||||
|
npm install --save-dev @types/node
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use `ts-node` and git-style sub-commands written as `.ts` files, you need to call your program through node to get the sub-commands called correctly. e.g.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node -r ts-node/register pm.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node options such as `--harmony`
|
||||||
|
|
||||||
|
You can enable `--harmony` option in two ways:
|
||||||
|
|
||||||
|
- Use `#! /usr/bin/env node --harmony` in the sub-commands scripts. (Note Windows does not support this pattern.)
|
||||||
|
- Use the `--harmony` option when call the command, like `node --harmony examples/pm publish`. The `--harmony` option will be preserved when spawning sub-command process.
|
||||||
|
|
||||||
|
### Node debugging
|
||||||
|
|
||||||
|
If you are using the node inspector for [debugging](https://nodejs.org/en/docs/guides/debugging-getting-started/) git-style executable (sub)commands using `node --inspect` et al,
|
||||||
|
the inspector port is incremented by 1 for the spawned subcommand.
|
||||||
|
|
||||||
|
### Override exit handling
|
||||||
|
|
||||||
|
By default Commander calls `process.exit` when it detects errors, or after displaying the help or version. You can override
|
||||||
|
this behaviour and optionally supply a callback. The default override throws a `CommanderError`.
|
||||||
|
|
||||||
|
The override callback is passed a `CommanderError` with properties `exitCode` number, `code` string, and `message`. The default override behaviour is to throw the error, except for async handling of executable subcommand completion which carries on. The normal display of error messages or version or help
|
||||||
|
is not affected by the override which is called after the display.
|
||||||
|
|
||||||
|
``` js
|
||||||
|
program.exitOverride();
|
||||||
|
|
||||||
|
try {
|
||||||
|
program.parse(process.argv);
|
||||||
|
} catch (err) {
|
||||||
|
// custom processing...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```js
|
||||||
|
const program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version('0.1.0')
|
||||||
|
.option('-C, --chdir <path>', 'change the working directory')
|
||||||
|
.option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
|
||||||
|
.option('-T, --no-tests', 'ignore test hook');
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('setup [env]')
|
||||||
|
.description('run setup commands for all envs')
|
||||||
|
.option("-s, --setup_mode [mode]", "Which setup mode to use")
|
||||||
|
.action(function(env, options){
|
||||||
|
const mode = options.setup_mode || "normal";
|
||||||
|
env = env || 'all';
|
||||||
|
console.log('setup for %s env(s) with %s mode', env, mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('exec <cmd>')
|
||||||
|
.alias('ex')
|
||||||
|
.description('execute the given remote cmd')
|
||||||
|
.option("-e, --exec_mode <mode>", "Which exec mode to use")
|
||||||
|
.action(function(cmd, options){
|
||||||
|
console.log('exec "%s" using %s mode', cmd, options.exec_mode);
|
||||||
|
}).on('--help', function() {
|
||||||
|
console.log('');
|
||||||
|
console.log('Examples:');
|
||||||
|
console.log('');
|
||||||
|
console.log(' $ deploy exec sequential');
|
||||||
|
console.log(' $ deploy exec async');
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('*')
|
||||||
|
.action(function(env){
|
||||||
|
console.log('deploying "%s"', env);
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
```
|
||||||
|
|
||||||
|
More Demos can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT](https://github.com/tj/commander.js/blob/master/LICENSE)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Commander 4.x is supported on Node 8 and above, and is likely to work with Node 6 but not tested.
|
||||||
|
(For versions of Node below Node 6, use Commander 3.x or 2.x.)
|
||||||
|
|
||||||
|
The main forum for free and community support is the project [Issues](https://github.com/tj/commander.js/issues) on GitHub.
|
||||||
|
|
||||||
|
### Commander for enterprise
|
||||||
|
|
||||||
|
Available as part of the Tidelift Subscription
|
||||||
|
|
||||||
|
The maintainers of Commander and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-commander?utm_source=npm-commander&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
|
||||||
15
SECURITY.md
Normal file
15
SECURITY.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Use this section to tell people about which versions of your project are
|
||||||
|
currently being supported with security updates.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 1.x | :white_check_mark: |
|
||||||
|
| < 1.0 | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please report all vulnerabilities at [https://github.com/mcollina/fastq/security](https://github.com/mcollina/fastq/security).
|
||||||
BIN
__init__.cpython-311.pyc
Normal file
BIN
__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__init__.py
Normal file
BIN
__init__.py
Normal file
Binary file not shown.
1
_svelte_metadata.json
Normal file
1
_svelte_metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"compilerOptions":{"css":"external","dev":true,"hmr":true},"configFile":"C:\\Users\\User\\Documents\\Docs-estadias-202526\\dock\\control_mve_frontend\\svelte.config.js","extensions":[".svelte"],"preprocess":{"name":"vite-preprocess","style":"async ({ attributes, content, filename = '' }) => {\n\t\tconst ext = attributes.lang ? `.${attributes.lang}` : '.css';\n\t\tif (attributes.lang && !isCSSRequest(ext)) return;\n\t\tif (!cssTransform) {\n\t\t\tcssTransform = createCssTransform(style, config).then((t) => (cssTransform = t));\n\t\t}\n\t\tconst transform = await cssTransform;\n\t\tconst suffix = `${lang_sep}${ext}`;\n\t\tconst moduleId = `${filename}${suffix}`;\n\t\tconst { code, map, deps } = await transform(content, moduleId);\n\t\tremoveLangSuffix(map, suffix);\n\t\tmapToRelative(map, filename);\n\t\tconst dependencies = deps ? Array.from(deps).filter((d) => !d.endsWith(suffix)) : undefined;\n\t\treturn {\n\t\t\tcode,\n\t\t\tmap: map ?? undefined,\n\t\t\tdependencies\n\t\t};\n\t}"}}
|
||||||
BIN
a3113e7c1b12_agregar_tabla_usuarios.cpython-311.pyc
Normal file
BIN
a3113e7c1b12_agregar_tabla_usuarios.cpython-311.pyc
Normal file
Binary file not shown.
124
a3113e7c1b12_agregar_tabla_usuarios.py
Normal file
124
a3113e7c1b12_agregar_tabla_usuarios.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
"""agregar tabla usuarios
|
||||||
|
|
||||||
|
Revision ID: a3113e7c1b12
|
||||||
|
Revises:
|
||||||
|
Create Date: 2025-12-15 19:24:37.318851
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'a3113e7c1b12'
|
||||||
|
down_revision: Union[str, None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('clientes',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('nombre', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('email', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('rfc', sa.String(length=13), nullable=True),
|
||||||
|
sa.Column('telefono', sa.String(length=20), nullable=True),
|
||||||
|
sa.Column('timbres_consumidos', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('estado', sa.Enum('ACTIVO', 'INACTIVO', 'SUSPENDIDO', name='estadocliente'), nullable=False),
|
||||||
|
sa.Column('permite_subclientes', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_clientes_email'), 'clientes', ['email'], unique=True)
|
||||||
|
op.create_index(op.f('ix_clientes_id'), 'clientes', ['id'], unique=False)
|
||||||
|
op.create_index(op.f('ix_clientes_rfc'), 'clientes', ['rfc'], unique=True)
|
||||||
|
op.create_table('usuarios',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('username', sa.String(length=50), nullable=False),
|
||||||
|
sa.Column('email', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('hashed_password', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('nombre_completo', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('is_superuser', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
|
||||||
|
sa.Column('last_login', sa.DateTime(timezone=True), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_usuarios_email'), 'usuarios', ['email'], unique=True)
|
||||||
|
op.create_index(op.f('ix_usuarios_id'), 'usuarios', ['id'], unique=False)
|
||||||
|
op.create_index(op.f('ix_usuarios_username'), 'usuarios', ['username'], unique=True)
|
||||||
|
op.create_table('limites_timbres',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('cliente_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('tipo', sa.Enum('ASIGNACION_INICIAL', 'INCREMENTO', 'DECREMENTO', 'AJUSTE', name='tipolimite'), nullable=False),
|
||||||
|
sa.Column('cantidad', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('descripcion', sa.String(length=500), nullable=True),
|
||||||
|
sa.Column('activo', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||||
|
sa.Column('inactivado_at', sa.DateTime(timezone=True), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['cliente_id'], ['clientes.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_limites_timbres_id'), 'limites_timbres', ['id'], unique=False)
|
||||||
|
op.create_table('subclientes',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('cliente_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('nombre', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('email', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('rfc', sa.String(length=13), nullable=True),
|
||||||
|
sa.Column('telefono', sa.String(length=20), nullable=True),
|
||||||
|
sa.Column('timbres_consumidos', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('limite_propio', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('estado', sa.Enum('ACTIVO', 'INACTIVO', 'SUSPENDIDO', name='estadocliente'), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['cliente_id'], ['clientes.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_subclientes_email'), 'subclientes', ['email'], unique=True)
|
||||||
|
op.create_index(op.f('ix_subclientes_id'), 'subclientes', ['id'], unique=False)
|
||||||
|
op.create_index(op.f('ix_subclientes_rfc'), 'subclientes', ['rfc'], unique=False)
|
||||||
|
op.create_table('movimientos_timbres',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('cliente_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('subcliente_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('tipo', sa.Enum('CONSUMO', 'RECARGA', 'AJUSTE', name='tipomovimiento'), nullable=False),
|
||||||
|
sa.Column('cantidad', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('descripcion', sa.String(length=500), nullable=True),
|
||||||
|
sa.Column('referencia_externa', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('balance_cliente', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['cliente_id'], ['clientes.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['subcliente_id'], ['subclientes.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_movimientos_timbres_id'), 'movimientos_timbres', ['id'], unique=False)
|
||||||
|
op.create_index(op.f('ix_movimientos_timbres_referencia_externa'), 'movimientos_timbres', ['referencia_externa'], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_movimientos_timbres_referencia_externa'), table_name='movimientos_timbres')
|
||||||
|
op.drop_index(op.f('ix_movimientos_timbres_id'), table_name='movimientos_timbres')
|
||||||
|
op.drop_table('movimientos_timbres')
|
||||||
|
op.drop_index(op.f('ix_subclientes_rfc'), table_name='subclientes')
|
||||||
|
op.drop_index(op.f('ix_subclientes_id'), table_name='subclientes')
|
||||||
|
op.drop_index(op.f('ix_subclientes_email'), table_name='subclientes')
|
||||||
|
op.drop_table('subclientes')
|
||||||
|
op.drop_index(op.f('ix_limites_timbres_id'), table_name='limites_timbres')
|
||||||
|
op.drop_table('limites_timbres')
|
||||||
|
op.drop_index(op.f('ix_usuarios_username'), table_name='usuarios')
|
||||||
|
op.drop_index(op.f('ix_usuarios_id'), table_name='usuarios')
|
||||||
|
op.drop_index(op.f('ix_usuarios_email'), table_name='usuarios')
|
||||||
|
op.drop_table('usuarios')
|
||||||
|
op.drop_index(op.f('ix_clientes_rfc'), table_name='clientes')
|
||||||
|
op.drop_index(op.f('ix_clientes_id'), table_name='clientes')
|
||||||
|
op.drop_index(op.f('ix_clientes_email'), table_name='clientes')
|
||||||
|
op.drop_table('clientes')
|
||||||
|
# ### end Alembic commands ###
|
||||||
16
acorn
Normal file
16
acorn
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../acorn/bin/acorn" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../acorn/bin/acorn" "$@"
|
||||||
|
fi
|
||||||
17
acorn.cmd
Normal file
17
acorn.cmd
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\acorn\bin\acorn" %*
|
||||||
28
acorn.ps1
Normal file
28
acorn.ps1
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../acorn/bin/acorn" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../acorn/bin/acorn" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../acorn/bin/acorn" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../acorn/bin/acorn" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
114
alembic.ini
Normal file
114
alembic.ini
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# path to migration scripts
|
||||||
|
script_location = alembic
|
||||||
|
|
||||||
|
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||||
|
# Uncomment the line below if you want the files to be prepended with date and time
|
||||||
|
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# sys.path path, will be prepended to sys.path if present.
|
||||||
|
# defaults to the current working directory.
|
||||||
|
prepend_sys_path = .
|
||||||
|
|
||||||
|
# timezone to use when rendering the date within the migration file
|
||||||
|
# as well as the filename.
|
||||||
|
# If specified, requires the python-dateutil library that can be
|
||||||
|
# installed by adding `alembic[tz]` to the pip requirements
|
||||||
|
# string value is passed to dateutil.tz.gettz()
|
||||||
|
# leave blank for localtime
|
||||||
|
# timezone =
|
||||||
|
|
||||||
|
# max length of characters to apply to the
|
||||||
|
# "slug" field
|
||||||
|
# truncate_slug_length = 40
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
# set to 'true' to allow .pyc and .pyo files without
|
||||||
|
# a source .py file to be detected as revisions in the
|
||||||
|
# versions/ directory
|
||||||
|
# sourceless = false
|
||||||
|
|
||||||
|
# version location specification; This defaults
|
||||||
|
# to alembic/versions. When using multiple version
|
||||||
|
# directories, initial revisions must be specified with --version-path.
|
||||||
|
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||||
|
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
||||||
|
|
||||||
|
# version path separator; As mentioned above, this is the character used to split
|
||||||
|
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||||
|
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||||
|
# Valid values for version_path_separator are:
|
||||||
|
#
|
||||||
|
# version_path_separator = :
|
||||||
|
# version_path_separator = ;
|
||||||
|
# version_path_separator = space
|
||||||
|
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||||
|
|
||||||
|
# set to 'true' to search source files recursively
|
||||||
|
# in each "version_locations" directory
|
||||||
|
# new in Alembic version 1.10
|
||||||
|
# recursive_version_locations = false
|
||||||
|
|
||||||
|
# the output encoding used when revision files
|
||||||
|
# are written from script.py.mako
|
||||||
|
# output_encoding = utf-8
|
||||||
|
|
||||||
|
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||||
|
|
||||||
|
|
||||||
|
[post_write_hooks]
|
||||||
|
# post_write_hooks defines scripts or Python functions that are run
|
||||||
|
# on newly generated revision scripts. See the documentation for further
|
||||||
|
# detail and examples
|
||||||
|
|
||||||
|
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||||
|
# hooks = black
|
||||||
|
# black.type = console_scripts
|
||||||
|
# black.entrypoint = black
|
||||||
|
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||||
|
|
||||||
|
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||||
|
# hooks = ruff
|
||||||
|
# ruff.type = exec
|
||||||
|
# ruff.executable = %(here)s/.venv/bin/ruff
|
||||||
|
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
||||||
3
app.css
Normal file
3
app.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
15
applypatch-msg.sample
Normal file
15
applypatch-msg.sample
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message taken by
|
||||||
|
# applypatch from an e-mail message.
|
||||||
|
#
|
||||||
|
# The hook should exit with non-zero status after issuing an
|
||||||
|
# appropriate message if it wants to stop the commit. The hook is
|
||||||
|
# allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "applypatch-msg".
|
||||||
|
|
||||||
|
. git-sh-setup
|
||||||
|
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||||
|
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||||
|
:
|
||||||
BIN
auth.cpython-311.pyc
Normal file
BIN
auth.cpython-311.pyc
Normal file
Binary file not shown.
232
auth.py
Normal file
232
auth.py
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import List
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models import Usuario
|
||||||
|
from app.schemas import (
|
||||||
|
UsuarioCreate, UsuarioUpdate, UsuarioResponse,
|
||||||
|
Token, LoginRequest, MessageResponse
|
||||||
|
)
|
||||||
|
from app.auth import verify_password, get_password_hash, create_access_token
|
||||||
|
from app.dependencies import get_current_user, get_current_active_superuser
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/register", response_model=UsuarioResponse, status_code=status.HTTP_201_CREATED)
|
||||||
|
def register(usuario: UsuarioCreate, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
Registrar un nuevo usuario (público - primer usuario será superuser)
|
||||||
|
"""
|
||||||
|
# Verificar si el username ya existe
|
||||||
|
if db.query(Usuario).filter(Usuario.username == usuario.username).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="El username ya está registrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verificar si el email ya existe
|
||||||
|
if db.query(Usuario).filter(Usuario.email == usuario.email).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="El email ya está registrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Si es el primer usuario, hacerlo superuser
|
||||||
|
total_usuarios = db.query(Usuario).count()
|
||||||
|
is_superuser = (total_usuarios == 0)
|
||||||
|
|
||||||
|
# Crear usuario
|
||||||
|
db_usuario = Usuario(
|
||||||
|
username=usuario.username,
|
||||||
|
email=usuario.email,
|
||||||
|
nombre_completo=usuario.nombre_completo,
|
||||||
|
hashed_password=get_password_hash(usuario.password),
|
||||||
|
is_superuser=is_superuser
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(db_usuario)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_usuario)
|
||||||
|
|
||||||
|
return db_usuario
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/login", response_model=Token)
|
||||||
|
def login(login_data: LoginRequest, db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
Iniciar sesión y obtener token JWT
|
||||||
|
"""
|
||||||
|
user = db.query(Usuario).filter(Usuario.username == login_data.username).first()
|
||||||
|
|
||||||
|
if not user or not verify_password(login_data.password, user.hashed_password):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Usuario o contraseña incorrectos",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Usuario inactivo"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Actualizar último login
|
||||||
|
user.last_login = datetime.utcnow()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# Crear token
|
||||||
|
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
access_token = create_access_token(
|
||||||
|
data={"sub": user.username}, expires_delta=access_token_expires
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/me", response_model=UsuarioResponse)
|
||||||
|
def read_users_me(current_user: Usuario = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
Obtener información del usuario actual
|
||||||
|
"""
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/me", response_model=UsuarioResponse)
|
||||||
|
def update_user_me(
|
||||||
|
usuario_update: UsuarioUpdate,
|
||||||
|
current_user: Usuario = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Actualizar información del usuario actual
|
||||||
|
"""
|
||||||
|
update_data = usuario_update.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
# Verificar email único si se actualiza
|
||||||
|
if "email" in update_data and update_data["email"] != current_user.email:
|
||||||
|
if db.query(Usuario).filter(Usuario.email == update_data["email"]).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="El email ya está registrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Si hay password, hashear
|
||||||
|
if "password" in update_data:
|
||||||
|
update_data["hashed_password"] = get_password_hash(update_data.pop("password"))
|
||||||
|
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(current_user, field, value)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(current_user)
|
||||||
|
|
||||||
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[UsuarioResponse])
|
||||||
|
def list_users(
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
current_user: Usuario = Depends(get_current_active_superuser),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Listar todos los usuarios (solo superuser)
|
||||||
|
"""
|
||||||
|
usuarios = db.query(Usuario).offset(skip).limit(limit).all()
|
||||||
|
return usuarios
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{usuario_id}", response_model=UsuarioResponse)
|
||||||
|
def get_user(
|
||||||
|
usuario_id: int,
|
||||||
|
current_user: Usuario = Depends(get_current_active_superuser),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Obtener un usuario por ID (solo superuser)
|
||||||
|
"""
|
||||||
|
usuario = db.query(Usuario).filter(Usuario.id == usuario_id).first()
|
||||||
|
|
||||||
|
if not usuario:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Usuario no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
return usuario
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{usuario_id}", response_model=UsuarioResponse)
|
||||||
|
def update_user(
|
||||||
|
usuario_id: int,
|
||||||
|
usuario_update: UsuarioUpdate,
|
||||||
|
current_user: Usuario = Depends(get_current_active_superuser),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Actualizar un usuario (solo superuser)
|
||||||
|
"""
|
||||||
|
usuario = db.query(Usuario).filter(Usuario.id == usuario_id).first()
|
||||||
|
|
||||||
|
if not usuario:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Usuario no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
update_data = usuario_update.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
# Verificar email único si se actualiza
|
||||||
|
if "email" in update_data and update_data["email"] != usuario.email:
|
||||||
|
if db.query(Usuario).filter(Usuario.email == update_data["email"]).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="El email ya está registrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Si hay password, hashear
|
||||||
|
if "password" in update_data:
|
||||||
|
update_data["hashed_password"] = get_password_hash(update_data.pop("password"))
|
||||||
|
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(usuario, field, value)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(usuario)
|
||||||
|
|
||||||
|
return usuario
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{usuario_id}", response_model=MessageResponse)
|
||||||
|
def delete_user(
|
||||||
|
usuario_id: int,
|
||||||
|
current_user: Usuario = Depends(get_current_active_superuser),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Eliminar un usuario (solo superuser)
|
||||||
|
"""
|
||||||
|
usuario = db.query(Usuario).filter(Usuario.id == usuario_id).first()
|
||||||
|
|
||||||
|
if not usuario:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Usuario no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# No permitir eliminar el propio usuario
|
||||||
|
if usuario.id == current_user.id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="No puedes eliminar tu propio usuario"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.delete(usuario)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {"message": f"Usuario {usuario.username} eliminado exitosamente"}
|
||||||
16
autoprefixer
Normal file
16
autoprefixer
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../autoprefixer/bin/autoprefixer" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../autoprefixer/bin/autoprefixer" "$@"
|
||||||
|
fi
|
||||||
17
autoprefixer.cmd
Normal file
17
autoprefixer.cmd
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\autoprefixer\bin\autoprefixer" %*
|
||||||
28
autoprefixer.ps1
Normal file
28
autoprefixer.ps1
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../autoprefixer/bin/autoprefixer" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../autoprefixer/bin/autoprefixer" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../autoprefixer/bin/autoprefixer" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../autoprefixer/bin/autoprefixer" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
16
baseline-browser-mapping
Normal file
16
baseline-browser-mapping
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../baseline-browser-mapping/dist/cli.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../baseline-browser-mapping/dist/cli.js" "$@"
|
||||||
|
fi
|
||||||
17
baseline-browser-mapping.cmd
Normal file
17
baseline-browser-mapping.cmd
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.js" %*
|
||||||
28
baseline-browser-mapping.ps1
Normal file
28
baseline-browser-mapping.ps1
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.js" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.js" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.js" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.js" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
66
bench.js
Normal file
66
bench.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const max = 1000000
|
||||||
|
const fastqueue = require('./')(worker, 1)
|
||||||
|
const { promisify } = require('util')
|
||||||
|
const immediate = promisify(setImmediate)
|
||||||
|
const qPromise = require('./').promise(immediate, 1)
|
||||||
|
const async = require('async')
|
||||||
|
const neo = require('neo-async')
|
||||||
|
const asyncqueue = async.queue(worker, 1)
|
||||||
|
const neoqueue = neo.queue(worker, 1)
|
||||||
|
|
||||||
|
function bench (func, done) {
|
||||||
|
const key = max + '*' + func.name
|
||||||
|
let count = -1
|
||||||
|
|
||||||
|
console.time(key)
|
||||||
|
end()
|
||||||
|
|
||||||
|
function end () {
|
||||||
|
if (++count < max) {
|
||||||
|
func(end)
|
||||||
|
} else {
|
||||||
|
console.timeEnd(key)
|
||||||
|
if (done) {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function benchFastQ (done) {
|
||||||
|
fastqueue.push(42, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
function benchAsyncQueue (done) {
|
||||||
|
asyncqueue.push(42, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
function benchNeoQueue (done) {
|
||||||
|
neoqueue.push(42, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
function worker (arg, cb) {
|
||||||
|
setImmediate(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
function benchSetImmediate (cb) {
|
||||||
|
worker(42, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
function benchFastQPromise (done) {
|
||||||
|
qPromise.push(42).then(function () { done() }, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
function runBench (done) {
|
||||||
|
async.eachSeries([
|
||||||
|
benchSetImmediate,
|
||||||
|
benchFastQ,
|
||||||
|
benchNeoQueue,
|
||||||
|
benchAsyncQueue,
|
||||||
|
benchFastQPromise
|
||||||
|
], bench, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
runBench(runBench)
|
||||||
263
binary-extensions.json
Normal file
263
binary-extensions.json
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
[
|
||||||
|
"3dm",
|
||||||
|
"3ds",
|
||||||
|
"3g2",
|
||||||
|
"3gp",
|
||||||
|
"7z",
|
||||||
|
"a",
|
||||||
|
"aac",
|
||||||
|
"adp",
|
||||||
|
"afdesign",
|
||||||
|
"afphoto",
|
||||||
|
"afpub",
|
||||||
|
"ai",
|
||||||
|
"aif",
|
||||||
|
"aiff",
|
||||||
|
"alz",
|
||||||
|
"ape",
|
||||||
|
"apk",
|
||||||
|
"appimage",
|
||||||
|
"ar",
|
||||||
|
"arj",
|
||||||
|
"asf",
|
||||||
|
"au",
|
||||||
|
"avi",
|
||||||
|
"bak",
|
||||||
|
"baml",
|
||||||
|
"bh",
|
||||||
|
"bin",
|
||||||
|
"bk",
|
||||||
|
"bmp",
|
||||||
|
"btif",
|
||||||
|
"bz2",
|
||||||
|
"bzip2",
|
||||||
|
"cab",
|
||||||
|
"caf",
|
||||||
|
"cgm",
|
||||||
|
"class",
|
||||||
|
"cmx",
|
||||||
|
"cpio",
|
||||||
|
"cr2",
|
||||||
|
"cur",
|
||||||
|
"dat",
|
||||||
|
"dcm",
|
||||||
|
"deb",
|
||||||
|
"dex",
|
||||||
|
"djvu",
|
||||||
|
"dll",
|
||||||
|
"dmg",
|
||||||
|
"dng",
|
||||||
|
"doc",
|
||||||
|
"docm",
|
||||||
|
"docx",
|
||||||
|
"dot",
|
||||||
|
"dotm",
|
||||||
|
"dra",
|
||||||
|
"DS_Store",
|
||||||
|
"dsk",
|
||||||
|
"dts",
|
||||||
|
"dtshd",
|
||||||
|
"dvb",
|
||||||
|
"dwg",
|
||||||
|
"dxf",
|
||||||
|
"ecelp4800",
|
||||||
|
"ecelp7470",
|
||||||
|
"ecelp9600",
|
||||||
|
"egg",
|
||||||
|
"eol",
|
||||||
|
"eot",
|
||||||
|
"epub",
|
||||||
|
"exe",
|
||||||
|
"f4v",
|
||||||
|
"fbs",
|
||||||
|
"fh",
|
||||||
|
"fla",
|
||||||
|
"flac",
|
||||||
|
"flatpak",
|
||||||
|
"fli",
|
||||||
|
"flv",
|
||||||
|
"fpx",
|
||||||
|
"fst",
|
||||||
|
"fvt",
|
||||||
|
"g3",
|
||||||
|
"gh",
|
||||||
|
"gif",
|
||||||
|
"graffle",
|
||||||
|
"gz",
|
||||||
|
"gzip",
|
||||||
|
"h261",
|
||||||
|
"h263",
|
||||||
|
"h264",
|
||||||
|
"icns",
|
||||||
|
"ico",
|
||||||
|
"ief",
|
||||||
|
"img",
|
||||||
|
"ipa",
|
||||||
|
"iso",
|
||||||
|
"jar",
|
||||||
|
"jpeg",
|
||||||
|
"jpg",
|
||||||
|
"jpgv",
|
||||||
|
"jpm",
|
||||||
|
"jxr",
|
||||||
|
"key",
|
||||||
|
"ktx",
|
||||||
|
"lha",
|
||||||
|
"lib",
|
||||||
|
"lvp",
|
||||||
|
"lz",
|
||||||
|
"lzh",
|
||||||
|
"lzma",
|
||||||
|
"lzo",
|
||||||
|
"m3u",
|
||||||
|
"m4a",
|
||||||
|
"m4v",
|
||||||
|
"mar",
|
||||||
|
"mdi",
|
||||||
|
"mht",
|
||||||
|
"mid",
|
||||||
|
"midi",
|
||||||
|
"mj2",
|
||||||
|
"mka",
|
||||||
|
"mkv",
|
||||||
|
"mmr",
|
||||||
|
"mng",
|
||||||
|
"mobi",
|
||||||
|
"mov",
|
||||||
|
"movie",
|
||||||
|
"mp3",
|
||||||
|
"mp4",
|
||||||
|
"mp4a",
|
||||||
|
"mpeg",
|
||||||
|
"mpg",
|
||||||
|
"mpga",
|
||||||
|
"mxu",
|
||||||
|
"nef",
|
||||||
|
"npx",
|
||||||
|
"numbers",
|
||||||
|
"nupkg",
|
||||||
|
"o",
|
||||||
|
"odp",
|
||||||
|
"ods",
|
||||||
|
"odt",
|
||||||
|
"oga",
|
||||||
|
"ogg",
|
||||||
|
"ogv",
|
||||||
|
"otf",
|
||||||
|
"ott",
|
||||||
|
"pages",
|
||||||
|
"pbm",
|
||||||
|
"pcx",
|
||||||
|
"pdb",
|
||||||
|
"pdf",
|
||||||
|
"pea",
|
||||||
|
"pgm",
|
||||||
|
"pic",
|
||||||
|
"png",
|
||||||
|
"pnm",
|
||||||
|
"pot",
|
||||||
|
"potm",
|
||||||
|
"potx",
|
||||||
|
"ppa",
|
||||||
|
"ppam",
|
||||||
|
"ppm",
|
||||||
|
"pps",
|
||||||
|
"ppsm",
|
||||||
|
"ppsx",
|
||||||
|
"ppt",
|
||||||
|
"pptm",
|
||||||
|
"pptx",
|
||||||
|
"psd",
|
||||||
|
"pya",
|
||||||
|
"pyc",
|
||||||
|
"pyo",
|
||||||
|
"pyv",
|
||||||
|
"qt",
|
||||||
|
"rar",
|
||||||
|
"ras",
|
||||||
|
"raw",
|
||||||
|
"resources",
|
||||||
|
"rgb",
|
||||||
|
"rip",
|
||||||
|
"rlc",
|
||||||
|
"rmf",
|
||||||
|
"rmvb",
|
||||||
|
"rpm",
|
||||||
|
"rtf",
|
||||||
|
"rz",
|
||||||
|
"s3m",
|
||||||
|
"s7z",
|
||||||
|
"scpt",
|
||||||
|
"sgi",
|
||||||
|
"shar",
|
||||||
|
"snap",
|
||||||
|
"sil",
|
||||||
|
"sketch",
|
||||||
|
"slk",
|
||||||
|
"smv",
|
||||||
|
"snk",
|
||||||
|
"so",
|
||||||
|
"stl",
|
||||||
|
"suo",
|
||||||
|
"sub",
|
||||||
|
"swf",
|
||||||
|
"tar",
|
||||||
|
"tbz",
|
||||||
|
"tbz2",
|
||||||
|
"tga",
|
||||||
|
"tgz",
|
||||||
|
"thmx",
|
||||||
|
"tif",
|
||||||
|
"tiff",
|
||||||
|
"tlz",
|
||||||
|
"ttc",
|
||||||
|
"ttf",
|
||||||
|
"txz",
|
||||||
|
"udf",
|
||||||
|
"uvh",
|
||||||
|
"uvi",
|
||||||
|
"uvm",
|
||||||
|
"uvp",
|
||||||
|
"uvs",
|
||||||
|
"uvu",
|
||||||
|
"viv",
|
||||||
|
"vob",
|
||||||
|
"war",
|
||||||
|
"wav",
|
||||||
|
"wax",
|
||||||
|
"wbmp",
|
||||||
|
"wdp",
|
||||||
|
"weba",
|
||||||
|
"webm",
|
||||||
|
"webp",
|
||||||
|
"whl",
|
||||||
|
"wim",
|
||||||
|
"wm",
|
||||||
|
"wma",
|
||||||
|
"wmv",
|
||||||
|
"wmx",
|
||||||
|
"woff",
|
||||||
|
"woff2",
|
||||||
|
"wrm",
|
||||||
|
"wvx",
|
||||||
|
"xbm",
|
||||||
|
"xif",
|
||||||
|
"xla",
|
||||||
|
"xlam",
|
||||||
|
"xls",
|
||||||
|
"xlsb",
|
||||||
|
"xlsm",
|
||||||
|
"xlsx",
|
||||||
|
"xlt",
|
||||||
|
"xltm",
|
||||||
|
"xltx",
|
||||||
|
"xm",
|
||||||
|
"xmind",
|
||||||
|
"xpi",
|
||||||
|
"xpm",
|
||||||
|
"xwd",
|
||||||
|
"xz",
|
||||||
|
"z",
|
||||||
|
"zip",
|
||||||
|
"zipx"
|
||||||
|
]
|
||||||
3
binary-extensions.json.d.ts
vendored
Normal file
3
binary-extensions.json.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare const binaryExtensionsJson: readonly string[];
|
||||||
|
|
||||||
|
export = binaryExtensionsJson;
|
||||||
1
browser-fallback.js
Normal file
1
browser-fallback.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default typeof window !== 'undefined';
|
||||||
54
browser.js
Normal file
54
browser.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
var BrowserslistError = require('./error')
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
loadQueries: function loadQueries() {
|
||||||
|
throw new BrowserslistError(
|
||||||
|
'Sharable configs are not supported in client-side build of Browserslist'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
getStat: function getStat(opts) {
|
||||||
|
return opts.stats
|
||||||
|
},
|
||||||
|
|
||||||
|
loadConfig: function loadConfig(opts) {
|
||||||
|
if (opts.config) {
|
||||||
|
throw new BrowserslistError(
|
||||||
|
'Browserslist config are not supported in client-side build'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadCountry: function loadCountry() {
|
||||||
|
throw new BrowserslistError(
|
||||||
|
'Country statistics are not supported ' +
|
||||||
|
'in client-side build of Browserslist'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
loadFeature: function loadFeature() {
|
||||||
|
throw new BrowserslistError(
|
||||||
|
'Supports queries are not available in client-side build of Browserslist'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
currentNode: function currentNode(resolve, context) {
|
||||||
|
return resolve(['maintained node versions'], context)[0]
|
||||||
|
},
|
||||||
|
|
||||||
|
parseConfig: noop,
|
||||||
|
|
||||||
|
readConfig: noop,
|
||||||
|
|
||||||
|
findConfig: noop,
|
||||||
|
|
||||||
|
findConfigFile: noop,
|
||||||
|
|
||||||
|
clearCaches: noop,
|
||||||
|
|
||||||
|
oldDataWarning: noop,
|
||||||
|
|
||||||
|
env: {}
|
||||||
|
}
|
||||||
16
browserslist
Normal file
16
browserslist
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../browserslist/cli.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../browserslist/cli.js" "$@"
|
||||||
|
fi
|
||||||
17
browserslist.cmd
Normal file
17
browserslist.cmd
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %*
|
||||||
28
browserslist.ps1
Normal file
28
browserslist.ps1
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../browserslist/cli.js" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../browserslist/cli.js" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
167
changelog.md
Normal file
167
changelog.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# [4.3.1](https://github.com/TehShrike/deepmerge/releases/tag/v4.3.1)
|
||||||
|
|
||||||
|
- Fix type definition for arrayMerge options. [#239](https://github.com/TehShrike/deepmerge/pull/239)
|
||||||
|
|
||||||
|
# [4.3.0](https://github.com/TehShrike/deepmerge/releases/tag/v4.3.0)
|
||||||
|
|
||||||
|
- Avoid thrown errors if the target doesn't have `propertyIsEnumerable`. [#252](https://github.com/TehShrike/deepmerge/pull/252)
|
||||||
|
|
||||||
|
# [4.2.2](https://github.com/TehShrike/deepmerge/releases/tag/v4.2.2)
|
||||||
|
|
||||||
|
- `isMergeableObject` is now only called if there are two values that could be merged. [a34dd4d2](https://github.com/TehShrike/deepmerge/commit/a34dd4d25bf5e250653540a2022bc832c7b00a19)
|
||||||
|
|
||||||
|
# [4.2.1](https://github.com/TehShrike/deepmerge/releases/tag/v4.2.1)
|
||||||
|
|
||||||
|
- Fix: falsey values can now be merged. [#170](https://github.com/TehShrike/deepmerge/issues/170)
|
||||||
|
|
||||||
|
# [4.2.0](https://github.com/TehShrike/deepmerge/releases/tag/v4.2.0)
|
||||||
|
|
||||||
|
- Properties are now only overwritten if they exist on the target object and are enumerable. [#164](https://github.com/TehShrike/deepmerge/pull/164)
|
||||||
|
|
||||||
|
Technically this could probably be a patch release since "which properties get overwritten" wasn't documented and accidentally overwriting a built-in function or some function up the property chain would almost certainly be undesirable, but it feels like a gray area, so here we are with a feature version bump.
|
||||||
|
|
||||||
|
# [4.1.2](https://github.com/TehShrike/deepmerge/releases/tag/v4.1.2)
|
||||||
|
|
||||||
|
- Rolled back #167 since `Object.assign` breaks ES5 support. [55067352](https://github.com/TehShrike/deepmerge/commit/55067352a92c65a6c44a5165f3387720aae1e192)
|
||||||
|
|
||||||
|
# [4.1.1](https://github.com/TehShrike/deepmerge/releases/tag/v4.1.1)
|
||||||
|
|
||||||
|
- The `options` argument is no longer mutated [#167](https://github.com/TehShrike/deepmerge/pull/167)
|
||||||
|
|
||||||
|
# [4.1.0](https://github.com/TehShrike/deepmerge/releases/tag/v4.1.0)
|
||||||
|
|
||||||
|
- `cloneUnlessOtherwiseSpecified` is now exposed to the `arrayMerge` function [#165](https://github.com/TehShrike/deepmerge/pull/165)
|
||||||
|
|
||||||
|
# [4.0.0](https://github.com/TehShrike/deepmerge/releases/tag/v4.0.0)
|
||||||
|
|
||||||
|
- The `main` entry point in `package.json` is now a CommonJS module instead of a UMD module [#155](https://github.com/TehShrike/deepmerge/pull/155)
|
||||||
|
|
||||||
|
# [3.3.0](https://github.com/TehShrike/deepmerge/releases/tag/v3.3.0)
|
||||||
|
|
||||||
|
- Enumerable Symbol properties are now copied [#151](https://github.com/TehShrike/deepmerge/pull/151)
|
||||||
|
|
||||||
|
# [3.2.1](https://github.com/TehShrike/deepmerge/releases/tag/v3.2.1)
|
||||||
|
|
||||||
|
- bumping dev dependency versions to try to shut up bogus security warnings from Github/npm [#149](https://github.com/TehShrike/deepmerge/pull/149)
|
||||||
|
|
||||||
|
# [3.2.0](https://github.com/TehShrike/deepmerge/releases/tag/v3.2.0)
|
||||||
|
|
||||||
|
- feature: added the [`customMerge`](https://github.com/TehShrike/deepmerge#custommerge) option [#133](https://github.com/TehShrike/deepmerge/pull/133)
|
||||||
|
|
||||||
|
# [3.1.0](https://github.com/TehShrike/deepmerge/releases/tag/v3.1.0)
|
||||||
|
|
||||||
|
- typescript typing: make the `all` function generic [#129](https://github.com/TehShrike/deepmerge/pull/129)
|
||||||
|
|
||||||
|
# [3.0.0](https://github.com/TehShrike/deepmerge/releases/tag/v3.0.0)
|
||||||
|
|
||||||
|
- drop ES module build [#123](https://github.com/TehShrike/deepmerge/issues/123)
|
||||||
|
|
||||||
|
# [2.2.1](https://github.com/TehShrike/deepmerge/releases/tag/v2.2.1)
|
||||||
|
|
||||||
|
- bug: typescript export type was wrong [#121](https://github.com/TehShrike/deepmerge/pull/121)
|
||||||
|
|
||||||
|
# [2.2.0](https://github.com/TehShrike/deepmerge/releases/tag/v2.2.0)
|
||||||
|
|
||||||
|
- feature: added TypeScript typings [#119](https://github.com/TehShrike/deepmerge/pull/119)
|
||||||
|
|
||||||
|
# [2.1.1](https://github.com/TehShrike/deepmerge/releases/tag/v2.1.1)
|
||||||
|
|
||||||
|
- documentation: Rename "methods" to "api", note ESM syntax [#103](https://github.com/TehShrike/deepmerge/pull/103)
|
||||||
|
- documentation: Fix grammar [#107](https://github.com/TehShrike/deepmerge/pull/107)
|
||||||
|
- documentation: Restructure headers for clarity + some wording tweaks [108](https://github.com/TehShrike/deepmerge/pull/108) + [109](https://github.com/TehShrike/deepmerge/pull/109)
|
||||||
|
|
||||||
|
|
||||||
|
# [2.1.0](https://github.com/TehShrike/deepmerge/releases/tag/v2.1.0)
|
||||||
|
|
||||||
|
- feature: Support a custom `isMergeableObject` function [#96](https://github.com/TehShrike/deepmerge/pull/96)
|
||||||
|
- documentation: note a Webpack bug that some users might need to work around [#100](https://github.com/TehShrike/deepmerge/pull/100)
|
||||||
|
|
||||||
|
# [2.0.1](https://github.com/TehShrike/deepmerge/releases/tag/v2.0.1)
|
||||||
|
|
||||||
|
- documentation: fix the old array merge algorithm in the readme. [#84](https://github.com/TehShrike/deepmerge/pull/84)
|
||||||
|
|
||||||
|
# [2.0.0](https://github.com/TehShrike/deepmerge/releases/tag/v2.0.0)
|
||||||
|
|
||||||
|
- breaking: the array merge algorithm has changed from a complicated thing to `target.concat(source).map(element => cloneUnlessOtherwiseSpecified(element, optionsArgument))`
|
||||||
|
- breaking: The `clone` option now defaults to `true`
|
||||||
|
- feature: `merge.all` now accepts an array of any size, even 0 or 1 elements
|
||||||
|
|
||||||
|
See [pull request 77](https://github.com/TehShrike/deepmerge/pull/77).
|
||||||
|
|
||||||
|
# [1.5.2](https://github.com/TehShrike/deepmerge/releases/tag/v1.5.2)
|
||||||
|
|
||||||
|
- fix: no longer attempts to merge React elements [#76](https://github.com/TehShrike/deepmerge/issues/76)
|
||||||
|
|
||||||
|
# [1.5.1](https://github.com/TehShrike/deepmerge/releases/tag/v1.5.1)
|
||||||
|
|
||||||
|
- bower support: officially dropping bower support. If you use bower, please depend on the [unpkg distribution](https://unpkg.com/deepmerge/dist/umd.js). See [#63](https://github.com/TehShrike/deepmerge/issues/63)
|
||||||
|
|
||||||
|
# [1.5.0](https://github.com/TehShrike/deepmerge/releases/tag/v1.5.0)
|
||||||
|
|
||||||
|
- bug fix: merging objects into arrays was allowed, and doesn't make any sense. [#65](https://github.com/TehShrike/deepmerge/issues/65) published as a feature release instead of a patch because it is a decent behavior change.
|
||||||
|
|
||||||
|
# [1.4.4](https://github.com/TehShrike/deepmerge/releases/tag/v1.4.4)
|
||||||
|
|
||||||
|
- bower support: updated `main` in bower.json
|
||||||
|
|
||||||
|
# [1.4.3](https://github.com/TehShrike/deepmerge/releases/tag/v1.4.3)
|
||||||
|
|
||||||
|
- bower support: inline is-mergeable-object in a new CommonJS build, so that people using both bower and CommonJS can bundle the library [0b34e6](https://github.com/TehShrike/deepmerge/commit/0b34e6e95f989f2fc8091d25f0d291c08f3d2d24)
|
||||||
|
|
||||||
|
# [1.4.2](https://github.com/TehShrike/deepmerge/releases/tag/v1.4.2)
|
||||||
|
|
||||||
|
- performance: bump is-mergeable-object dependency version for a slight performance improvement [5906c7](https://github.com/TehShrike/deepmerge/commit/5906c765d691d48e83d76efbb0d4b9ca150dc12c)
|
||||||
|
|
||||||
|
# [1.4.1](https://github.com/TehShrike/deepmerge/releases/tag/v1.4.1)
|
||||||
|
|
||||||
|
- documentation: fix unpkg link [acc45b](https://github.com/TehShrike/deepmerge/commit/acc45be85519c1df906a72ecb24764b622d18d47)
|
||||||
|
|
||||||
|
# [1.4.0](https://github.com/TehShrike/deepmerge/releases/tag/v1.4.0)
|
||||||
|
|
||||||
|
- api: instead of only exporting a UMD module, expose a UMD module with `pkg.main`, a CJS module with `pkg.browser`, and an ES module with `pkg.module` [#62](https://github.com/TehShrike/deepmerge/pull/62)
|
||||||
|
|
||||||
|
# [1.3.2](https://github.com/TehShrike/deepmerge/releases/tag/v1.3.2)
|
||||||
|
|
||||||
|
- documentation: note the minified/gzipped file sizes [56](https://github.com/TehShrike/deepmerge/pull/56)
|
||||||
|
- documentation: make data structures more readable in merge example: pull request [57](https://github.com/TehShrike/deepmerge/pull/57)
|
||||||
|
|
||||||
|
# [1.3.1](https://github.com/TehShrike/deepmerge/releases/tag/v1.3.1)
|
||||||
|
|
||||||
|
- documentation: clarify and test some array merging documentation: pull request [51](https://github.com/TehShrike/deepmerge/pull/51)
|
||||||
|
|
||||||
|
# [1.3.0](https://github.com/TehShrike/deepmerge/releases/tag/v1.3.0)
|
||||||
|
|
||||||
|
- feature: `merge.all`, a merge function that merges any number of objects: pull request [50](https://github.com/TehShrike/deepmerge/pull/50)
|
||||||
|
|
||||||
|
# [1.2.0](https://github.com/TehShrike/deepmerge/releases/tag/v1.2.0)
|
||||||
|
|
||||||
|
- fix: an error that would be thrown when an array would be merged onto a truthy non-array value: pull request [46](https://github.com/TehShrike/deepmerge/pull/46)
|
||||||
|
- feature: the ability to clone: Issue [28](https://github.com/TehShrike/deepmerge/issues/28), pull requests [44](https://github.com/TehShrike/deepmerge/pull/44) and [48](https://github.com/TehShrike/deepmerge/pull/48)
|
||||||
|
- maintenance: added tests + travis to `.npmignore`: pull request [47](https://github.com/TehShrike/deepmerge/pull/47)
|
||||||
|
|
||||||
|
# [1.1.1](https://github.com/TehShrike/deepmerge/releases/tag/v1.1.1)
|
||||||
|
|
||||||
|
- fix an issue where an error was thrown when merging an array onto a non-array: [Pull request 46](https://github.com/TehShrike/deepmerge/pull/46)
|
||||||
|
|
||||||
|
# [1.1.0](https://github.com/TehShrike/deepmerge/releases/tag/v1.1.0)
|
||||||
|
|
||||||
|
- allow consumers to specify their own array merging algorithm: [Pull request 37](https://github.com/TehShrike/deepmerge/pull/37)
|
||||||
|
|
||||||
|
# [1.0.3](https://github.com/TehShrike/deepmerge/releases/tag/v1.0.3)
|
||||||
|
|
||||||
|
- adding bower.json back: [Issue 38](https://github.com/TehShrike/deepmerge/pull/38)
|
||||||
|
- updating keywords and Github links in package.json [bc3898e](https://github.com/TehShrike/deepmerge/commit/bc3898e587a56f74591328f40f656b0152c1d5eb)
|
||||||
|
|
||||||
|
# [1.0.2](https://github.com/TehShrike/deepmerge/releases/tag/v1.0.2)
|
||||||
|
|
||||||
|
- Updating the readme: dropping bower, testing that the example works: [7102fc](https://github.com/TehShrike/deepmerge/commit/7102fcc4ddec11e2d33205866f9f18df14e5aeb5)
|
||||||
|
|
||||||
|
# [1.0.1](https://github.com/TehShrike/deepmerge/releases/tag/v1.0.1)
|
||||||
|
|
||||||
|
- `null`, dates, and regular expressions are now properly merged in arrays: [Issue 18](https://github.com/TehShrike/deepmerge/pull/18), plus commit: [ef1c6b](https://github.com/TehShrike/deepmerge/commit/ef1c6bac8350ba12a24966f0bc7da02560827586)
|
||||||
|
|
||||||
|
# 1.0.0
|
||||||
|
|
||||||
|
- Should only be a patch change, because this module is READY. [Issue 15](https://github.com/TehShrike/deepmerge/issues/15)
|
||||||
|
- Regular expressions are now treated like primitive values when merging: [Issue 30](https://github.com/TehShrike/deepmerge/pull/30)
|
||||||
|
- Dates are now treated like primitives when merging: [Issue 31](https://github.com/TehShrike/deepmerge/issues/31)
|
||||||
8
child_process.js
Normal file
8
child_process.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
require('thenify-all').withCallback(
|
||||||
|
require('child_process'),
|
||||||
|
exports, [
|
||||||
|
'exec',
|
||||||
|
'execFile',
|
||||||
|
]
|
||||||
|
)
|
||||||
84
chromium-versions.js
Normal file
84
chromium-versions.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
module.exports = {
|
||||||
|
"39": "0.20",
|
||||||
|
"40": "0.21",
|
||||||
|
"41": "0.21",
|
||||||
|
"42": "0.25",
|
||||||
|
"43": "0.27",
|
||||||
|
"44": "0.30",
|
||||||
|
"45": "0.31",
|
||||||
|
"47": "0.36",
|
||||||
|
"49": "0.37",
|
||||||
|
"50": "1.1",
|
||||||
|
"51": "1.2",
|
||||||
|
"52": "1.3",
|
||||||
|
"53": "1.4",
|
||||||
|
"54": "1.4",
|
||||||
|
"56": "1.6",
|
||||||
|
"58": "1.7",
|
||||||
|
"59": "1.8",
|
||||||
|
"61": "2.0",
|
||||||
|
"66": "3.0",
|
||||||
|
"69": "4.0",
|
||||||
|
"72": "5.0",
|
||||||
|
"73": "5.0",
|
||||||
|
"76": "6.0",
|
||||||
|
"78": "7.0",
|
||||||
|
"79": "8.0",
|
||||||
|
"80": "8.0",
|
||||||
|
"82": "9.0",
|
||||||
|
"83": "9.0",
|
||||||
|
"84": "10.0",
|
||||||
|
"85": "10.0",
|
||||||
|
"86": "11.0",
|
||||||
|
"87": "11.0",
|
||||||
|
"89": "12.0",
|
||||||
|
"90": "13.0",
|
||||||
|
"91": "13.0",
|
||||||
|
"92": "14.0",
|
||||||
|
"93": "14.0",
|
||||||
|
"94": "15.0",
|
||||||
|
"95": "16.0",
|
||||||
|
"96": "16.0",
|
||||||
|
"98": "17.0",
|
||||||
|
"99": "18.0",
|
||||||
|
"100": "18.0",
|
||||||
|
"102": "19.0",
|
||||||
|
"103": "20.0",
|
||||||
|
"104": "20.0",
|
||||||
|
"105": "21.0",
|
||||||
|
"106": "21.0",
|
||||||
|
"107": "22.0",
|
||||||
|
"108": "22.0",
|
||||||
|
"110": "23.0",
|
||||||
|
"111": "24.0",
|
||||||
|
"112": "24.0",
|
||||||
|
"114": "25.0",
|
||||||
|
"116": "26.0",
|
||||||
|
"118": "27.0",
|
||||||
|
"119": "28.0",
|
||||||
|
"120": "28.0",
|
||||||
|
"121": "29.0",
|
||||||
|
"122": "29.0",
|
||||||
|
"123": "30.0",
|
||||||
|
"124": "30.0",
|
||||||
|
"125": "31.0",
|
||||||
|
"126": "31.0",
|
||||||
|
"127": "32.0",
|
||||||
|
"128": "32.0",
|
||||||
|
"129": "33.0",
|
||||||
|
"130": "33.0",
|
||||||
|
"131": "34.0",
|
||||||
|
"132": "34.0",
|
||||||
|
"133": "35.0",
|
||||||
|
"134": "35.0",
|
||||||
|
"135": "36.0",
|
||||||
|
"136": "36.0",
|
||||||
|
"137": "37.0",
|
||||||
|
"138": "37.0",
|
||||||
|
"139": "38.0",
|
||||||
|
"140": "38.0",
|
||||||
|
"141": "39.0",
|
||||||
|
"142": "39.0",
|
||||||
|
"143": "40.0",
|
||||||
|
"144": "40.0"
|
||||||
|
};
|
||||||
1
chromium-versions.json
Normal file
1
chromium-versions.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"39":"0.20","40":"0.21","41":"0.21","42":"0.25","43":"0.27","44":"0.30","45":"0.31","47":"0.36","49":"0.37","50":"1.1","51":"1.2","52":"1.3","53":"1.4","54":"1.4","56":"1.6","58":"1.7","59":"1.8","61":"2.0","66":"3.0","69":"4.0","72":"5.0","73":"5.0","76":"6.0","78":"7.0","79":"8.0","80":"8.0","82":"9.0","83":"9.0","84":"10.0","85":"10.0","86":"11.0","87":"11.0","89":"12.0","90":"13.0","91":"13.0","92":"14.0","93":"14.0","94":"15.0","95":"16.0","96":"16.0","98":"17.0","99":"18.0","100":"18.0","102":"19.0","103":"20.0","104":"20.0","105":"21.0","106":"21.0","107":"22.0","108":"22.0","110":"23.0","111":"24.0","112":"24.0","114":"25.0","116":"26.0","118":"27.0","119":"28.0","120":"28.0","121":"29.0","122":"29.0","123":"30.0","124":"30.0","125":"31.0","126":"31.0","127":"32.0","128":"32.0","129":"33.0","130":"33.0","131":"34.0","132":"34.0","133":"35.0","134":"35.0","135":"36.0","136":"36.0","137":"37.0","138":"37.0","139":"38.0","140":"38.0","141":"39.0","142":"39.0","143":"40.0","144":"40.0"}
|
||||||
156
cli.js
Normal file
156
cli.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var fs = require('fs')
|
||||||
|
var updateDb = require('update-browserslist-db')
|
||||||
|
|
||||||
|
var browserslist = require('./')
|
||||||
|
var pkg = require('./package.json')
|
||||||
|
|
||||||
|
var args = process.argv.slice(2)
|
||||||
|
|
||||||
|
var USAGE =
|
||||||
|
'Usage:\n' +
|
||||||
|
' npx browserslist\n' +
|
||||||
|
' npx browserslist "QUERIES"\n' +
|
||||||
|
' npx browserslist --json "QUERIES"\n' +
|
||||||
|
' npx browserslist --config="path/to/browserlist/file"\n' +
|
||||||
|
' npx browserslist --coverage "QUERIES"\n' +
|
||||||
|
' npx browserslist --coverage=US "QUERIES"\n' +
|
||||||
|
' npx browserslist --coverage=US,RU,global "QUERIES"\n' +
|
||||||
|
' npx browserslist --env="environment name defined in config"\n' +
|
||||||
|
' npx browserslist --stats="path/to/browserlist/stats/file"\n' +
|
||||||
|
' npx browserslist --mobile-to-desktop\n' +
|
||||||
|
' npx browserslist --ignore-unknown-versions\n'
|
||||||
|
|
||||||
|
function isArg(arg) {
|
||||||
|
return args.some(function (str) {
|
||||||
|
return str === arg || str.indexOf(arg + '=') === 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(msg) {
|
||||||
|
process.stderr.write('browserslist: ' + msg + '\n')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArg('--help') || isArg('-h')) {
|
||||||
|
process.stdout.write(pkg.description + '.\n\n' + USAGE + '\n')
|
||||||
|
} else if (isArg('--version') || isArg('-v')) {
|
||||||
|
process.stdout.write('browserslist ' + pkg.version + '\n')
|
||||||
|
} else if (isArg('--update-db')) {
|
||||||
|
/* c8 ignore next 8 */
|
||||||
|
process.stdout.write(
|
||||||
|
'The --update-db command is deprecated.\n' +
|
||||||
|
'Please use npx update-browserslist-db@latest instead.\n'
|
||||||
|
)
|
||||||
|
process.stdout.write('Browserslist DB update will still be made.\n')
|
||||||
|
updateDb(function (str) {
|
||||||
|
process.stdout.write(str)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
var mode = 'browsers'
|
||||||
|
var opts = {}
|
||||||
|
var queries
|
||||||
|
var areas
|
||||||
|
|
||||||
|
for (var i = 0; i < args.length; i++) {
|
||||||
|
if (args[i][0] !== '-') {
|
||||||
|
queries = args[i].replace(/^["']|["']$/g, '')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var arg = args[i].split('=')
|
||||||
|
var name = arg[0]
|
||||||
|
var value = arg[1]
|
||||||
|
|
||||||
|
if (value) value = value.replace(/^["']|["']$/g, '')
|
||||||
|
|
||||||
|
if (name === '--config' || name === '-b') {
|
||||||
|
opts.config = value
|
||||||
|
} else if (name === '--env' || name === '-e') {
|
||||||
|
opts.env = value
|
||||||
|
} else if (name === '--stats' || name === '-s') {
|
||||||
|
opts.stats = value
|
||||||
|
} else if (name === '--coverage' || name === '-c') {
|
||||||
|
if (mode !== 'json') mode = 'coverage'
|
||||||
|
if (value) {
|
||||||
|
areas = value.split(',')
|
||||||
|
} else {
|
||||||
|
areas = ['global']
|
||||||
|
}
|
||||||
|
} else if (name === '--json') {
|
||||||
|
mode = 'json'
|
||||||
|
} else if (name === '--mobile-to-desktop') {
|
||||||
|
/* c8 ignore next */
|
||||||
|
opts.mobileToDesktop = true
|
||||||
|
} else if (name === '--ignore-unknown-versions') {
|
||||||
|
/* c8 ignore next */
|
||||||
|
opts.ignoreUnknownVersions = true
|
||||||
|
} else {
|
||||||
|
error('Unknown arguments ' + args[i] + '.\n\n' + USAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var browsers
|
||||||
|
try {
|
||||||
|
browsers = browserslist(queries, opts)
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name === 'BrowserslistError') {
|
||||||
|
error(e.message)
|
||||||
|
} /* c8 ignore start */ else {
|
||||||
|
throw e
|
||||||
|
} /* c8 ignore end */
|
||||||
|
}
|
||||||
|
|
||||||
|
var coverage
|
||||||
|
if (mode === 'browsers') {
|
||||||
|
browsers.forEach(function (browser) {
|
||||||
|
process.stdout.write(browser + '\n')
|
||||||
|
})
|
||||||
|
} else if (areas) {
|
||||||
|
coverage = areas.map(function (area) {
|
||||||
|
var stats
|
||||||
|
if (area !== 'global') {
|
||||||
|
stats = area
|
||||||
|
} else if (opts.stats) {
|
||||||
|
stats = JSON.parse(fs.readFileSync(opts.stats))
|
||||||
|
}
|
||||||
|
var result = browserslist.coverage(browsers, stats)
|
||||||
|
var round = Math.round(result * 100) / 100.0
|
||||||
|
|
||||||
|
return [area, round]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (mode === 'coverage') {
|
||||||
|
var prefix = 'These browsers account for '
|
||||||
|
process.stdout.write(prefix)
|
||||||
|
coverage.forEach(function (data, index) {
|
||||||
|
var area = data[0]
|
||||||
|
var round = data[1]
|
||||||
|
var end = 'globally'
|
||||||
|
if (area && area !== 'global') {
|
||||||
|
end = 'in the ' + area.toUpperCase()
|
||||||
|
} else if (opts.stats) {
|
||||||
|
end = 'in custom statistics'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index !== 0) {
|
||||||
|
process.stdout.write(prefix.replace(/./g, ' '))
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(round + '% of all users ' + end + '\n')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === 'json') {
|
||||||
|
var data = { browsers: browsers }
|
||||||
|
if (coverage) {
|
||||||
|
data.coverage = coverage.reduce(function (object, j) {
|
||||||
|
object[j[0]] = j[1]
|
||||||
|
return object
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
process.stdout.write(JSON.stringify(data, null, ' ') + '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
cliente.cpython-311.pyc
Normal file
BIN
cliente.cpython-311.pyc
Normal file
Binary file not shown.
64
cliente.py
Normal file
64
cliente.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from pydantic import BaseModel, EmailStr, Field
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from app.models.enums import EstadoCliente
|
||||||
|
|
||||||
|
|
||||||
|
class ClienteBase(BaseModel):
|
||||||
|
nombre: str = Field(..., min_length=1, max_length=255)
|
||||||
|
email: EmailStr
|
||||||
|
rfc: Optional[str] = Field(None, max_length=13)
|
||||||
|
telefono: Optional[str] = Field(None, max_length=20)
|
||||||
|
permite_subclientes: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
class ClienteCreate(ClienteBase):
|
||||||
|
limite_timbres: int = Field(0, ge=0)
|
||||||
|
|
||||||
|
|
||||||
|
class ClienteUpdate(BaseModel):
|
||||||
|
nombre: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||||
|
email: Optional[EmailStr] = None
|
||||||
|
rfc: Optional[str] = Field(None, max_length=13)
|
||||||
|
telefono: Optional[str] = Field(None, max_length=20)
|
||||||
|
limite_timbres: Optional[int] = Field(None, ge=0)
|
||||||
|
estado: Optional[EstadoCliente] = None
|
||||||
|
permite_subclientes: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ClienteResponse(ClienteBase):
|
||||||
|
id: int
|
||||||
|
timbres_consumidos: int
|
||||||
|
estado: EstadoCliente
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: Optional[datetime]
|
||||||
|
|
||||||
|
# Campos calculados dinámicamente
|
||||||
|
limite_timbres: Optional[int] = None
|
||||||
|
timbres_disponibles: Optional[int] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class ClienteWithStats(ClienteResponse):
|
||||||
|
total_subclientes: int = 0
|
||||||
|
timbres_consumidos_subclientes: int = 0
|
||||||
|
total_limites_asignados: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class BalanceResponse(BaseModel):
|
||||||
|
cliente_id: int
|
||||||
|
nombre_cliente: str
|
||||||
|
limite_timbres: int
|
||||||
|
timbres_disponibles: int
|
||||||
|
timbres_consumidos: int
|
||||||
|
porcentaje_uso: float
|
||||||
|
total_subclientes: int = 0
|
||||||
|
timbres_consumidos_subclientes: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class RecargaTimbreCreate(BaseModel):
|
||||||
|
"""Schema específico para recargar timbres"""
|
||||||
|
cantidad: int = Field(..., gt=0)
|
||||||
|
descripcion: Optional[str] = Field(None, max_length=500)
|
||||||
BIN
clientes.cpython-311.pyc
Normal file
BIN
clientes.cpython-311.pyc
Normal file
Binary file not shown.
395
clientes.py
Normal file
395
clientes.py
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import List
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models import Cliente, SubCliente, MovimientoTimbre, LimiteTimbre, Usuario
|
||||||
|
from app.models.enums import TipoMovimiento, TipoLimite
|
||||||
|
from app.schemas import (
|
||||||
|
ClienteCreate, ClienteUpdate, ClienteResponse, ClienteWithStats,
|
||||||
|
BalanceResponse, RecargaTimbreCreate, MessageResponse, LimiteHistorialResponse, LimiteResponse
|
||||||
|
)
|
||||||
|
from app.dependencies import get_current_user
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=ClienteResponse, status_code=status.HTTP_201_CREATED)
|
||||||
|
def crear_cliente(
|
||||||
|
cliente: ClienteCreate,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Crear un nuevo cliente con su límite de timbres inicial
|
||||||
|
"""
|
||||||
|
# Verificar si el email ya existe
|
||||||
|
if db.query(Cliente).filter(Cliente.email == cliente.email).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="El email ya está registrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verificar si el RFC ya existe (si se proporciona)
|
||||||
|
if cliente.rfc and db.query(Cliente).filter(Cliente.rfc == cliente.rfc).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="El RFC ya está registrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
db_cliente = Cliente(
|
||||||
|
nombre=cliente.nombre,
|
||||||
|
email=cliente.email,
|
||||||
|
rfc=cliente.rfc,
|
||||||
|
telefono=cliente.telefono,
|
||||||
|
permite_subclientes=cliente.permite_subclientes
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(db_cliente)
|
||||||
|
db.flush() # Para obtener el ID sin hacer commit
|
||||||
|
|
||||||
|
# Crear el límite inicial si se proporciona
|
||||||
|
if cliente.limite_timbres > 0:
|
||||||
|
limite_inicial = LimiteTimbre(
|
||||||
|
cliente_id=db_cliente.id,
|
||||||
|
tipo=TipoLimite.ASIGNACION_INICIAL,
|
||||||
|
cantidad=cliente.limite_timbres,
|
||||||
|
descripcion="Límite inicial al crear el cliente"
|
||||||
|
)
|
||||||
|
db.add(limite_inicial)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_cliente)
|
||||||
|
|
||||||
|
# Agregar campos calculados
|
||||||
|
response = ClienteResponse.model_validate(db_cliente)
|
||||||
|
response.limite_timbres = db_cliente.get_limite_actual(db)
|
||||||
|
response.timbres_disponibles = db_cliente.get_timbres_disponibles(db)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[ClienteResponse])
|
||||||
|
def listar_clientes(
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Listar todos los clientes
|
||||||
|
"""
|
||||||
|
clientes = db.query(Cliente).offset(skip).limit(limit).all()
|
||||||
|
|
||||||
|
# Agregar campos calculados a cada cliente
|
||||||
|
response = []
|
||||||
|
for cliente in clientes:
|
||||||
|
cliente_dict = ClienteResponse.model_validate(cliente)
|
||||||
|
cliente_dict.limite_timbres = cliente.get_limite_actual(db)
|
||||||
|
cliente_dict.timbres_disponibles = cliente.get_timbres_disponibles(db)
|
||||||
|
response.append(cliente_dict)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{cliente_id}", response_model=ClienteWithStats)
|
||||||
|
def obtener_cliente(
|
||||||
|
cliente_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Obtener un cliente por ID con estadísticas de subclientes
|
||||||
|
"""
|
||||||
|
cliente = db.query(Cliente).filter(Cliente.id == cliente_id).first()
|
||||||
|
|
||||||
|
if not cliente:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cliente no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Obtener estadísticas de subclientes
|
||||||
|
total_subclientes = db.query(SubCliente).filter(
|
||||||
|
SubCliente.cliente_id == cliente_id
|
||||||
|
).count()
|
||||||
|
|
||||||
|
timbres_consumidos_subclientes = db.query(SubCliente).filter(
|
||||||
|
SubCliente.cliente_id == cliente_id
|
||||||
|
).with_entities(SubCliente.timbres_consumidos).all()
|
||||||
|
|
||||||
|
total_consumidos_sub = sum([t[0] for t in timbres_consumidos_subclientes])
|
||||||
|
|
||||||
|
# Obtener total de límites asignados
|
||||||
|
from sqlalchemy import func as sql_func
|
||||||
|
total_limites = db.query(sql_func.sum(LimiteTimbre.cantidad)).filter(
|
||||||
|
LimiteTimbre.cliente_id == cliente_id
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
# Crear respuesta con estadísticas
|
||||||
|
response = ClienteWithStats.model_validate(cliente)
|
||||||
|
response.limite_timbres = cliente.get_limite_actual(db)
|
||||||
|
response.timbres_disponibles = cliente.get_timbres_disponibles(db)
|
||||||
|
response.total_subclientes = total_subclientes
|
||||||
|
response.timbres_consumidos_subclientes = total_consumidos_sub
|
||||||
|
response.total_limites_asignados = total_limites
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{cliente_id}", response_model=ClienteResponse)
|
||||||
|
def actualizar_cliente(
|
||||||
|
cliente_id: int,
|
||||||
|
cliente_update: ClienteUpdate,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Actualizar información de un cliente
|
||||||
|
"""
|
||||||
|
db_cliente = db.query(Cliente).filter(Cliente.id == cliente_id).first()
|
||||||
|
|
||||||
|
if not db_cliente:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cliente no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
update_data = cliente_update.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
# Verificar email único si se actualiza
|
||||||
|
if "email" in update_data and update_data["email"] != db_cliente.email:
|
||||||
|
if db.query(Cliente).filter(Cliente.email == update_data["email"]).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="El email ya está registrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verificar RFC único si se actualiza
|
||||||
|
if "rfc" in update_data and update_data["rfc"] != db_cliente.rfc:
|
||||||
|
if db.query(Cliente).filter(Cliente.rfc == update_data["rfc"]).first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="El RFC ya está registrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Actualizar campos básicos (excluyendo limite_timbres que ya no existe)
|
||||||
|
update_data_without_limite = {k: v for k, v in update_data.items() if k != 'limite_timbres'}
|
||||||
|
|
||||||
|
for field, value in update_data_without_limite.items():
|
||||||
|
setattr(db_cliente, field, value)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_cliente)
|
||||||
|
|
||||||
|
# Preparar respuesta con campos calculados
|
||||||
|
response = ClienteResponse.model_validate(db_cliente)
|
||||||
|
response.limite_timbres = db_cliente.get_limite_actual(db)
|
||||||
|
response.timbres_disponibles = db_cliente.get_timbres_disponibles(db)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{cliente_id}", response_model=MessageResponse)
|
||||||
|
def eliminar_cliente(
|
||||||
|
cliente_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Eliminar un cliente (también elimina sus subclientes y movimientos)
|
||||||
|
"""
|
||||||
|
db_cliente = db.query(Cliente).filter(Cliente.id == cliente_id).first()
|
||||||
|
|
||||||
|
if not db_cliente:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cliente no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.delete(db_cliente)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {"message": f"Cliente {db_cliente.nombre} eliminado exitosamente"}
|
||||||
|
|
||||||
|
#==========================================================================================
|
||||||
|
@router.get("/{cliente_id}/balance", response_model=BalanceResponse)
|
||||||
|
def obtener_balance(
|
||||||
|
cliente_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Obtener el balance de timbres de un cliente
|
||||||
|
"""
|
||||||
|
cliente = db.query(Cliente).filter(Cliente.id == cliente_id).first()
|
||||||
|
|
||||||
|
if not cliente:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cliente no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
total_subclientes = db.query(SubCliente).filter(
|
||||||
|
SubCliente.cliente_id == cliente_id
|
||||||
|
).count()
|
||||||
|
|
||||||
|
timbres_consumidos_subclientes = sum([
|
||||||
|
s.timbres_consumidos for s in db.query(SubCliente).filter(
|
||||||
|
SubCliente.cliente_id == cliente_id
|
||||||
|
).all()
|
||||||
|
])
|
||||||
|
|
||||||
|
limite_actual = cliente.get_limite_actual(db)
|
||||||
|
timbres_disponibles = cliente.get_timbres_disponibles(db)
|
||||||
|
porcentaje = (cliente.timbres_consumidos / limite_actual * 100) if limite_actual > 0 else 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"cliente_id": cliente.id,
|
||||||
|
"nombre_cliente": cliente.nombre,
|
||||||
|
"limite_timbres": limite_actual,
|
||||||
|
"timbres_disponibles": timbres_disponibles,
|
||||||
|
"timbres_consumidos": cliente.timbres_consumidos,
|
||||||
|
"porcentaje_uso": round(porcentaje, 2),
|
||||||
|
"total_subclientes": total_subclientes,
|
||||||
|
"timbres_consumidos_subclientes": timbres_consumidos_subclientes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#==========================================================================================
|
||||||
|
|
||||||
|
@router.post("/{cliente_id}/recargar", response_model=MessageResponse)
|
||||||
|
def recargar_timbres(
|
||||||
|
cliente_id: int,
|
||||||
|
recarga: RecargaTimbreCreate,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Recargar timbres para un cliente (incrementa límite y disponibles)
|
||||||
|
"""
|
||||||
|
cliente = db.query(Cliente).filter(Cliente.id == cliente_id).first()
|
||||||
|
|
||||||
|
if not cliente:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cliente no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Crear nuevo límite (incremento)
|
||||||
|
nuevo_limite = LimiteTimbre(
|
||||||
|
cliente_id=cliente_id,
|
||||||
|
tipo=TipoLimite.INCREMENTO,
|
||||||
|
cantidad=recarga.cantidad,
|
||||||
|
descripcion=recarga.descripcion or f"Recarga de {recarga.cantidad} timbres"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(nuevo_limite)
|
||||||
|
db.flush()
|
||||||
|
|
||||||
|
# Registrar movimiento de recarga
|
||||||
|
limite_actual = cliente.get_limite_actual(db)
|
||||||
|
timbres_disponibles = cliente.get_timbres_disponibles(db)
|
||||||
|
|
||||||
|
movimiento = MovimientoTimbre(
|
||||||
|
cliente_id=cliente_id,
|
||||||
|
tipo=TipoMovimiento.RECARGA,
|
||||||
|
cantidad=recarga.cantidad,
|
||||||
|
descripcion=recarga.descripcion,
|
||||||
|
balance_cliente=timbres_disponibles
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(movimiento)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": f"Se recargaron {recarga.cantidad} timbres exitosamente",
|
||||||
|
"detail": {
|
||||||
|
"nuevo_limite": limite_actual,
|
||||||
|
"timbres_disponibles": timbres_disponibles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{cliente_id}/limites", response_model=LimiteHistorialResponse)
|
||||||
|
def obtener_historial_limites(
|
||||||
|
cliente_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Obtener el historial completo de límites de un cliente
|
||||||
|
"""
|
||||||
|
cliente = db.query(Cliente).filter(Cliente.id == cliente_id).first()
|
||||||
|
|
||||||
|
if not cliente:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cliente no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Obtener todos los límites
|
||||||
|
limites = db.query(LimiteTimbre).filter(
|
||||||
|
LimiteTimbre.cliente_id == cliente_id
|
||||||
|
).order_by(LimiteTimbre.created_at.desc()).all()
|
||||||
|
|
||||||
|
# Calcular totales
|
||||||
|
from sqlalchemy import func as sql_func
|
||||||
|
limite_actual = db.query(sql_func.sum(LimiteTimbre.cantidad)).filter(
|
||||||
|
LimiteTimbre.cliente_id == cliente_id,
|
||||||
|
LimiteTimbre.activo == 1
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
total_asignado = db.query(sql_func.sum(LimiteTimbre.cantidad)).filter(
|
||||||
|
LimiteTimbre.cliente_id == cliente_id
|
||||||
|
).scalar() or 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"limite_actual": limite_actual,
|
||||||
|
"total_asignado": total_asignado,
|
||||||
|
"historial": limites
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{cliente_id}/ajustar-limite", response_model=MessageResponse)
|
||||||
|
def ajustar_limite(
|
||||||
|
cliente_id: int,
|
||||||
|
cantidad: int,
|
||||||
|
descripcion: str = None,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Ajustar el límite de un cliente (puede ser positivo o negativo)
|
||||||
|
"""
|
||||||
|
cliente = db.query(Cliente).filter(Cliente.id == cliente_id).first()
|
||||||
|
|
||||||
|
if not cliente:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cliente no encontrado"
|
||||||
|
)
|
||||||
|
|
||||||
|
tipo = TipoLimite.INCREMENTO if cantidad > 0 else TipoLimite.DECREMENTO
|
||||||
|
|
||||||
|
nuevo_limite = LimiteTimbre(
|
||||||
|
cliente_id=cliente_id,
|
||||||
|
tipo=tipo,
|
||||||
|
cantidad=abs(cantidad),
|
||||||
|
descripcion=descripcion or f"Ajuste de límite: {cantidad}"
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(nuevo_limite)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
limite_actual = cliente.get_limite_actual(db)
|
||||||
|
timbres_disponibles = cliente.get_timbres_disponibles(db)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": "Límite ajustado exitosamente",
|
||||||
|
"detail": {
|
||||||
|
"ajuste": cantidad,
|
||||||
|
"nuevo_limite": limite_actual,
|
||||||
|
"timbres_disponibles": timbres_disponibles
|
||||||
|
}
|
||||||
|
}
|
||||||
6
clsx.d.mts
Normal file
6
clsx.d.mts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type ClassValue = ClassArray | ClassDictionary | string | number | bigint | null | boolean | undefined;
|
||||||
|
export type ClassDictionary = Record<string, any>;
|
||||||
|
export type ClassArray = ClassValue[];
|
||||||
|
|
||||||
|
export function clsx(...inputs: ClassValue[]): string;
|
||||||
|
export default clsx;
|
||||||
10
clsx.d.ts
vendored
Normal file
10
clsx.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
declare namespace clsx {
|
||||||
|
type ClassValue = ClassArray | ClassDictionary | string | number | bigint | null | boolean | undefined;
|
||||||
|
type ClassDictionary = Record<string, any>;
|
||||||
|
type ClassArray = ClassValue[];
|
||||||
|
function clsx(...inputs: ClassValue[]): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare function clsx(...inputs: clsx.ClassValue[]): string;
|
||||||
|
|
||||||
|
export = clsx;
|
||||||
24
commit-msg.sample
Normal file
24
commit-msg.sample
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An example hook script to check the commit log message.
|
||||||
|
# Called by "git commit" with one argument, the name of the file
|
||||||
|
# that has the commit message. The hook should exit with non-zero
|
||||||
|
# status after issuing an appropriate message if it wants to stop the
|
||||||
|
# commit. The hook is allowed to edit the commit message file.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "commit-msg".
|
||||||
|
|
||||||
|
# Uncomment the below to add a Signed-off-by line to the message.
|
||||||
|
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||||
|
# hook is more suited to it.
|
||||||
|
#
|
||||||
|
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||||
|
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||||
|
|
||||||
|
# This example catches duplicate Signed-off-by lines.
|
||||||
|
|
||||||
|
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||||
|
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||||
|
echo >&2 Duplicate Signed-off-by lines.
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
BIN
common.cpython-311.pyc
Normal file
BIN
common.cpython-311.pyc
Normal file
Binary file not shown.
7
common.py
Normal file
7
common.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class MessageResponse(BaseModel):
|
||||||
|
message: str
|
||||||
|
detail: Optional[dict] = None
|
||||||
15
config
Normal file
15
config
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[core]
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = false
|
||||||
|
bare = false
|
||||||
|
logallrefupdates = true
|
||||||
|
symlinks = false
|
||||||
|
ignorecase = true
|
||||||
|
[remote "origin"]
|
||||||
|
url = https://git.aduanasoft.com/ADUANASOFT/control_mve_frontend.git
|
||||||
|
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||||
|
[branch "main"]
|
||||||
|
remote = origin
|
||||||
|
merge = refs/heads/main
|
||||||
|
vscode-merge-base = origin/main
|
||||||
|
gk-last-accessed = 2025-12-23T14:12:27.516Z
|
||||||
BIN
config.cpython-311.pyc
Normal file
BIN
config.cpython-311.pyc
Normal file
Binary file not shown.
30
config.py
Normal file
30
config.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
# Database
|
||||||
|
POSTGRES_USER: str
|
||||||
|
POSTGRES_PASSWORD: str
|
||||||
|
POSTGRES_DB: str
|
||||||
|
POSTGRES_HOST: str
|
||||||
|
POSTGRES_PORT: int
|
||||||
|
|
||||||
|
# API
|
||||||
|
API_PORT: int = 8000
|
||||||
|
API_HOST: str = "0.0.0.0"
|
||||||
|
|
||||||
|
# Security
|
||||||
|
SECRET_KEY: str
|
||||||
|
ALGORITHM: str = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||||
|
|
||||||
|
@property
|
||||||
|
def DATABASE_URL(self) -> str:
|
||||||
|
return f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env"
|
||||||
|
case_sensitive = True
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
162
core.json
Normal file
162
core.json
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
{
|
||||||
|
"assert": true,
|
||||||
|
"node:assert": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"assert/strict": ">= 15",
|
||||||
|
"node:assert/strict": ">= 16",
|
||||||
|
"async_hooks": ">= 8",
|
||||||
|
"node:async_hooks": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"buffer_ieee754": ">= 0.5 && < 0.9.7",
|
||||||
|
"buffer": true,
|
||||||
|
"node:buffer": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"child_process": true,
|
||||||
|
"node:child_process": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"cluster": ">= 0.5",
|
||||||
|
"node:cluster": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"console": true,
|
||||||
|
"node:console": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"constants": true,
|
||||||
|
"node:constants": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"crypto": true,
|
||||||
|
"node:crypto": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_debug_agent": ">= 1 && < 8",
|
||||||
|
"_debugger": "< 8",
|
||||||
|
"dgram": true,
|
||||||
|
"node:dgram": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"diagnostics_channel": [">= 14.17 && < 15", ">= 15.1"],
|
||||||
|
"node:diagnostics_channel": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"dns": true,
|
||||||
|
"node:dns": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"dns/promises": ">= 15",
|
||||||
|
"node:dns/promises": ">= 16",
|
||||||
|
"domain": ">= 0.7.12",
|
||||||
|
"node:domain": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"events": true,
|
||||||
|
"node:events": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"freelist": "< 6",
|
||||||
|
"fs": true,
|
||||||
|
"node:fs": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"fs/promises": [">= 10 && < 10.1", ">= 14"],
|
||||||
|
"node:fs/promises": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_http_agent": ">= 0.11.1",
|
||||||
|
"node:_http_agent": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_http_client": ">= 0.11.1",
|
||||||
|
"node:_http_client": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_http_common": ">= 0.11.1",
|
||||||
|
"node:_http_common": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_http_incoming": ">= 0.11.1",
|
||||||
|
"node:_http_incoming": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_http_outgoing": ">= 0.11.1",
|
||||||
|
"node:_http_outgoing": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_http_server": ">= 0.11.1",
|
||||||
|
"node:_http_server": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"http": true,
|
||||||
|
"node:http": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"http2": ">= 8.8",
|
||||||
|
"node:http2": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"https": true,
|
||||||
|
"node:https": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"inspector": ">= 8",
|
||||||
|
"node:inspector": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"inspector/promises": [">= 19"],
|
||||||
|
"node:inspector/promises": [">= 19"],
|
||||||
|
"_linklist": "< 8",
|
||||||
|
"module": true,
|
||||||
|
"node:module": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"net": true,
|
||||||
|
"node:net": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"node-inspect/lib/_inspect": ">= 7.6 && < 12",
|
||||||
|
"node-inspect/lib/internal/inspect_client": ">= 7.6 && < 12",
|
||||||
|
"node-inspect/lib/internal/inspect_repl": ">= 7.6 && < 12",
|
||||||
|
"os": true,
|
||||||
|
"node:os": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"path": true,
|
||||||
|
"node:path": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"path/posix": ">= 15.3",
|
||||||
|
"node:path/posix": ">= 16",
|
||||||
|
"path/win32": ">= 15.3",
|
||||||
|
"node:path/win32": ">= 16",
|
||||||
|
"perf_hooks": ">= 8.5",
|
||||||
|
"node:perf_hooks": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"process": ">= 1",
|
||||||
|
"node:process": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"punycode": ">= 0.5",
|
||||||
|
"node:punycode": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"querystring": true,
|
||||||
|
"node:querystring": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"readline": true,
|
||||||
|
"node:readline": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"readline/promises": ">= 17",
|
||||||
|
"node:readline/promises": ">= 17",
|
||||||
|
"repl": true,
|
||||||
|
"node:repl": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"node:sea": [">= 20.12 && < 21", ">= 21.7"],
|
||||||
|
"smalloc": ">= 0.11.5 && < 3",
|
||||||
|
"node:sqlite": [">= 22.13 && < 23", ">= 23.4"],
|
||||||
|
"_stream_duplex": ">= 0.9.4",
|
||||||
|
"node:_stream_duplex": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_stream_transform": ">= 0.9.4",
|
||||||
|
"node:_stream_transform": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_stream_wrap": ">= 1.4.1",
|
||||||
|
"node:_stream_wrap": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_stream_passthrough": ">= 0.9.4",
|
||||||
|
"node:_stream_passthrough": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_stream_readable": ">= 0.9.4",
|
||||||
|
"node:_stream_readable": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_stream_writable": ">= 0.9.4",
|
||||||
|
"node:_stream_writable": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"stream": true,
|
||||||
|
"node:stream": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"stream/consumers": ">= 16.7",
|
||||||
|
"node:stream/consumers": ">= 16.7",
|
||||||
|
"stream/promises": ">= 15",
|
||||||
|
"node:stream/promises": ">= 16",
|
||||||
|
"stream/web": ">= 16.5",
|
||||||
|
"node:stream/web": ">= 16.5",
|
||||||
|
"string_decoder": true,
|
||||||
|
"node:string_decoder": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"sys": [">= 0.4 && < 0.7", ">= 0.8"],
|
||||||
|
"node:sys": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"test/reporters": ">= 19.9 && < 20.2",
|
||||||
|
"node:test/reporters": [">= 18.17 && < 19", ">= 19.9", ">= 20"],
|
||||||
|
"test/mock_loader": ">= 22.3 && < 22.7",
|
||||||
|
"node:test/mock_loader": ">= 22.3 && < 22.7",
|
||||||
|
"node:test": [">= 16.17 && < 17", ">= 18"],
|
||||||
|
"timers": true,
|
||||||
|
"node:timers": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"timers/promises": ">= 15",
|
||||||
|
"node:timers/promises": ">= 16",
|
||||||
|
"_tls_common": ">= 0.11.13",
|
||||||
|
"node:_tls_common": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"_tls_legacy": ">= 0.11.3 && < 10",
|
||||||
|
"_tls_wrap": ">= 0.11.3",
|
||||||
|
"node:_tls_wrap": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"tls": true,
|
||||||
|
"node:tls": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"trace_events": ">= 10",
|
||||||
|
"node:trace_events": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"tty": true,
|
||||||
|
"node:tty": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"url": true,
|
||||||
|
"node:url": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"util": true,
|
||||||
|
"node:util": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"util/types": ">= 15.3",
|
||||||
|
"node:util/types": ">= 16",
|
||||||
|
"v8/tools/arguments": ">= 10 && < 12",
|
||||||
|
"v8/tools/codemap": [">= 4.4 && < 5", ">= 5.2 && < 12"],
|
||||||
|
"v8/tools/consarray": [">= 4.4 && < 5", ">= 5.2 && < 12"],
|
||||||
|
"v8/tools/csvparser": [">= 4.4 && < 5", ">= 5.2 && < 12"],
|
||||||
|
"v8/tools/logreader": [">= 4.4 && < 5", ">= 5.2 && < 12"],
|
||||||
|
"v8/tools/profile_view": [">= 4.4 && < 5", ">= 5.2 && < 12"],
|
||||||
|
"v8/tools/splaytree": [">= 4.4 && < 5", ">= 5.2 && < 12"],
|
||||||
|
"v8": ">= 1",
|
||||||
|
"node:v8": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"vm": true,
|
||||||
|
"node:vm": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"wasi": [">= 13.4 && < 13.5", ">= 18.17 && < 19", ">= 20"],
|
||||||
|
"node:wasi": [">= 18.17 && < 19", ">= 20"],
|
||||||
|
"worker_threads": ">= 11.7",
|
||||||
|
"node:worker_threads": [">= 14.18 && < 15", ">= 16"],
|
||||||
|
"zlib": ">= 0.5",
|
||||||
|
"node:zlib": [">= 14.18 && < 15", ">= 16"]
|
||||||
|
}
|
||||||
9
crypto.js
Normal file
9
crypto.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
require('thenify-all').withCallback(
|
||||||
|
require('crypto'),
|
||||||
|
exports, [
|
||||||
|
'pbkdf2',
|
||||||
|
'pseudoRandomBytes',
|
||||||
|
'randomBytes'
|
||||||
|
]
|
||||||
|
)
|
||||||
16
cssesc
Normal file
16
cssesc
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../cssesc/bin/cssesc" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../cssesc/bin/cssesc" "$@"
|
||||||
|
fi
|
||||||
17
cssesc.cmd
Normal file
17
cssesc.cmd
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\cssesc\bin\cssesc" %*
|
||||||
110
cssesc.js
Normal file
110
cssesc.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*! https://mths.be/cssesc v3.0.0 by @mathias */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var object = {};
|
||||||
|
var hasOwnProperty = object.hasOwnProperty;
|
||||||
|
var merge = function merge(options, defaults) {
|
||||||
|
if (!options) {
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
var result = {};
|
||||||
|
for (var key in defaults) {
|
||||||
|
// `if (defaults.hasOwnProperty(key) { … }` is not needed here, since
|
||||||
|
// only recognized option names are used.
|
||||||
|
result[key] = hasOwnProperty.call(options, key) ? options[key] : defaults[key];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
var regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/;
|
||||||
|
var regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/;
|
||||||
|
var regexAlwaysEscape = /['"\\]/;
|
||||||
|
var regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g;
|
||||||
|
|
||||||
|
// https://mathiasbynens.be/notes/css-escapes#css
|
||||||
|
var cssesc = function cssesc(string, options) {
|
||||||
|
options = merge(options, cssesc.options);
|
||||||
|
if (options.quotes != 'single' && options.quotes != 'double') {
|
||||||
|
options.quotes = 'single';
|
||||||
|
}
|
||||||
|
var quote = options.quotes == 'double' ? '"' : '\'';
|
||||||
|
var isIdentifier = options.isIdentifier;
|
||||||
|
|
||||||
|
var firstChar = string.charAt(0);
|
||||||
|
var output = '';
|
||||||
|
var counter = 0;
|
||||||
|
var length = string.length;
|
||||||
|
while (counter < length) {
|
||||||
|
var character = string.charAt(counter++);
|
||||||
|
var codePoint = character.charCodeAt();
|
||||||
|
var value = void 0;
|
||||||
|
// If it’s not a printable ASCII character…
|
||||||
|
if (codePoint < 0x20 || codePoint > 0x7E) {
|
||||||
|
if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) {
|
||||||
|
// It’s a high surrogate, and there is a next character.
|
||||||
|
var extra = string.charCodeAt(counter++);
|
||||||
|
if ((extra & 0xFC00) == 0xDC00) {
|
||||||
|
// next character is low surrogate
|
||||||
|
codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000;
|
||||||
|
} else {
|
||||||
|
// It’s an unmatched surrogate; only append this code unit, in case
|
||||||
|
// the next code unit is the high surrogate of a surrogate pair.
|
||||||
|
counter--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||||||
|
} else {
|
||||||
|
if (options.escapeEverything) {
|
||||||
|
if (regexAnySingleEscape.test(character)) {
|
||||||
|
value = '\\' + character;
|
||||||
|
} else {
|
||||||
|
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||||||
|
}
|
||||||
|
} else if (/[\t\n\f\r\x0B]/.test(character)) {
|
||||||
|
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||||||
|
} else if (character == '\\' || !isIdentifier && (character == '"' && quote == character || character == '\'' && quote == character) || isIdentifier && regexSingleEscape.test(character)) {
|
||||||
|
value = '\\' + character;
|
||||||
|
} else {
|
||||||
|
value = character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIdentifier) {
|
||||||
|
if (/^-[-\d]/.test(output)) {
|
||||||
|
output = '\\-' + output.slice(1);
|
||||||
|
} else if (/\d/.test(firstChar)) {
|
||||||
|
output = '\\3' + firstChar + ' ' + output.slice(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove spaces after `\HEX` escapes that are not followed by a hex digit,
|
||||||
|
// since they’re redundant. Note that this is only possible if the escape
|
||||||
|
// sequence isn’t preceded by an odd number of backslashes.
|
||||||
|
output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) {
|
||||||
|
if ($1 && $1.length % 2) {
|
||||||
|
// It’s not safe to remove the space, so don’t.
|
||||||
|
return $0;
|
||||||
|
}
|
||||||
|
// Strip the space.
|
||||||
|
return ($1 || '') + $2;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isIdentifier && options.wrap) {
|
||||||
|
return quote + output + quote;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Expose default options (so they can be overridden globally).
|
||||||
|
cssesc.options = {
|
||||||
|
'escapeEverything': false,
|
||||||
|
'isIdentifier': false,
|
||||||
|
'quotes': 'single',
|
||||||
|
'wrap': false
|
||||||
|
};
|
||||||
|
|
||||||
|
cssesc.version = '3.0.0';
|
||||||
|
|
||||||
|
module.exports = cssesc;
|
||||||
28
cssesc.ps1
Normal file
28
cssesc.ps1
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../cssesc/bin/cssesc" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../cssesc/bin/cssesc" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../cssesc/bin/cssesc" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../cssesc/bin/cssesc" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
BIN
database.cpython-311.pyc
Normal file
BIN
database.cpython-311.pyc
Normal file
Binary file not shown.
20
database.py
Normal file
20
database.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
engine = create_engine(settings.DATABASE_URL)
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
"""
|
||||||
|
Dependency para obtener la sesión de base de datos
|
||||||
|
"""
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
BIN
dependencies.cpython-311.pyc
Normal file
BIN
dependencies.cpython-311.pyc
Normal file
Binary file not shown.
59
dependencies.py
Normal file
59
dependencies.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from fastapi import Depends, HTTPException, status
|
||||||
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models import Usuario
|
||||||
|
from app.auth import decode_access_token
|
||||||
|
|
||||||
|
security = HTTPBearer()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user(
|
||||||
|
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
) -> Usuario:
|
||||||
|
"""
|
||||||
|
Dependency para obtener el usuario actual desde el token JWT
|
||||||
|
"""
|
||||||
|
credentials_exception = HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="No se pudo validar las credenciales",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
token = credentials.credentials
|
||||||
|
payload = decode_access_token(token)
|
||||||
|
|
||||||
|
if payload is None:
|
||||||
|
raise credentials_exception
|
||||||
|
|
||||||
|
username: str = payload.get("sub")
|
||||||
|
if username is None:
|
||||||
|
raise credentials_exception
|
||||||
|
|
||||||
|
user = db.query(Usuario).filter(Usuario.username == username).first()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
raise credentials_exception
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Usuario inactivo"
|
||||||
|
)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_active_superuser(
|
||||||
|
current_user: Usuario = Depends(get_current_user)
|
||||||
|
) -> Usuario:
|
||||||
|
"""
|
||||||
|
Dependency para verificar que el usuario sea superusuario
|
||||||
|
"""
|
||||||
|
if not current_user.is_superuser:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="No tiene permisos suficientes"
|
||||||
|
)
|
||||||
|
return current_user
|
||||||
1
description
Normal file
1
description
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Unnamed repository; edit this file 'description' to name the repository.
|
||||||
2
dev-fallback.js
Normal file
2
dev-fallback.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const node_env = globalThis.process?.env?.NODE_ENV;
|
||||||
|
export default node_env && !node_env.toLowerCase().startsWith('prod');
|
||||||
274
didYouMean-1.2.1.js
Normal file
274
didYouMean-1.2.1.js
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
didYouMean.js - A simple JavaScript matching engine
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
[Available on GitHub](https://github.com/dcporter/didyoumean.js).
|
||||||
|
|
||||||
|
A super-simple, highly optimized JS library for matching human-quality input to a list of potential
|
||||||
|
matches. You can use it to suggest a misspelled command-line utility option to a user, or to offer
|
||||||
|
links to nearby valid URLs on your 404 page. (The examples below are taken from a personal project,
|
||||||
|
my [HTML5 business card](http://dcporter.aws.af.cm/me), which uses didYouMean.js to suggest correct
|
||||||
|
URLs from misspelled ones, such as [dcporter.aws.af.cm/me/instagarm](http://dcporter.aws.af.cm/me/instagarm).)
|
||||||
|
Uses the [Levenshtein distance algorithm](https://en.wikipedia.org/wiki/Levenshtein_distance).
|
||||||
|
|
||||||
|
didYouMean.js works in the browser as well as in node.js. To install it for use in node:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install didyoumean
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
Matching against a list of strings:
|
||||||
|
```
|
||||||
|
var input = 'insargrm'
|
||||||
|
var list = ['facebook', 'twitter', 'instagram', 'linkedin'];
|
||||||
|
console.log(didYouMean(input, list));
|
||||||
|
> 'instagram'
|
||||||
|
// The method matches 'insargrm' to 'instagram'.
|
||||||
|
|
||||||
|
input = 'google plus';
|
||||||
|
console.log(didYouMean(input, list));
|
||||||
|
> null
|
||||||
|
// The method was unable to find 'google plus' in the list of options.
|
||||||
|
```
|
||||||
|
|
||||||
|
Matching against a list of objects:
|
||||||
|
```
|
||||||
|
var input = 'insargrm';
|
||||||
|
var list = [ { id: 'facebook' }, { id: 'twitter' }, { id: 'instagram' }, { id: 'linkedin' } ];
|
||||||
|
var key = 'id';
|
||||||
|
console.log(didYouMean(input, list, key));
|
||||||
|
> 'instagram'
|
||||||
|
// The method returns the matching value.
|
||||||
|
|
||||||
|
didYouMean.returnWinningObject = true;
|
||||||
|
console.log(didYouMean(input, list, key));
|
||||||
|
> { id: 'instagram' }
|
||||||
|
// The method returns the matching object.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
didYouMean(str, list, [key])
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
- str: The string input to match.
|
||||||
|
- list: An array of strings or objects to match against.
|
||||||
|
- key (OPTIONAL): If your list array contains objects, you must specify the key which contains the string
|
||||||
|
to match against.
|
||||||
|
|
||||||
|
Returns: the closest matching string, or null if no strings exceed the threshold.
|
||||||
|
|
||||||
|
|
||||||
|
Options
|
||||||
|
-------
|
||||||
|
|
||||||
|
Options are set on the didYouMean function object. You may change them at any time.
|
||||||
|
|
||||||
|
### threshold
|
||||||
|
|
||||||
|
By default, the method will only return strings whose edit distance is less than 40% (0.4x) of their length.
|
||||||
|
For example, if a ten-letter string is five edits away from its nearest match, the method will return null.
|
||||||
|
|
||||||
|
You can control this by setting the "threshold" value on the didYouMean function. For example, to set the
|
||||||
|
edit distance threshold to 50% of the input string's length:
|
||||||
|
|
||||||
|
```
|
||||||
|
didYouMean.threshold = 0.5;
|
||||||
|
```
|
||||||
|
|
||||||
|
To return the nearest match no matter the threshold, set this value to null.
|
||||||
|
|
||||||
|
### thresholdAbsolute
|
||||||
|
|
||||||
|
This option behaves the same as threshold, but instead takes an integer number of edit steps. For example,
|
||||||
|
if thresholdAbsolute is set to 20 (the default), then the method will only return strings whose edit distance
|
||||||
|
is less than 20. Both options apply.
|
||||||
|
|
||||||
|
### caseSensitive
|
||||||
|
|
||||||
|
By default, the method will perform case-insensitive comparisons. If you wish to force case sensitivity, set
|
||||||
|
the "caseSensitive" value to true:
|
||||||
|
|
||||||
|
```
|
||||||
|
didYouMean.caseSensitive = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
### nullResultValue
|
||||||
|
|
||||||
|
By default, the method will return null if there is no sufficiently close match. You can change this value here.
|
||||||
|
|
||||||
|
### returnWinningObject
|
||||||
|
|
||||||
|
By default, the method will return the winning string value (if any). If your list contains objects rather
|
||||||
|
than strings, you may set returnWinningObject to true.
|
||||||
|
|
||||||
|
```
|
||||||
|
didYouMean.returnWinningObject = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
This option has no effect on lists of strings.
|
||||||
|
|
||||||
|
### returnFirstMatch
|
||||||
|
|
||||||
|
By default, the method will search all values and return the closest match. If you're simply looking for a "good-
|
||||||
|
enough" match, you can set your thresholds appropriately and set returnFirstMatch to true to substantially speed
|
||||||
|
things up.
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
didYouMean copyright (c) 2013-2014 Dave Porter.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License
|
||||||
|
[here](http://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// The didYouMean method.
|
||||||
|
function didYouMean(str, list, key) {
|
||||||
|
if (!str) return null;
|
||||||
|
|
||||||
|
// If we're running a case-insensitive search, smallify str.
|
||||||
|
if (!didYouMean.caseSensitive) { str = str.toLowerCase(); }
|
||||||
|
|
||||||
|
// Calculate the initial value (the threshold) if present.
|
||||||
|
var thresholdRelative = didYouMean.threshold === null ? null : didYouMean.threshold * str.length,
|
||||||
|
thresholdAbsolute = didYouMean.thresholdAbsolute,
|
||||||
|
winningVal;
|
||||||
|
if (thresholdRelative !== null && thresholdAbsolute !== null) winningVal = Math.min(thresholdRelative, thresholdAbsolute);
|
||||||
|
else if (thresholdRelative !== null) winningVal = thresholdRelative;
|
||||||
|
else if (thresholdAbsolute !== null) winningVal = thresholdAbsolute;
|
||||||
|
else winningVal = null;
|
||||||
|
|
||||||
|
// Get the edit distance to each option. If the closest one is less than 40% (by default) of str's length,
|
||||||
|
// then return it.
|
||||||
|
var winner, candidate, testCandidate, val,
|
||||||
|
i, len = list.length;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
// Get item.
|
||||||
|
candidate = list[i];
|
||||||
|
// If there's a key, get the candidate value out of the object.
|
||||||
|
if (key) { candidate = candidate[key]; }
|
||||||
|
// Gatekeep.
|
||||||
|
if (!candidate) { continue; }
|
||||||
|
// If we're running a case-insensitive search, smallify the candidate.
|
||||||
|
if (!didYouMean.caseSensitive) { testCandidate = candidate.toLowerCase(); }
|
||||||
|
else { testCandidate = candidate; }
|
||||||
|
// Get and compare edit distance.
|
||||||
|
val = getEditDistance(str, testCandidate, winningVal);
|
||||||
|
// If this value is smaller than our current winning value, OR if we have no winning val yet (i.e. the
|
||||||
|
// threshold option is set to null, meaning the caller wants a match back no matter how bad it is), then
|
||||||
|
// this is our new winner.
|
||||||
|
if (winningVal === null || val < winningVal) {
|
||||||
|
winningVal = val;
|
||||||
|
// Set the winner to either the value or its object, depending on the returnWinningObject option.
|
||||||
|
if (key && didYouMean.returnWinningObject) winner = list[i];
|
||||||
|
else winner = candidate;
|
||||||
|
// If we're returning the first match, return it now.
|
||||||
|
if (didYouMean.returnFirstMatch) return winner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a winner, return it.
|
||||||
|
return winner || didYouMean.nullResultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default options.
|
||||||
|
didYouMean.threshold = 0.4;
|
||||||
|
didYouMean.thresholdAbsolute = 20;
|
||||||
|
didYouMean.caseSensitive = false;
|
||||||
|
didYouMean.nullResultValue = null;
|
||||||
|
didYouMean.returnWinningObject = null;
|
||||||
|
didYouMean.returnFirstMatch = false;
|
||||||
|
|
||||||
|
// Expose.
|
||||||
|
// In node...
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = didYouMean;
|
||||||
|
}
|
||||||
|
// Otherwise...
|
||||||
|
else {
|
||||||
|
window.didYouMean = didYouMean;
|
||||||
|
}
|
||||||
|
|
||||||
|
var MAX_INT = Math.pow(2,32) - 1; // We could probably go higher than this, but for practical reasons let's not.
|
||||||
|
function getEditDistance(a, b, max) {
|
||||||
|
// Handle null or undefined max.
|
||||||
|
max = max || max === 0 ? max : MAX_INT;
|
||||||
|
|
||||||
|
var lena = a.length;
|
||||||
|
var lenb = b.length;
|
||||||
|
|
||||||
|
// Fast path - no A or B.
|
||||||
|
if (lena === 0) return Math.min(max + 1, lenb);
|
||||||
|
if (lenb === 0) return Math.min(max + 1, lena);
|
||||||
|
|
||||||
|
// Fast path - length diff larger than max.
|
||||||
|
if (Math.abs(lena - lenb) > max) return max + 1;
|
||||||
|
|
||||||
|
// Slow path.
|
||||||
|
var matrix = [],
|
||||||
|
i, j, colMin, minJ, maxJ;
|
||||||
|
|
||||||
|
// Set up the first row ([0, 1, 2, 3, etc]).
|
||||||
|
for (i = 0; i <= lenb; i++) { matrix[i] = [i]; }
|
||||||
|
|
||||||
|
// Set up the first column (same).
|
||||||
|
for (j = 0; j <= lena; j++) { matrix[0][j] = j; }
|
||||||
|
|
||||||
|
// Loop over the rest of the columns.
|
||||||
|
for (i = 1; i <= lenb; i++) {
|
||||||
|
colMin = MAX_INT;
|
||||||
|
minJ = 1;
|
||||||
|
if (i > max) minJ = i - max;
|
||||||
|
maxJ = lenb + 1;
|
||||||
|
if (maxJ > max + i) maxJ = max + i;
|
||||||
|
// Loop over the rest of the rows.
|
||||||
|
for (j = 1; j <= lena; j++) {
|
||||||
|
// If j is out of bounds, just put a large value in the slot.
|
||||||
|
if (j < minJ || j > maxJ) {
|
||||||
|
matrix[i][j] = max + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise do the normal Levenshtein thing.
|
||||||
|
else {
|
||||||
|
// If the characters are the same, there's no change in edit distance.
|
||||||
|
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||||
|
matrix[i][j] = matrix[i - 1][j - 1];
|
||||||
|
}
|
||||||
|
// Otherwise, see if we're substituting, inserting or deleting.
|
||||||
|
else {
|
||||||
|
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // Substitute
|
||||||
|
Math.min(matrix[i][j - 1] + 1, // Insert
|
||||||
|
matrix[i - 1][j] + 1)); // Delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either way, update colMin.
|
||||||
|
if (matrix[i][j] < colMin) colMin = matrix[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this column's minimum is greater than the allowed maximum, there's no point
|
||||||
|
// in going on with life.
|
||||||
|
if (colMin > max) return max + 1;
|
||||||
|
}
|
||||||
|
// If we made it this far without running into the max, then return the final matrix value.
|
||||||
|
return matrix[lenb][lena];
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
17
didYouMean-1.2.1.min.js
vendored
Normal file
17
didYouMean-1.2.1.min.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
didYouMean.js copyright (c) 2013-2014 Dave Porter.
|
||||||
|
|
||||||
|
[Available on GitHub](https://github.com/dcporter/didyoumean.js).
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License
|
||||||
|
[here](http://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
(function(){"use strict";function e(t,r,i){if(!t)return null;if(!e.caseSensitive){t=t.toLowerCase()}var s=e.threshold===null?null:e.threshold*t.length,o=e.thresholdAbsolute,u;if(s!==null&&o!==null)u=Math.min(s,o);else if(s!==null)u=s;else if(o!==null)u=o;else u=null;var a,f,l,c,h,p=r.length;for(h=0;h<p;h++){f=r[h];if(i){f=f[i]}if(!f){continue}if(!e.caseSensitive){l=f.toLowerCase()}else{l=f}c=n(t,l,u);if(u===null||c<u){u=c;if(i&&e.returnWinningObject)a=r[h];else a=f;if(e.returnFirstMatch)return a}}return a||e.nullResultValue}function n(e,n,r){r=r||r===0?r:t;var i=e.length;var s=n.length;if(i===0)return Math.min(r+1,s);if(s===0)return Math.min(r+1,i);if(Math.abs(i-s)>r)return r+1;var o=[],u,a,f,l,c;for(u=0;u<=s;u++){o[u]=[u]}for(a=0;a<=i;a++){o[0][a]=a}for(u=1;u<=s;u++){f=t;l=1;if(u>r)l=u-r;c=s+1;if(c>r+u)c=r+u;for(a=1;a<=i;a++){if(a<l||a>c){o[u][a]=r+1}else{if(n.charAt(u-1)===e.charAt(a-1)){o[u][a]=o[u-1][a-1]}else{o[u][a]=Math.min(o[u-1][a-1]+1,Math.min(o[u][a-1]+1,o[u-1][a]+1))}}if(o[u][a]<f)f=o[u][a]}if(f>r)return r+1}return o[s][i]}e.threshold=.4;e.thresholdAbsolute=20;e.caseSensitive=false;e.nullResultValue=null;e.returnWinningObject=null;e.returnFirstMatch=false;if(typeof module!=="undefined"&&module.exports){module.exports=e}else{window.didYouMean=e}var t=Math.pow(2,32)-1})();
|
||||||
16
dns.js
Normal file
16
dns.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
require('thenify-all').withCallback(
|
||||||
|
require('dns'),
|
||||||
|
exports, [
|
||||||
|
'lookup',
|
||||||
|
'resolve',
|
||||||
|
'resolve4',
|
||||||
|
'resolve6',
|
||||||
|
'resolveCname',
|
||||||
|
'resolveMx',
|
||||||
|
'resolveNs',
|
||||||
|
'resolveSrv',
|
||||||
|
'resolveTxt',
|
||||||
|
'reverse'
|
||||||
|
]
|
||||||
|
)
|
||||||
19
docker-compose.dev.yml
Normal file
19
docker-compose.dev.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
frontend:
|
||||||
|
image: node:20-alpine
|
||||||
|
working_dir: /app
|
||||||
|
container_name: contol-mve-frontend
|
||||||
|
ports:
|
||||||
|
- "5173:5173"
|
||||||
|
environment:
|
||||||
|
- VITE_API_BASE_URL=${VITE_API_URL}
|
||||||
|
volumes:
|
||||||
|
- ./:/app
|
||||||
|
- /app/node_modules
|
||||||
|
command: sh -c "npm install && npm run dev -- --host"
|
||||||
|
networks:
|
||||||
|
- control-mve-frontend-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
control-mve-frontend-network:
|
||||||
|
driver: bridge
|
||||||
14
docker-compose.prod.yaml
Normal file
14
docker-compose.prod.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
frontend:
|
||||||
|
build: .
|
||||||
|
ports: - "4000:80"
|
||||||
|
environment: - VITE_API_BASE_URL=${VITE_API_URL}
|
||||||
|
container_name: control-mve-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- control-mve-frontend-network
|
||||||
|
|
||||||
|
|
||||||
|
networks:
|
||||||
|
control-mve-frontend-network:
|
||||||
|
driver: bridge
|
||||||
44
docker-compose.yml
Normal file
44
docker-compose.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: control_mve_postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-control_mve}
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
api:
|
||||||
|
build: .
|
||||||
|
container_name: control_mve_api
|
||||||
|
command: sh -c "sh /code/migrate.sh && uvicorn app.main:app --host 0.0.0.0 --port 8009 --reload"
|
||||||
|
volumes:
|
||||||
|
- ./app:/code/app
|
||||||
|
- ./alembic:/code/alembic
|
||||||
|
- ./alembic.ini:/code/alembic.ini
|
||||||
|
ports:
|
||||||
|
- "${API_PORT:-8009}:8009"
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
BIN
enums.cpython-311.pyc
Normal file
BIN
enums.cpython-311.pyc
Normal file
Binary file not shown.
20
enums.py
Normal file
20
enums.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class EstadoCliente(str, enum.Enum):
|
||||||
|
ACTIVO = "activo"
|
||||||
|
INACTIVO = "inactivo"
|
||||||
|
SUSPENDIDO = "suspendido"
|
||||||
|
|
||||||
|
|
||||||
|
class TipoMovimiento(str, enum.Enum):
|
||||||
|
CONSUMO = "consumo"
|
||||||
|
RECARGA = "recarga"
|
||||||
|
AJUSTE = "ajuste"
|
||||||
|
|
||||||
|
|
||||||
|
class TipoLimite(str, enum.Enum):
|
||||||
|
ASIGNACION_INICIAL = "asignacion_inicial"
|
||||||
|
INCREMENTO = "incremento"
|
||||||
|
DECREMENTO = "decremento"
|
||||||
|
AJUSTE = "ajuste"
|
||||||
BIN
env.cpython-311.pyc
Normal file
BIN
env.cpython-311.pyc
Normal file
Binary file not shown.
89
env.py
Normal file
89
env.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy import pool
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
# Importar la configuración de la app
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
from app.database import Base
|
||||||
|
|
||||||
|
# Importar todos los modelos para que Alembic los detecte
|
||||||
|
from app.models import Cliente, SubCliente, MovimientoTimbre, LimiteTimbre, Usuario
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
if config.config_file_name is not None:
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
target_metadata = Base.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline() -> None:
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = settings.DATABASE_URL
|
||||||
|
context.configure(
|
||||||
|
url=url,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online() -> None:
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
configuration = config.get_section(config.config_ini_section)
|
||||||
|
configuration["sqlalchemy.url"] = settings.DATABASE_URL
|
||||||
|
connectable = engine_from_config(
|
||||||
|
configuration,
|
||||||
|
prefix="sqlalchemy.",
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(
|
||||||
|
connection=connection, target_metadata=target_metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
7
error.d.ts
vendored
Normal file
7
error.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
declare class BrowserslistError extends Error {
|
||||||
|
constructor(message: any)
|
||||||
|
name: 'BrowserslistError'
|
||||||
|
browserslist: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export = BrowserslistError
|
||||||
12
error.js
Normal file
12
error.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
function BrowserslistError(message) {
|
||||||
|
this.name = 'BrowserslistError'
|
||||||
|
this.message = message
|
||||||
|
this.browserslist = true
|
||||||
|
if (Error.captureStackTrace) {
|
||||||
|
Error.captureStackTrace(this, BrowserslistError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserslistError.prototype = Error.prototype
|
||||||
|
|
||||||
|
module.exports = BrowserslistError
|
||||||
16
esbuild
Normal file
16
esbuild
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../esbuild/bin/esbuild" "$@"
|
||||||
|
fi
|
||||||
17
esbuild.cmd
Normal file
17
esbuild.cmd
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %*
|
||||||
28
esbuild.ps1
Normal file
28
esbuild.ps1
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../esbuild/bin/esbuild" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../esbuild/bin/esbuild" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
14
example.js
Normal file
14
example.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* eslint-disable no-var */
|
||||||
|
|
||||||
|
var queue = require('./')(worker, 1)
|
||||||
|
|
||||||
|
queue.push(42, function (err, result) {
|
||||||
|
if (err) { throw err }
|
||||||
|
console.log('the result is', result)
|
||||||
|
})
|
||||||
|
|
||||||
|
function worker (arg, cb) {
|
||||||
|
cb(null, 42 * 2)
|
||||||
|
}
|
||||||
11
example.mjs
Normal file
11
example.mjs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { promise as queueAsPromised } from './queue.js'
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
const queue = queueAsPromised(worker, 1)
|
||||||
|
|
||||||
|
console.log('the result is', await queue.push(42))
|
||||||
|
|
||||||
|
async function worker (arg) {
|
||||||
|
return 42 * 2
|
||||||
|
}
|
||||||
6
exclude
Normal file
6
exclude
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# git ls-files --others --exclude-from=.git/info/exclude
|
||||||
|
# Lines that start with '#' are comments.
|
||||||
|
# For a project mostly in C, the following would be a good set of
|
||||||
|
# exclude patterns (uncomment them if you want to use them):
|
||||||
|
# *.[oa]
|
||||||
|
# *~
|
||||||
3
extensions.json
Normal file
3
extensions.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["svelte.svelte-vscode"]
|
||||||
|
}
|
||||||
79
fraction.d.mts
Normal file
79
fraction.d.mts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Interface representing a fraction with numerator and denominator.
|
||||||
|
*/
|
||||||
|
export interface NumeratorDenominator {
|
||||||
|
n: number | bigint;
|
||||||
|
d: number | bigint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for handling multiple types of input for Fraction operations.
|
||||||
|
*/
|
||||||
|
export type FractionInput =
|
||||||
|
| Fraction
|
||||||
|
| number
|
||||||
|
| bigint
|
||||||
|
| string
|
||||||
|
| [number | bigint | string, number | bigint | string]
|
||||||
|
| NumeratorDenominator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function signature for Fraction operations like add, sub, mul, etc.
|
||||||
|
*/
|
||||||
|
export type FractionParam = {
|
||||||
|
(numerator: number | bigint, denominator: number | bigint): Fraction;
|
||||||
|
(num: FractionInput): Fraction;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fraction class representing a rational number with numerator and denominator.
|
||||||
|
*/
|
||||||
|
declare class Fraction {
|
||||||
|
constructor();
|
||||||
|
constructor(num: FractionInput);
|
||||||
|
constructor(numerator: number | bigint, denominator: number | bigint);
|
||||||
|
|
||||||
|
s: bigint;
|
||||||
|
n: bigint;
|
||||||
|
d: bigint;
|
||||||
|
|
||||||
|
abs(): Fraction;
|
||||||
|
neg(): Fraction;
|
||||||
|
|
||||||
|
add: FractionParam;
|
||||||
|
sub: FractionParam;
|
||||||
|
mul: FractionParam;
|
||||||
|
div: FractionParam;
|
||||||
|
pow: FractionParam;
|
||||||
|
log: FractionParam;
|
||||||
|
gcd: FractionParam;
|
||||||
|
lcm: FractionParam;
|
||||||
|
|
||||||
|
mod(): Fraction;
|
||||||
|
mod(num: FractionInput): Fraction;
|
||||||
|
|
||||||
|
ceil(places?: number): Fraction;
|
||||||
|
floor(places?: number): Fraction;
|
||||||
|
round(places?: number): Fraction;
|
||||||
|
roundTo: FractionParam;
|
||||||
|
|
||||||
|
inverse(): Fraction;
|
||||||
|
simplify(eps?: number): Fraction;
|
||||||
|
|
||||||
|
equals(num: FractionInput): boolean;
|
||||||
|
lt(num: FractionInput): boolean;
|
||||||
|
lte(num: FractionInput): boolean;
|
||||||
|
gt(num: FractionInput): boolean;
|
||||||
|
gte(num: FractionInput): boolean;
|
||||||
|
compare(num: FractionInput): number;
|
||||||
|
divisible(num: FractionInput): boolean;
|
||||||
|
|
||||||
|
valueOf(): number;
|
||||||
|
toString(decimalPlaces?: number): string;
|
||||||
|
toLatex(showMixed?: boolean): string;
|
||||||
|
toFraction(showMixed?: boolean): string;
|
||||||
|
toContinued(): bigint[];
|
||||||
|
clone(): Fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Fraction as default, Fraction };
|
||||||
79
fraction.d.ts
vendored
Normal file
79
fraction.d.ts
vendored
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
declare class Fraction {
|
||||||
|
constructor();
|
||||||
|
constructor(num: Fraction.FractionInput);
|
||||||
|
constructor(numerator: number | bigint, denominator: number | bigint);
|
||||||
|
|
||||||
|
s: bigint;
|
||||||
|
n: bigint;
|
||||||
|
d: bigint;
|
||||||
|
|
||||||
|
abs(): Fraction;
|
||||||
|
neg(): Fraction;
|
||||||
|
|
||||||
|
add: Fraction.FractionParam;
|
||||||
|
sub: Fraction.FractionParam;
|
||||||
|
mul: Fraction.FractionParam;
|
||||||
|
div: Fraction.FractionParam;
|
||||||
|
pow: Fraction.FractionParam;
|
||||||
|
log: Fraction.FractionParam;
|
||||||
|
gcd: Fraction.FractionParam;
|
||||||
|
lcm: Fraction.FractionParam;
|
||||||
|
|
||||||
|
mod(): Fraction;
|
||||||
|
mod(num: Fraction.FractionInput): Fraction;
|
||||||
|
|
||||||
|
ceil(places?: number): Fraction;
|
||||||
|
floor(places?: number): Fraction;
|
||||||
|
round(places?: number): Fraction;
|
||||||
|
roundTo: Fraction.FractionParam;
|
||||||
|
|
||||||
|
inverse(): Fraction;
|
||||||
|
simplify(eps?: number): Fraction;
|
||||||
|
|
||||||
|
equals(num: Fraction.FractionInput): boolean;
|
||||||
|
lt(num: Fraction.FractionInput): boolean;
|
||||||
|
lte(num: Fraction.FractionInput): boolean;
|
||||||
|
gt(num: Fraction.FractionInput): boolean;
|
||||||
|
gte(num: Fraction.FractionInput): boolean;
|
||||||
|
compare(num: Fraction.FractionInput): number;
|
||||||
|
divisible(num: Fraction.FractionInput): boolean;
|
||||||
|
|
||||||
|
valueOf(): number;
|
||||||
|
toString(decimalPlaces?: number): string;
|
||||||
|
toLatex(showMixed?: boolean): string;
|
||||||
|
toFraction(showMixed?: boolean): string;
|
||||||
|
toContinued(): bigint[];
|
||||||
|
clone(): Fraction;
|
||||||
|
|
||||||
|
static default: typeof Fraction;
|
||||||
|
static Fraction: typeof Fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace Fraction {
|
||||||
|
interface NumeratorDenominator { n: number | bigint; d: number | bigint; }
|
||||||
|
type FractionInput =
|
||||||
|
| Fraction
|
||||||
|
| number
|
||||||
|
| bigint
|
||||||
|
| string
|
||||||
|
| [number | bigint | string, number | bigint | string]
|
||||||
|
| NumeratorDenominator;
|
||||||
|
|
||||||
|
type FractionParam = {
|
||||||
|
(numerator: number | bigint, denominator: number | bigint): Fraction;
|
||||||
|
(num: FractionInput): Fraction;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export matches CJS runtime:
|
||||||
|
* module.exports = Fraction;
|
||||||
|
* module.exports.default = Fraction;
|
||||||
|
* module.exports.Fraction = Fraction;
|
||||||
|
*/
|
||||||
|
declare const FractionExport: typeof Fraction & {
|
||||||
|
default: typeof Fraction;
|
||||||
|
Fraction: typeof Fraction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export = FractionExport;
|
||||||
62
fs.js
Normal file
62
fs.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
var Promise = require('any-promise')
|
||||||
|
var fs
|
||||||
|
try {
|
||||||
|
fs = require('graceful-fs')
|
||||||
|
} catch(err) {
|
||||||
|
fs = require('fs')
|
||||||
|
}
|
||||||
|
|
||||||
|
var api = [
|
||||||
|
'appendFile',
|
||||||
|
'chmod',
|
||||||
|
'chown',
|
||||||
|
'close',
|
||||||
|
'fchmod',
|
||||||
|
'fchown',
|
||||||
|
'fdatasync',
|
||||||
|
'fstat',
|
||||||
|
'fsync',
|
||||||
|
'ftruncate',
|
||||||
|
'futimes',
|
||||||
|
'lchown',
|
||||||
|
'link',
|
||||||
|
'lstat',
|
||||||
|
'mkdir',
|
||||||
|
'open',
|
||||||
|
'read',
|
||||||
|
'readFile',
|
||||||
|
'readdir',
|
||||||
|
'readlink',
|
||||||
|
'realpath',
|
||||||
|
'rename',
|
||||||
|
'rmdir',
|
||||||
|
'stat',
|
||||||
|
'symlink',
|
||||||
|
'truncate',
|
||||||
|
'unlink',
|
||||||
|
'utimes',
|
||||||
|
'write',
|
||||||
|
'writeFile'
|
||||||
|
]
|
||||||
|
|
||||||
|
typeof fs.access === 'function' && api.push('access')
|
||||||
|
typeof fs.copyFile === 'function' && api.push('copyFile')
|
||||||
|
typeof fs.mkdtemp === 'function' && api.push('mkdtemp')
|
||||||
|
|
||||||
|
require('thenify-all').withCallback(fs, exports, api)
|
||||||
|
|
||||||
|
exports.exists = function (filename, callback) {
|
||||||
|
// callback
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
return fs.stat(filename, function (err) {
|
||||||
|
callback(null, !err);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// or promise
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
fs.stat(filename, function (err) {
|
||||||
|
resolve(!err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
174
fsmonitor-watchman.sample
Normal file
174
fsmonitor-watchman.sample
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use IPC::Open2;
|
||||||
|
|
||||||
|
# An example hook script to integrate Watchman
|
||||||
|
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||||
|
# new and modified files.
|
||||||
|
#
|
||||||
|
# The hook is passed a version (currently 2) and last update token
|
||||||
|
# formatted as a string and outputs to stdout a new update token and
|
||||||
|
# all files that have been modified since the update token. Paths must
|
||||||
|
# be relative to the root of the working tree and separated by a single NUL.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "query-watchman" and set
|
||||||
|
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||||
|
#
|
||||||
|
my ($version, $last_update_token) = @ARGV;
|
||||||
|
|
||||||
|
# Uncomment for debugging
|
||||||
|
# print STDERR "$0 $version $last_update_token\n";
|
||||||
|
|
||||||
|
# Check the hook interface version
|
||||||
|
if ($version ne 2) {
|
||||||
|
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $git_work_tree = get_working_dir();
|
||||||
|
|
||||||
|
my $retry = 1;
|
||||||
|
|
||||||
|
my $json_pkg;
|
||||||
|
eval {
|
||||||
|
require JSON::XS;
|
||||||
|
$json_pkg = "JSON::XS";
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
require JSON::PP;
|
||||||
|
$json_pkg = "JSON::PP";
|
||||||
|
};
|
||||||
|
|
||||||
|
launch_watchman();
|
||||||
|
|
||||||
|
sub launch_watchman {
|
||||||
|
my $o = watchman_query();
|
||||||
|
if (is_work_tree_watched($o)) {
|
||||||
|
output_result($o->{clock}, @{$o->{files}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub output_result {
|
||||||
|
my ($clockid, @files) = @_;
|
||||||
|
|
||||||
|
# Uncomment for debugging watchman output
|
||||||
|
# open (my $fh, ">", ".git/watchman-output.out");
|
||||||
|
# binmode $fh, ":utf8";
|
||||||
|
# print $fh "$clockid\n@files\n";
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
binmode STDOUT, ":utf8";
|
||||||
|
print $clockid;
|
||||||
|
print "\0";
|
||||||
|
local $, = "\0";
|
||||||
|
print @files;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub watchman_clock {
|
||||||
|
my $response = qx/watchman clock "$git_work_tree"/;
|
||||||
|
die "Failed to get clock id on '$git_work_tree'.\n" .
|
||||||
|
"Falling back to scanning...\n" if $? != 0;
|
||||||
|
|
||||||
|
return $json_pkg->new->utf8->decode($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub watchman_query {
|
||||||
|
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
|
||||||
|
or die "open2() failed: $!\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
|
||||||
|
# In the query expression below we're asking for names of files that
|
||||||
|
# changed since $last_update_token but not from the .git folder.
|
||||||
|
#
|
||||||
|
# To accomplish this, we're using the "since" generator to use the
|
||||||
|
# recency index to select candidate nodes and "fields" to limit the
|
||||||
|
# output to file names only. Then we're using the "expression" term to
|
||||||
|
# further constrain the results.
|
||||||
|
my $last_update_line = "";
|
||||||
|
if (substr($last_update_token, 0, 1) eq "c") {
|
||||||
|
$last_update_token = "\"$last_update_token\"";
|
||||||
|
$last_update_line = qq[\n"since": $last_update_token,];
|
||||||
|
}
|
||||||
|
my $query = <<" END";
|
||||||
|
["query", "$git_work_tree", {$last_update_line
|
||||||
|
"fields": ["name"],
|
||||||
|
"expression": ["not", ["dirname", ".git"]]
|
||||||
|
}]
|
||||||
|
END
|
||||||
|
|
||||||
|
# Uncomment for debugging the watchman query
|
||||||
|
# open (my $fh, ">", ".git/watchman-query.json");
|
||||||
|
# print $fh $query;
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
print CHLD_IN $query;
|
||||||
|
close CHLD_IN;
|
||||||
|
my $response = do {local $/; <CHLD_OUT>};
|
||||||
|
|
||||||
|
# Uncomment for debugging the watch response
|
||||||
|
# open ($fh, ">", ".git/watchman-response.json");
|
||||||
|
# print $fh $response;
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
die "Watchman: command returned no output.\n" .
|
||||||
|
"Falling back to scanning...\n" if $response eq "";
|
||||||
|
die "Watchman: command returned invalid output: $response\n" .
|
||||||
|
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||||
|
|
||||||
|
return $json_pkg->new->utf8->decode($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub is_work_tree_watched {
|
||||||
|
my ($output) = @_;
|
||||||
|
my $error = $output->{error};
|
||||||
|
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||||
|
$retry--;
|
||||||
|
my $response = qx/watchman watch "$git_work_tree"/;
|
||||||
|
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||||
|
"Falling back to scanning...\n" if $? != 0;
|
||||||
|
$output = $json_pkg->new->utf8->decode($response);
|
||||||
|
$error = $output->{error};
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
# Uncomment for debugging watchman output
|
||||||
|
# open (my $fh, ">", ".git/watchman-output.out");
|
||||||
|
# close $fh;
|
||||||
|
|
||||||
|
# Watchman will always return all files on the first query so
|
||||||
|
# return the fast "everything is dirty" flag to git and do the
|
||||||
|
# Watchman query just to get it over with now so we won't pay
|
||||||
|
# the cost in git to look up each individual file.
|
||||||
|
my $o = watchman_clock();
|
||||||
|
$error = $output->{error};
|
||||||
|
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
output_result($o->{clock}, ("/"));
|
||||||
|
$last_update_token = $o->{clock};
|
||||||
|
|
||||||
|
eval { launch_watchman() };
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
die "Watchman: $error.\n" .
|
||||||
|
"Falling back to scanning...\n" if $error;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_working_dir {
|
||||||
|
my $working_dir;
|
||||||
|
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
|
||||||
|
$working_dir = Win32::GetCwd();
|
||||||
|
$working_dir =~ tr/\\/\//;
|
||||||
|
} else {
|
||||||
|
require Cwd;
|
||||||
|
$working_dir = Cwd::cwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $working_dir;
|
||||||
|
}
|
||||||
2632
full-chromium-versions.js
Normal file
2632
full-chromium-versions.js
Normal file
File diff suppressed because it is too large
Load Diff
1
full-chromium-versions.json
Normal file
1
full-chromium-versions.json
Normal file
File diff suppressed because one or more lines are too long
1684
full-versions.js
Normal file
1684
full-versions.js
Normal file
File diff suppressed because it is too large
Load Diff
1
full-versions.json
Normal file
1
full-versions.json
Normal file
File diff suppressed because one or more lines are too long
90
handler.d.ts
vendored
Normal file
90
handler.d.ts
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import type { WatchEventType, Stats, FSWatcher as NativeFsWatcher } from 'fs';
|
||||||
|
import type { FSWatcher, WatchHelper, Throttler } from './index.js';
|
||||||
|
import type { EntryInfo } from 'readdirp';
|
||||||
|
export type Path = string;
|
||||||
|
export declare const STR_DATA = "data";
|
||||||
|
export declare const STR_END = "end";
|
||||||
|
export declare const STR_CLOSE = "close";
|
||||||
|
export declare const EMPTY_FN: () => void;
|
||||||
|
export declare const IDENTITY_FN: (val: unknown) => unknown;
|
||||||
|
export declare const isWindows: boolean;
|
||||||
|
export declare const isMacos: boolean;
|
||||||
|
export declare const isLinux: boolean;
|
||||||
|
export declare const isFreeBSD: boolean;
|
||||||
|
export declare const isIBMi: boolean;
|
||||||
|
export declare const EVENTS: {
|
||||||
|
readonly ALL: "all";
|
||||||
|
readonly READY: "ready";
|
||||||
|
readonly ADD: "add";
|
||||||
|
readonly CHANGE: "change";
|
||||||
|
readonly ADD_DIR: "addDir";
|
||||||
|
readonly UNLINK: "unlink";
|
||||||
|
readonly UNLINK_DIR: "unlinkDir";
|
||||||
|
readonly RAW: "raw";
|
||||||
|
readonly ERROR: "error";
|
||||||
|
};
|
||||||
|
export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
|
||||||
|
export type FsWatchContainer = {
|
||||||
|
listeners: (path: string) => void | Set<any>;
|
||||||
|
errHandlers: (err: unknown) => void | Set<any>;
|
||||||
|
rawEmitters: (ev: WatchEventType, path: string, opts: unknown) => void | Set<any>;
|
||||||
|
watcher: NativeFsWatcher;
|
||||||
|
watcherUnusable?: boolean;
|
||||||
|
};
|
||||||
|
export interface WatchHandlers {
|
||||||
|
listener: (path: string) => void;
|
||||||
|
errHandler: (err: unknown) => void;
|
||||||
|
rawEmitter: (ev: WatchEventType, path: string, opts: unknown) => void;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @mixin
|
||||||
|
*/
|
||||||
|
export declare class NodeFsHandler {
|
||||||
|
fsw: FSWatcher;
|
||||||
|
_boundHandleError: (error: unknown) => void;
|
||||||
|
constructor(fsW: FSWatcher);
|
||||||
|
/**
|
||||||
|
* Watch file for changes with fs_watchFile or fs_watch.
|
||||||
|
* @param path to file or dir
|
||||||
|
* @param listener on fs change
|
||||||
|
* @returns closer for the watcher instance
|
||||||
|
*/
|
||||||
|
_watchWithNodeFs(path: string, listener: (path: string, newStats?: any) => void | Promise<void>): (() => void) | undefined;
|
||||||
|
/**
|
||||||
|
* Watch a file and emit add event if warranted.
|
||||||
|
* @returns closer for the watcher instance
|
||||||
|
*/
|
||||||
|
_handleFile(file: Path, stats: Stats, initialAdd: boolean): (() => void) | undefined;
|
||||||
|
/**
|
||||||
|
* Handle symlinks encountered while reading a dir.
|
||||||
|
* @param entry returned by readdirp
|
||||||
|
* @param directory path of dir being read
|
||||||
|
* @param path of this item
|
||||||
|
* @param item basename of this item
|
||||||
|
* @returns true if no more processing is needed for this entry.
|
||||||
|
*/
|
||||||
|
_handleSymlink(entry: EntryInfo, directory: string, path: Path, item: string): Promise<boolean | undefined>;
|
||||||
|
_handleRead(directory: string, initialAdd: boolean, wh: WatchHelper, target: Path, dir: Path, depth: number, throttler: Throttler): Promise<unknown> | undefined;
|
||||||
|
/**
|
||||||
|
* Read directory to add / remove files from `@watched` list and re-read it on change.
|
||||||
|
* @param dir fs path
|
||||||
|
* @param stats
|
||||||
|
* @param initialAdd
|
||||||
|
* @param depth relative to user-supplied path
|
||||||
|
* @param target child path targeted for watch
|
||||||
|
* @param wh Common watch helpers for this path
|
||||||
|
* @param realpath
|
||||||
|
* @returns closer for the watcher instance.
|
||||||
|
*/
|
||||||
|
_handleDir(dir: string, stats: Stats, initialAdd: boolean, depth: number, target: string, wh: WatchHelper, realpath: string): Promise<(() => void) | undefined>;
|
||||||
|
/**
|
||||||
|
* Handle added file, directory, or glob pattern.
|
||||||
|
* Delegates call to _handleFile / _handleDir after checks.
|
||||||
|
* @param path to file or ir
|
||||||
|
* @param initialAdd was the file added at watch instantiation?
|
||||||
|
* @param priorWh depth relative to user-supplied path
|
||||||
|
* @param depth Child path actually targeted for watch
|
||||||
|
* @param target Child path actually targeted for watch
|
||||||
|
*/
|
||||||
|
_addToNodeFs(path: string, initialAdd: boolean, priorWh: WatchHelper | undefined, depth: number, target?: string): Promise<string | false | undefined>;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user