diff --git a/Server/data.json b/Server/data.json new file mode 100644 index 0000000..91848f4 --- /dev/null +++ b/Server/data.json @@ -0,0 +1,1642 @@ +{ + "users": [ + { + "login": "Admin", + "password": "Admin", + "email": "admin@mail.ru", + "role": "ADMIN", + "id": 1 + }, + { + "id": 2, + "login": "User1", + "password": "123", + "email": "user1@mail.ru", + "role": "USER" + } + ], + "authors": [ + { + "name": "Дж. Оруэлл", + "id": 1 + }, + { + "name": "М.А. Булгаков", + "id": 2 + }, + { + "name": "Р. Брэдбери", + "id": 3 + } + ], + "books": [ + { + "title": "книга", + "description": "тест", + "content": "книга о приключениях", + "cover": [ + -1, + -40, + -1, + -32, + 0, + 16, + 74, + 70, + 73, + 70, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 1, + 0, + 0, + -1, + -30, + 2, + 40, + 73, + 67, + 67, + 95, + 80, + 82, + 79, + 70, + 73, + 76, + 69, + 0, + 1, + 1, + 0, + 0, + 2, + 24, + 0, + 0, + 0, + 0, + 2, + 16, + 0, + 0, + 109, + 110, + 116, + 114, + 82, + 71, + 66, + 32, + 88, + 89, + 90, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 97, + 99, + 115, + 112, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + -10, + -42, + 0, + 1, + 0, + 0, + 0, + 0, + -45, + 45, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 9, + 100, + 101, + 115, + 99, + 0, + 0, + 0, + -16, + 0, + 0, + 0, + 116, + 114, + 88, + 89, + 90, + 0, + 0, + 1, + 100, + 0, + 0, + 0, + 20, + 103, + 88, + 89, + 90, + 0, + 0, + 1, + 120, + 0, + 0, + 0, + 20, + 98, + 88, + 89, + 90, + 0, + 0, + 1, + -116, + 0, + 0, + 0, + 20, + 114, + 84, + 82, + 67, + 0, + 0, + 1, + -96, + 0, + 0, + 0, + 40, + 103, + 84, + 82, + 67, + 0, + 0, + 1, + -96, + 0, + 0, + 0, + 40, + 98, + 84, + 82, + 67, + 0, + 0, + 1, + -96, + 0, + 0, + 0, + 40, + 119, + 116, + 112, + 116, + 0, + 0, + 1, + -56, + 0, + 0, + 0, + 20, + 99, + 112, + 114, + 116, + 0, + 0, + 1, + -36, + 0, + 0, + 0, + 60, + 109, + 108, + 117, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 12, + 101, + 110, + 85, + 83, + 0, + 0, + 0, + 88, + 0, + 0, + 0, + 28, + 0, + 115, + 0, + 82, + 0, + 71, + 0, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 88, + 89, + 90, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 111, + -94, + 0, + 0, + 56, + -11, + 0, + 0, + 3, + -112, + 88, + 89, + 90, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 98, + -103, + 0, + 0, + -73, + -123, + 0, + 0, + 24, + -38, + 88, + 89, + 90, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + 36, + -96, + 0, + 0, + 15, + -124, + 0, + 0, + -74, + -49, + 112, + 97, + 114, + 97, + 0, + 0, + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 2, + 102, + 102, + 0, + 0, + -14, + -89, + 0, + 0, + 13, + 89, + 0, + 0, + 19, + -48, + 0, + 0, + 10, + 91, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 88, + 89, + 90, + 32, + 0, + 0, + 0, + 0, + 0, + 0, + -10, + -42, + 0, + 1, + 0, + 0, + 0, + 0, + -45, + 45, + 109, + 108, + 117, + 99, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 12, + 101, + 110, + 85, + 83, + 0, + 0, + 0, + 32, + 0, + 0, + 0, + 28, + 0, + 71, + 0, + 111, + 0, + 111, + 0, + 103, + 0, + 108, + 0, + 101, + 0, + 32, + 0, + 73, + 0, + 110, + 0, + 99, + 0, + 46, + 0, + 32, + 0, + 50, + 0, + 48, + 0, + 49, + 0, + 54, + -1, + -37, + 0, + 67, + 0, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -37, + 0, + 67, + 1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -64, + 0, + 17, + 8, + 0, + -56, + 0, + -56, + 3, + 1, + 34, + 0, + 2, + 17, + 1, + 3, + 17, + 1, + -1, + -60, + 0, + 23, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 2, + 3, + -1, + -60, + 0, + 37, + 16, + 1, + 1, + 0, + 2, + 1, + 4, + 2, + 1, + 5, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 17, + 49, + 65, + 2, + 18, + 33, + 81, + 97, + -127, + 113, + 34, + -79, + -63, + -47, + -16, + 66, + -1, + -60, + 0, + 21, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + -1, + -60, + 0, + 22, + 17, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 17, + -1, + -38, + 0, + 12, + 3, + 1, + 0, + 2, + 17, + 3, + 17, + 0, + 63, + 0, + -24, + 51, + -109, + 32, + -48, + -58, + 87, + 32, + -73, + 70, + 89, + -86, + -118, + 90, + -78, + -80, + -48, + 46, + 73, + 88, + -85, + 52, + 13, + 102, + 38, + 127, + 116, + -92, + -2, + -63, + -84, + -60, + -54, + 84, + -97, + -56, + 54, + -106, + -116, + -48, + 111, + 37, + -87, + 52, + -108, + 22, + 85, + -53, + 19, + 127, + 77, + 1, + 46, + 107, + 78, + 122, + -82, + -118, + -128, + 0, + 0, + 14, + 96, + 0, + 8, + 13, + 69, + 102, + 92, + 29, + -39, + -10, + -118, + 114, + -45, + 25, + -59, + 107, + -70, + 2, + 86, + -90, + -104, + -66, + -38, + -99, + 80, + 10, + -109, + 107, + 108, + -87, + 44, + -106, + -126, + -44, + -97, + -54, + -37, + 61, + -90, + 113, + -128, + 105, + -102, + -67, + -47, + 51, + -112, + 106, + 104, + -84, + -50, + -81, + 11, + -35, + 62, + 65, + 38, + -29, + 76, + 95, + -27, + 123, + -66, + 0, + -83, + -51, + 49, + -100, + -16, + -36, + -48, + 40, + 10, + -128, + 0, + -26, + 0, + 8, + -88, + 9, + 72, + 82, + 34, + -89, + 43, + -27, + 46, + -42, + 40, + -73, + 56, + 69, + -70, + 101, + 6, + -68, + -78, + -45, + 60, + -125, + 81, + 45, + 89, + -108, + -96, + 121, + 95, + 41, + 20, + 25, + -115, + 121, + 101, + -87, + -12, + 9, + 72, + -73, + -124, + 5, + 116, + -102, + -116, + 55, + 52, + 21, + 64, + 84, + 0, + 7, + 48, + 0, + 69, + 64, + 74, + 66, + -112, + 82, + -20, + 40, + -126, + -35, + 50, + -41, + 12, + -125, + 76, + -75, + 25, + 5, + -123, + -39, + 11, + -80, + 23, + -124, + 107, + -128, + 101, + 98, + 44, + 2, + -1, + 0, + 104, + -73, + 127, + 73, + 1, + -89, + 71, + 55, + 64, + -96, + 10, + -128, + 0, + -26, + 0, + 8, + 0, + -108, + 90, + -126, + -91, + 22, + -128, + 35, + 76, + -96, + -44, + 103, + -106, + -109, + -112, + 11, + -75, + 75, + -80, + 23, + -124, + 94, + 1, + 22, + 34, + -64, + 75, + -80, + -69, + 88, + 11, + 56, + 116, + 98, + 112, + -38, + -108, + 0, + 64, + 0, + 115, + 1, + 0, + 0, + 75, + -92, + 84, + 85, + 41, + 22, + -23, + 16, + 94, + 25, + -115, + 32, + 53, + -31, + -98, + 85, + 57, + 6, + -68, + 51, + 118, + -90, + -24, + 30, + 11, + -96, + -70, + 4, + 105, + -106, + -96, + 51, + -51, + 106, + 51, + -51, + 106, + 2, + -51, + -74, + -52, + -37, + 74, + -108, + 0, + 0, + 1, + -52, + 81, + 4, + 0, + 6, + 90, + 103, + 22, + -86, + -83, + -46, + 70, + -69, + 111, + -77, + -74, + 124, + -96, + -119, + -53, + -90, + 39, + -91, + 6, + 50, + -49, + 46, + -52, + -34, + -103, + 126, + 63, + 10, + 50, + -100, + -105, + 51, + 105, + -117, + 80, + 82, + -23, + -87, + -46, + -72, + -128, + -25, + 26, + 94, + -34, + -109, + -76, + 24, + -25, + -19, + -88, + 118, + 95, + 103, + 109, + 6, + -93, + 76, + -12, + -76, + -88, + 0, + 0, + 0, + -50, + 12, + 52, + 3, + 24, + 70, + -85, + -97, + 40, + 52, + -44, + 102, + 54, + 40, + -128, + 34, + -128, + 10, + 2, + -116, + -11, + 113, + -7, + 102, + -75, + -43, + -57, + -27, + -117, + 124, + -94, + -76, + 103, + 9, + 60, + -83, + -48, + 44, + -71, + 51, + -122, + 101, + -61, + 89, + -15, + -111, + 23, + 41, + -106, + 101, + 104, + 26, + 6, + 122, + -108, + 104, + 0, + 0, + 1, + 21, + 1, + -102, + -52, + 94, + -92, + -120, + -83, + 70, + -110, + 40, + 34, + -94, + -120, + 42, + 40, + 0, + 40, + -51, + -29, + -17, + -10, + 98, + -19, + -69, + -57, + -37, + 21, + 21, + 127, + 6, + -9, + 80, + 17, + -84, + 100, + -61, + 64, + 51, + -124, + -8, + 106, + -60, + -80, + 26, + -102, + 46, + -110, + 105, + 106, + -124, + -46, + -77, + -46, + -48, + 0, + 0, + -126, + 3, + 29, + 74, + -100, + -86, + 43, + 112, + 1, + 0, + 0, + 80, + 80, + 0, + 25, + -68, + 125, + -80, + -35, + -36, + -5, + 97, + 20, + -117, + -45, + -77, + 23, + -46, + -55, + 65, + -95, + 60, + -98, + 65, + 68, + -51, + -12, + 100, + 9, + -52, + 105, + -103, + -74, + -124, + 102, + 110, + -76, + -57, + -3, + 54, + -96, + 0, + 34, + 85, + 103, + -85, + 72, + 49, + 27, + -116, + -58, + -123, + 104, + -53, + 0, + 53, + -109, + 44, + -26, + 38, + 127, + 32, + -34, + 76, + -79, + -35, + -16, + 119, + 124, + 3, + 121, + 92, + -71, + -25, + -31, + 114, + 13, + 93, + -60, + 37, + -52, + -4, + 0, + -90, + 89, + 51, + 61, + -125, + 89, + 86, + 64, + 104, + 100, + 6, + -122, + 87, + 32, + -105, + 113, + -74, + 122, + -76, + -77, + 81, + 81, + 64, + 1, + -117, + 47, + -90, + -64, + 115, + -57, + 87, + -93, + 29, + 78, + -128, + 57, + -10, + -33, + -11, + 59, + 47, + -61, + -96, + 14, + 125, + -97, + 43, + -39, + -14, + -40, + 12, + 118, + 124, + -99, + -65, + 45, + -128, + -25, + -37, + 78, + -38, + -24, + 3, + 18, + 89, + -100, + -89, + 109, + 110, + -110, + -96, + -57, + 109, + 59, + 107, + -96, + -93, + -97, + 109, + -12, + -99, + -73, + -45, + -88, + 14, + 125, + -73, + -3, + 76, + 117, + 58, + 0, + -25, + -114, + -93, + 29, + 94, + -99, + 0, + -41, + 63, + -43, + -23, + -71, + -91, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4, + -109, + -56, + 2, + -128, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -1, + -39 + ], + "authorId": 1, + "userId": 2, + "id": 1 + } + ] +} \ No newline at end of file diff --git a/Server/package-lock.json b/Server/package-lock.json new file mode 100644 index 0000000..0203f7f --- /dev/null +++ b/Server/package-lock.json @@ -0,0 +1,1335 @@ +{ + "name": "fake-db", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fake-db", + "version": "1.0.0", + "devDependencies": { + "json-server": "0.17.4" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect-pause": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-pause/-/connect-pause-0.1.1.tgz", + "integrity": "sha512-a1gSWQBQD73krFXdUEYJom2RTFrWUL3YvXDCRkyv//GVXc79cdW9MngtRuN9ih4FDKBtfJAJId+BbDuX+1rh2w==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "dev": true, + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-urlrewrite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/express-urlrewrite/-/express-urlrewrite-1.4.0.tgz", + "integrity": "sha512-PI5h8JuzoweS26vFizwQl6UTF25CAHSggNv0J25Dn/IKZscJHWZzPrI5z2Y2jgOzIaw2qh8l6+/jUcig23Z2SA==", + "dev": true, + "dependencies": { + "debug": "*", + "path-to-regexp": "^1.0.3" + } + }, + "node_modules/express-urlrewrite/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, + "node_modules/json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", + "dev": true, + "dependencies": { + "jju": "^1.1.0" + } + }, + "node_modules/json-server": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/json-server/-/json-server-0.17.4.tgz", + "integrity": "sha512-bGBb0WtFuAKbgI7JV3A864irWnMZSvBYRJbohaOuatHwKSRFUfqtQlrYMrB6WbalXy/cJabyjlb7JkHli6dYjQ==", + "dev": true, + "dependencies": { + "body-parser": "^1.19.0", + "chalk": "^4.1.2", + "compression": "^1.7.4", + "connect-pause": "^0.1.1", + "cors": "^2.8.5", + "errorhandler": "^1.5.1", + "express": "^4.17.1", + "express-urlrewrite": "^1.4.0", + "json-parse-helpfulerror": "^1.0.3", + "lodash": "^4.17.21", + "lodash-id": "^0.14.1", + "lowdb": "^1.0.0", + "method-override": "^3.0.0", + "morgan": "^1.10.0", + "nanoid": "^3.1.23", + "please-upgrade-node": "^3.2.0", + "pluralize": "^8.0.0", + "server-destroy": "^1.0.1", + "yargs": "^17.0.1" + }, + "bin": { + "json-server": "lib/cli/bin.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-id": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/lodash-id/-/lodash-id-0.14.1.tgz", + "integrity": "sha512-ikQPBTiq/d5m6dfKQlFdIXFzvThPi2Be9/AHxktOnDSfSxE1j9ICbBT5Elk1ke7HSTgM38LHTpmJovo9/klnLg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/lowdb": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", + "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.3", + "is-promise": "^2.1.0", + "lodash": "4", + "pify": "^3.0.0", + "steno": "^0.4.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/method-override": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", + "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", + "dev": true, + "dependencies": { + "debug": "3.1.0", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/method-override/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/steno": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz", + "integrity": "sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.3" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/Server/package.json b/Server/package.json new file mode 100644 index 0000000..0fb3100 --- /dev/null +++ b/Server/package.json @@ -0,0 +1,12 @@ +{ + "name": "fake-db", + "version": "1.0.0", + "scripts": { + "start": "json-server --watch data.json --host 0.0.0.0 -p 8079" + }, + "dependencies": { + }, + "devDependencies": { + "json-server": "0.17.4" + } +} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 09d332d..07b9bb9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.devtools.ksp") + id("org.jetbrains.kotlin.plugin.serialization") } android { @@ -59,6 +60,8 @@ dependencies { implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") + + implementation("dev.materii.pullrefresh:pullrefresh:1.0.0") val room_version = "2.5.2" implementation("androidx.room:room-runtime:$room_version") annotationProcessor("androidx.room:room-compiler:$room_version") @@ -78,4 +81,14 @@ dependencies { val paging_version = "3.2.0-rc01" implementation("androidx.paging:paging-runtime:$paging_version") implementation("androidx.paging:paging-compose:$paging_version") + + // retrofit + val retrofitVersion = "2.9.0" + implementation("com.squareup.retrofit2:retrofit:$retrofitVersion") + implementation("com.squareup.okhttp3:logging-interceptor:4.11.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") + + // LiveData | + + implementation("androidx.compose.runtime:runtime-livedata:1.5.4") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 36e417c..51c293d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + tools:targetApi="31" + android:networkSecurityConfig="@xml/network_security_config" + android:vmSafeMode="true"> + + @GET("books/{id}") + suspend fun getBook( + @Path("id") id: Int, + ): BookRemote + + @GET("search") + suspend fun getBySearch( + @Query("title") searchStr: String, + ): List + + @GET("books") + suspend fun getByAuthor( + @Query("authorId") auth: Int, + ): List + + @GET("books") + suspend fun getByUser( + @Query("userId") user: Int, + ): List + + @GET("books") + suspend fun getBooks( + @Query("_page") page: Int, + @Query("_limit") limit: Int, + ): List + + @POST("books") + suspend fun createBook( + @Body book: BookRemote, + ): BookRemote + + @PUT("books/{id}") + suspend fun updateBook( + @Path("id") id: Int, + @Body book: BookRemote, + ): BookRemote + + @DELETE("books/{id}") + suspend fun deleteBook( + @Path("id") id: Int, + ): BookRemote + + @GET("authors") + suspend fun getAuthors(): List + @GET("authors") + suspend fun getAuthors( + @Query("_page") page: Int, + @Query("_limit") limit: Int, + ): List + @GET("authors/{id}") + suspend fun getAuthor( + @Path("id") id: Int, + ): AuthorRemote + @POST("authors") + suspend fun createAuthor( + @Body author: AuthorRemote, + ): AuthorRemote + + @PUT("authors/{id}") + suspend fun updateAuthor( + @Path("id") id: Int, + @Body author: AuthorRemote, + ): AuthorRemote + + @DELETE("authors/{id}") + suspend fun deleteAuthor( + @Path("id") id: Int, + ): AuthorRemote + + @GET("users") + suspend fun getUsers(): List + @GET("users/{id}") + suspend fun getUser( + @Path("id") id: Int, + ): UserRemote + @GET("users") + suspend fun getUserByLoginPass( + @Query("login") login: String, + @Query("password") password: String + ): UserRemote + @POST("users") + suspend fun createUser( + @Body user: UserRemote, + ): UserRemote + + @PUT("users/{id}") + suspend fun updateUser( + @Path("id") id: Int, + @Body user: UserRemote, + ): UserRemote + + @DELETE("users/{id}") + suspend fun deleteUser( + @Path("id") id: Int, + ): UserRemote + companion object { + private const val BASE_URL = "http://100.87.48.148:8079/" + + @Volatile + private var INSTANCE: ServerService? = null + + fun getInstance(): ServerService { + return INSTANCE ?: synchronized(this) { + val logger = HttpLoggingInterceptor() + logger.level = HttpLoggingInterceptor.Level.BASIC + val client = OkHttpClient.Builder() + .addInterceptor(logger) + .build() + val json = Json { ignoreUnknownKeys = true } // Создаем экземпляр Json с ignoreUnknownKeys = true + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) // Применяем конфигурацию Json + .build() + .create(ServerService::class.java) + .also { INSTANCE = it } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/AuthorRemote.kt b/app/src/main/java/com/example/myapplication/api/model/AuthorRemote.kt new file mode 100644 index 0000000..63200e2 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/AuthorRemote.kt @@ -0,0 +1,38 @@ +package com.example.myapplication.api.model + +import com.example.myapplication.db.model.Author +import kotlinx.serialization.Serializable + +@Serializable +data class AuthorRemote( + val id: Int = 0, + val name: String = "", +){ + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AuthorRemote + + if (id != other.id) return false + if (name != other.name) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + name.hashCode() + return result + } +} + +fun AuthorRemote.toAuthor(): Author = Author( + id, + name +) + +fun Author.toAuthorRemote(): AuthorRemote = AuthorRemote( + id, + name +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/BookRemote.kt b/app/src/main/java/com/example/myapplication/api/model/BookRemote.kt new file mode 100644 index 0000000..1eb497a --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/BookRemote.kt @@ -0,0 +1,68 @@ +package com.example.myapplication.api.model + +import com.example.myapplication.db.database.Converters +import com.example.myapplication.db.model.Book +import kotlinx.serialization.Serializable + +private val __converters = Converters() +@Serializable +data class BookRemote( + val id: Int = 0, + val title: String = "", + val description: String = "", + val content: String = "", + val cover: ByteArray? = null, + val authorId: Int = 0, + val userId: Int = 0 +){ + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BookRemote + + if (id != other.id) return false + if (title != other.title) return false + if (description != other.description) return false + if (content != other.content) return false + if (cover != null) { + if (other.cover == null) return false + if (!cover.contentEquals(other.cover)) return false + } else if (other.cover != null) return false + if (authorId != other.authorId) return false + if (userId != other.userId) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + title.hashCode() + result = 31 * result + description.hashCode() + result = 31 * result + content.hashCode() + result = 31 * result + (cover?.contentHashCode() ?: 0) + result = 31 * result + authorId + result = 31 * result + userId + return result + } +} + +fun BookRemote.toBook(): Book = Book( + id, + title, + description, + content, + cover?.let { __converters.toBitmap(it) }, + authorId, + userId +) + +fun Book.toBookRemote(): BookRemote = BookRemote( + id, + title, + description, + content, + cover?.let { __converters.fromBitmap(it) }, + authorId, + userId +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt b/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt new file mode 100644 index 0000000..dd47587 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt @@ -0,0 +1,54 @@ +package com.example.myapplication.api.model + +import com.example.myapplication.db.model.User +import kotlinx.serialization.Serializable + +@Serializable +data class UserRemote( + val id: Int = 0, + val login: String = "", + val password: String = "", + val email: String = "", + val role: String = "" +){ + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UserRemote + + if (id != other.id) return false + if (login != other.login) return false + if (password != other.password) return false + if (email != other.email) return false + if (role != other.role) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + login.hashCode() + result = 31 * result + password.hashCode() + result = 31 * result + email.hashCode() + result = 31 * result + role.hashCode() + + return result + } +} + +fun UserRemote.toUser(): User = User( + id, + login, + password, + email, + role +) + +fun User.toUserRemote(): UserRemote = UserRemote( + uid, + login, + password, + email, + role +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/respositories/Mediator/AuthorRemoteMediator.kt b/app/src/main/java/com/example/myapplication/api/respositories/Mediator/AuthorRemoteMediator.kt new file mode 100644 index 0000000..50bfcf8 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/respositories/Mediator/AuthorRemoteMediator.kt @@ -0,0 +1,107 @@ +package com.example.myapplication.api.respositories.Mediator + +import android.util.Log +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.myapplication.api.ServerService +import com.example.myapplication.api.model.toAuthor +import com.example.myapplication.db.database.AppDatabase +import com.example.myapplication.db.model.Author +import com.example.myapplication.db.model.RemoteKeyType +import com.example.myapplication.db.model.RemoteKeys +import com.example.myapplication.db.respository.OfflineAuthorRepository +import com.example.myapplication.db.respository.OfflineBookRepository +import com.example.myapplication.db.respository.OfflineRemoteKeyRepository +import retrofit2.HttpException +import java.io.IOException +@OptIn(ExperimentalPagingApi::class) +class AuthorRemoteMediator( + private val service: ServerService, + private val dbAuthorRepository: OfflineAuthorRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val database: AppDatabase +): RemoteMediator() { + override suspend fun initialize(): RemoteMediator.InitializeAction { + return RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): RemoteMediator.MediatorResult { + val page = when (loadType) { + LoadType.REFRESH -> { + val remoteKeys = getRemoteKeyClosestToCurrentPosition(state) + remoteKeys?.nextKey?.minus(1) ?: 1 + } + + LoadType.PREPEND -> { + val remoteKeys = getRemoteKeyForFirstItem(state) + remoteKeys?.prevKey + ?: return RemoteMediator.MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + + LoadType.APPEND -> { + val remoteKeys = getRemoteKeyForLastItem(state) + remoteKeys?.nextKey + ?: return RemoteMediator.MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + } + + try { + val authors = service.getAuthors(page, state.config.pageSize).map{it.toAuthor()} + Log.i("Authors info", authors.toString()) + val endOfPaginationReached = authors.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.AUTHOR) + dbAuthorRepository.clearAll() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = authors.map { + RemoteKeys( + entityId = it.id, + type = RemoteKeyType.AUTHOR, + prevKey = prevKey, + nextKey = nextKey + ) + } + dbRemoteKeyRepository.createRemoteKeys(keys) + dbAuthorRepository.insertAuthors(authors) + } + return RemoteMediator.MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return RemoteMediator.MediatorResult.Error(exception) + } catch (exception: HttpException) { + return RemoteMediator.MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { author -> + dbRemoteKeyRepository.getAllRemoteKeys(author.id, RemoteKeyType.AUTHOR) + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { author -> + dbRemoteKeyRepository.getAllRemoteKeys(author.id, RemoteKeyType.AUTHOR) + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.id?.let { authorUid -> + dbRemoteKeyRepository.getAllRemoteKeys(authorUid, RemoteKeyType.AUTHOR) + } + } + } +} diff --git a/app/src/main/java/com/example/myapplication/api/respositories/Mediator/BookRemoteMediator.kt b/app/src/main/java/com/example/myapplication/api/respositories/Mediator/BookRemoteMediator.kt new file mode 100644 index 0000000..284051c --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/respositories/Mediator/BookRemoteMediator.kt @@ -0,0 +1,110 @@ +package com.example.myapplication.api.respositories.Mediator + +import android.util.Log +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.myapplication.api.ServerService +import com.example.myapplication.api.model.toBook +import com.example.myapplication.api.respositories.RestAuthorRepository +import com.example.myapplication.db.database.AppDatabase +import com.example.myapplication.db.model.Book +import com.example.myapplication.db.model.RemoteKeyType +import com.example.myapplication.db.model.RemoteKeys +import com.example.myapplication.db.respository.OfflineBookRepository +import com.example.myapplication.db.respository.OfflineRemoteKeyRepository +import retrofit2.HttpException +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class BookRemoteMediator( + private val service: ServerService, + private val dbBookRepository: OfflineBookRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val authorRestRepository: RestAuthorRepository, + private val database: AppDatabase +) : RemoteMediator() { + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + val page = when (loadType) { + LoadType.REFRESH -> { + val remoteKeys = getRemoteKeyClosestToCurrentPosition(state) + remoteKeys?.nextKey?.minus(1) ?: 1 + } + + LoadType.PREPEND -> { + val remoteKeys = getRemoteKeyForFirstItem(state) + remoteKeys?.prevKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + + LoadType.APPEND -> { + val remoteKeys = getRemoteKeyForLastItem(state) + remoteKeys?.nextKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + } + + try { + val books = service.getBooks(page, state.config.pageSize).map { it.toBook() } + Log.i("Books info", books.toString()) + val endOfPaginationReached = books.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.BOOK) + dbBookRepository.clearAll() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = books.map { + RemoteKeys( + entityId = it.id, + type = RemoteKeyType.BOOK, + prevKey = prevKey, + nextKey = nextKey + ) + } + authorRestRepository.getAll() + dbRemoteKeyRepository.createRemoteKeys(keys) + dbBookRepository.insertBooks(books) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return MediatorResult.Error(exception) + } catch (exception: HttpException) { + return MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { book -> + dbRemoteKeyRepository.getAllRemoteKeys(book.id, RemoteKeyType.BOOK) + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { book -> + dbRemoteKeyRepository.getAllRemoteKeys(book.id, RemoteKeyType.BOOK) + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.id?.let { bookUid -> + dbRemoteKeyRepository.getAllRemoteKeys(bookUid, RemoteKeyType.BOOK) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/respositories/RestAuthorRepository.kt b/app/src/main/java/com/example/myapplication/api/respositories/RestAuthorRepository.kt new file mode 100644 index 0000000..1742166 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/respositories/RestAuthorRepository.kt @@ -0,0 +1,72 @@ +package com.example.myapplication.api.respositories + +import android.util.Log +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.myapplication.api.ServerService +import com.example.myapplication.db.database.AppDatabase +import com.example.myapplication.db.database.AppContainer +import com.example.myapplication.db.respository.OfflineAuthorRepository +import com.example.myapplication.db.respository.OfflineRemoteKeyRepository +import com.example.myapplication.db.respository.AuthorRepository +import com.example.myapplication.api.model.toAuthor +import com.example.myapplication.api.model.toAuthorRemote +import com.example.myapplication.db.model.Author +import com.example.myapplication.api.respositories.Mediator.AuthorRemoteMediator +import kotlinx.coroutines.flow.Flow + +class RestAuthorRepository( + private val service: ServerService, + private val dbAuthorRepository: OfflineAuthorRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val database: AppDatabase +): AuthorRepository { + override suspend fun getAll(): List { + return service.getAuthors().map{x -> x.toAuthor()} + } + + override suspend fun getAllDrop(): List { + return service.getAuthors().map{x -> x.toAuthor()} + } + + override suspend fun getByUid(uid: Int): Author { + return service.getAuthor(uid).toAuthor() + } + + override suspend fun insertAuthor(author: Author) { + dbAuthorRepository.insertAuthor(service.createAuthor(author.toAuthorRemote()).toAuthor()) + } + + override suspend fun updateAuthor(author: Author) { + service.updateAuthor(author.id, author.toAuthorRemote()) + dbAuthorRepository.updateAuthor(author) + } + + override suspend fun deleteAuthor(author: Author) { + service.deleteAuthor(author.id) + dbAuthorRepository.deleteAuthor(author) + } + + override fun loadAllAuthorsPaged(): Flow> { + Log.d(RestAuthorRepository::class.simpleName, "Get authors") + + val pagingSourceFactory = { dbAuthorRepository.loadAuthorsPaged() } + + @OptIn(ExperimentalPagingApi::class) + return Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + remoteMediator = AuthorRemoteMediator( + service, + dbAuthorRepository, + dbRemoteKeyRepository, + database, + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/respositories/RestBookRepository.kt b/app/src/main/java/com/example/myapplication/api/respositories/RestBookRepository.kt new file mode 100644 index 0000000..a0f003d --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/respositories/RestBookRepository.kt @@ -0,0 +1,84 @@ +package com.example.myapplication.api.respositories + +import android.util.Log +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.myapplication.api.ServerService +import com.example.myapplication.db.respository.OfflineBookRepository +import com.example.myapplication.db.respository.OfflineRemoteKeyRepository +import com.example.myapplication.db.database.AppDatabase +import com.example.myapplication.db.respository.BookRepository +import com.example.myapplication.api.model.toBook +import com.example.myapplication.api.model.toBookRemote +import com.example.myapplication.api.respositories.Mediator.BookRemoteMediator +import com.example.myapplication.db.database.AppContainer +import com.example.myapplication.db.model.Book +import kotlinx.coroutines.flow.Flow + +class RestBookRepository( + private val service: ServerService, + private val dbBookRepository: OfflineBookRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val authorRestRepository: RestAuthorRepository, + private val database: AppDatabase +):BookRepository { + override suspend fun getAll(): List { + return service.getBooks().map{x -> x.toBook()} + } + + override suspend fun getByUid(uid: Int): Book { + return service.getBook(uid).toBook() + } + + override suspend fun getBySearch(searchStr: String): List { + return service.getBySearch(searchStr).map{x -> x.toBook()} + } + + override suspend fun getByAuthorId(authorId: Int): List { + return service.getByAuthor(authorId).map{ x -> x.toBook()} + } + + override suspend fun getByUserId(userId: Int): List { + return service.getByUser(userId).map{ x -> x.toBook()} + } + + override fun loadAllBooksPaged(): Flow> { + Log.d(RestBookRepository::class.simpleName, "Get books") + + val pagingSourceFactory = { dbBookRepository.loadBooksPaged() } + + @OptIn(ExperimentalPagingApi::class) + return Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + remoteMediator = BookRemoteMediator( + service, + dbBookRepository, + dbRemoteKeyRepository, + authorRestRepository, + database, + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } + + override suspend fun insertBook(book: Book) { + dbBookRepository.insertBook(service.createBook(book.toBookRemote()).toBook()) + } + + override suspend fun updateBook(book: Book) { + service.updateBook(book.id, book.toBookRemote()) + dbBookRepository.updateBook(book) + } + + override suspend fun deleteBook(book: Book) { + service.deleteBook(book.id) + dbBookRepository.deleteBook(book) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/respositories/RestUserRepository.kt b/app/src/main/java/com/example/myapplication/api/respositories/RestUserRepository.kt new file mode 100644 index 0000000..5988667 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/respositories/RestUserRepository.kt @@ -0,0 +1,47 @@ +package com.example.myapplication.api.respositories + +import com.example.myapplication.api.ServerService +import com.example.myapplication.api.model.toUser +import com.example.myapplication.api.model.toBook +import com.example.myapplication.api.model.toUserRemote +import com.example.myapplication.db.database.AppDatabase +import com.example.myapplication.db.model.Book +import com.example.myapplication.db.model.User +import com.example.myapplication.db.respository.OfflineUserRepository +import com.example.myapplication.db.respository.UserRepository + +class RestUserRepository( + private val service: ServerService, + private val dbUserRepository: OfflineUserRepository, + private val database: AppDatabase +): UserRepository { + override suspend fun getAll(): List { + return service.getUsers().map{x -> x.toUser()} + } + + override suspend fun getByUid(uid: Int): User { + return service.getUser(uid).toUser() + } + + override suspend fun getUserBooks(userid: Int): List { + return service.getByUser(userid).map{x -> x.toBook()} + } + + override suspend fun tryLogin(login: String, password: String): User? { + return service.getUserByLoginPass(login, password).toUser() + } + + override suspend fun insert(user: User) { + dbUserRepository.insert(service.createUser(user.toUserRemote()).toUser()) + } + + override suspend fun update(user: User) { + service.updateUser(user.uid, user.toUserRemote()) + dbUserRepository.update(user) + } + + override suspend fun delete(user: User) { + service.deleteUser(user.uid) + dbUserRepository.delete(user) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/AppViewModelProvider.kt b/app/src/main/java/com/example/myapplication/composeui/AppViewModelProvider.kt index 68b0d99..b5e61ed 100644 --- a/app/src/main/java/com/example/myapplication/composeui/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/myapplication/composeui/AppViewModelProvider.kt @@ -21,52 +21,52 @@ object AppViewModelProvider { initializer { UserPageViewModel( this.createSavedStateHandle(), - myApplication().container.userRepository + myApplication().container.userRestRepository ) } initializer { UserEditViewModel( this.createSavedStateHandle(), - myApplication().container.userRepository + myApplication().container.userRestRepository ) } initializer { SearchPageViewModel( - myApplication().container.bookRepository, + myApplication().container.bookRestRepository, this.createSavedStateHandle() ) } initializer { BookPageViewModel( - myApplication().container.bookRepository, + myApplication().container.bookRestRepository, this.createSavedStateHandle() ) } initializer { BookListViewModel( - myApplication().container.bookRepository + myApplication().container.bookRestRepository ) } initializer { BookEditViewModel( this.createSavedStateHandle(), - myApplication().container.bookRepository + myApplication().container.bookRestRepository ) } initializer { AuthorListViewModel( - myApplication().container.authorRepository + myApplication().container.authorRestRepository ) } initializer { AuthorEditViewModel( this.createSavedStateHandle(), - myApplication().container.authorRepository + myApplication().container.authorRestRepository ) } initializer { AuthorDropDownViewModel( - myApplication().container.authorRepository + myApplication().container.authorRestRepository ) } } diff --git a/app/src/main/java/com/example/myapplication/composeui/AuthorCell.kt b/app/src/main/java/com/example/myapplication/composeui/AuthorCell.kt index ba9dfb3..95ffe12 100644 --- a/app/src/main/java/com/example/myapplication/composeui/AuthorCell.kt +++ b/app/src/main/java/com/example/myapplication/composeui/AuthorCell.kt @@ -56,7 +56,7 @@ fun AuthorCell(navController: NavController?, author: Author, viewModel: AuthorL modifier = Modifier .padding(all = 5.dp), onClick = { - val route = Screen.AuthorEdit.route.replace("{id}", author.uid.toString()) + val route = Screen.AuthorEdit.route.replace("{id}", author.id.toString()) navController!!.navigate(route) }, ) { @@ -71,16 +71,7 @@ fun AuthorCell(navController: NavController?, author: Author, viewModel: AuthorL .padding(all = 5.dp), onClick = { scope.launch { - if (AppDatabase.getInstance(context).bookDao().getByAuthorId(author.uid).firstOrNull().toString() == "[]") { viewModel.deleteAuthor(author) - } else { - val toast = Toast.makeText( - MainActivity.appContext, - "Невозможно удалить, есть книги данного автора", - Toast.LENGTH_SHORT - ) - toast.show() - } } }, colors = ButtonDefaults.buttonColors(containerColor = Color.Red), diff --git a/app/src/main/java/com/example/myapplication/composeui/BookCell.kt b/app/src/main/java/com/example/myapplication/composeui/BookCell.kt index 7d7d968..474fa01 100644 --- a/app/src/main/java/com/example/myapplication/composeui/BookCell.kt +++ b/app/src/main/java/com/example/myapplication/composeui/BookCell.kt @@ -46,7 +46,7 @@ fun BookCell(navController: NavController?, book: Book, viewModel: BookListViewM navController?.navigate( Screen.BookView.route.replace( "{id}", - book.uid.toString() + book.id.toString() ) ) }) @@ -59,7 +59,7 @@ fun BookCell(navController: NavController?, book: Book, viewModel: BookListViewM navController?.navigate( Screen.BookView.route.replace( "{id}", - book.uid.toString() + book.id.toString() ) ) }) { @@ -79,7 +79,7 @@ fun BookCell(navController: NavController?, book: Book, viewModel: BookListViewM modifier = Modifier .padding(all = 5.dp), onClick = { - val route = Screen.BookEdit.route.replace("{id}", book.uid.toString()) + val route = Screen.BookEdit.route.replace("{id}", book.id.toString()) navController!!.navigate(route) }, ) { diff --git a/app/src/main/java/com/example/myapplication/composeui/BookEdit.kt b/app/src/main/java/com/example/myapplication/composeui/BookEdit.kt index 64bad9e..6917b38 100644 --- a/app/src/main/java/com/example/myapplication/composeui/BookEdit.kt +++ b/app/src/main/java/com/example/myapplication/composeui/BookEdit.kt @@ -144,7 +144,7 @@ private fun BookEdit( authorUiState = authorUiState, authorsListUiState = authorsListUiState, onAuthorUpdate = { - onUpdate(bookUiState.bookDetails.copy(authorId = it.uid)) + onUpdate(bookUiState.bookDetails.copy(authorId = it.id)) onAuthorUpdate(it) } ) diff --git a/app/src/main/java/com/example/myapplication/composeui/BookView.kt b/app/src/main/java/com/example/myapplication/composeui/BookView.kt index b38b397..d1f3d22 100644 --- a/app/src/main/java/com/example/myapplication/composeui/BookView.kt +++ b/app/src/main/java/com/example/myapplication/composeui/BookView.kt @@ -46,9 +46,6 @@ private fun getBookImage(context: Context, imageId: String): Bitmap { fun BookView(navController: NavController, id: Int, viewModel: BookPageViewModel = viewModel(factory = AppViewModelProvider.Factory)) { val scope = rememberCoroutineScope() val book = viewModel.bookPageUiState - LaunchedEffect(Unit) { - viewModel.refreshState() - } val context = LocalContext.current Column( Modifier diff --git a/app/src/main/java/com/example/myapplication/composeui/Enter.kt b/app/src/main/java/com/example/myapplication/composeui/Enter.kt index cbb66b4..d64648e 100644 --- a/app/src/main/java/com/example/myapplication/composeui/Enter.kt +++ b/app/src/main/java/com/example/myapplication/composeui/Enter.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.navigation.compose.rememberNavController import com.example.myapplication.MainActivity import com.example.myapplication.SingletonClass +import com.example.myapplication.api.ServerService import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.db.database.AppDatabase import com.example.myapplication.db.model.User @@ -44,6 +45,7 @@ fun Enter(navController: NavController) { var SingletonClass = SingletonClass() val scope = rememberCoroutineScope() val context = LocalContext.current + val service: ServerService var login by remember{mutableStateOf("")} var password by remember{mutableStateOf("")} Column( @@ -65,15 +67,17 @@ fun Enter(navController: NavController) { .fillMaxWidth() .padding(all = 10.dp), onClick = { - scope.launch {if( AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString()) != null){ - SingletonClass.setUserId(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.uid!!) - SingletonClass.setRole(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.role!!) + scope.launch { + //if( AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString()) != null){ + // SingletonClass.setUserId(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.uid!!) + //SingletonClass.setRole(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.role!!) navController?.navigate(Screen.Profile.route) + //} + //else{ + // val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT) + // toast.show() + //} } - else{ - val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT) - toast.show() - }} }) { Text(stringResource(id = R.string.enter)) } diff --git a/app/src/main/java/com/example/myapplication/composeui/ProfileEdit.kt b/app/src/main/java/com/example/myapplication/composeui/ProfileEdit.kt index da17121..9b20926 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ProfileEdit.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ProfileEdit.kt @@ -1,42 +1,24 @@ package com.example.myapplication.composeui -import android.annotation.SuppressLint -import android.content.res.Configuration -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController -import androidx.navigation.compose.rememberNavController -import com.example.myapplication.R import com.example.myapplication.composeui.ViewModel.UserDetails import com.example.myapplication.composeui.ViewModel.UserEditViewModel -import com.example.myapplication.composeui.ViewModel.UserPageViewModel import com.example.myapplication.composeui.ViewModel.UserUiState -import com.example.myapplication.composeui.navigation.Screen -import com.example.myapplication.ui.theme.MyApplicationTheme import kotlinx.coroutines.launch @Composable diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorDropDownViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorDropDownViewModel.kt index 007addc..9b36129 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorDropDownViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorDropDownViewModel.kt @@ -26,7 +26,7 @@ class AuthorDropDownViewModel( fun setCurrentAuthor(authorId: Int) { val author: Author? = - authorsListUiState.authorList.firstOrNull { author -> author.uid == authorId } + authorsListUiState.authorList.firstOrNull { author -> author.id == authorId } author?.let { updateUiState(it) } } @@ -43,4 +43,4 @@ data class AuthorsUiState( val author: Author? = null ) -fun Author.toUiState() = AuthorsUiState(author = Author(uid = uid, name = name)) \ No newline at end of file +fun Author.toUiState() = AuthorsUiState(author = Author(id = id, name = name)) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorEditViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorEditViewModel.kt index 60e63b2..67ed9d0 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorEditViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorEditViewModel.kt @@ -36,9 +36,9 @@ class AuthorEditViewModel(savedStateHandle: SavedStateHandle, suspend fun saveAuthor() { if (validateInput()) { if (authorUid > 0) { - authorRepository.update(authorUiState.authorDetails.toAuthor(authorUid)) + authorRepository.updateAuthor(authorUiState.authorDetails.toAuthor(authorUid)) } else { - authorRepository.insert(authorUiState.authorDetails.toAuthor()) + authorRepository.insertAuthor(authorUiState.authorDetails.toAuthor()) } } } @@ -57,7 +57,7 @@ data class AuthorDetails( val name: String = "" ) fun AuthorDetails.toAuthor(uid: Int = 0): Author = Author( - uid = uid, + id = uid, name = name ) diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorListViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorListViewModel.kt index 9dab526..a021728 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorListViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorListViewModel.kt @@ -26,7 +26,7 @@ class AuthorListViewModel( } val authorPagedData: Flow> = authorRepository.loadAllAuthorsPaged() suspend fun deleteAuthor(author: Author) { - authorRepository.delete(author) + authorRepository.deleteAuthor(author) } } data class AuthorListUiState(val authorList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookEditViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookEditViewModel.kt index 8401d76..11480fb 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookEditViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookEditViewModel.kt @@ -36,9 +36,9 @@ class BookEditViewModel(savedStateHandle: SavedStateHandle, suspend fun saveBook() { if (validateInput()) { if (bookUid > 0) { - bookRepository.update(bookUiState.bookDetails.toBook(bookUid)) + bookRepository.updateBook(bookUiState.bookDetails.toBook(bookUid)) } else { - bookRepository.insert(bookUiState.bookDetails.toBook()) + bookRepository.insertBook(bookUiState.bookDetails.toBook()) } } } @@ -67,7 +67,7 @@ data class BookDetails( val userId: Int = 0, ) fun BookDetails.toBook(uid: Int = 0): Book = Book( - uid = uid, + id = uid, title = title, description = description, content = content, diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookListViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookListViewModel.kt index 2d1e95b..af1022b 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookListViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookListViewModel.kt @@ -26,7 +26,7 @@ class BookListViewModel( } val bookPagedData: Flow> = bookRepository.loadAllBooksPaged() suspend fun deleteBook(book: Book) { - bookRepository.delete(book) + bookRepository.deleteBook(book) } } diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookPageViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookPageViewModel.kt index 412078b..7fa9c99 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookPageViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/BookPageViewModel.kt @@ -9,6 +9,16 @@ import androidx.lifecycle.SavedStateHandle import com.example.myapplication.db.model.Book import com.example.myapplication.db.model.BookWithAuthor import com.example.myapplication.db.respository.BookRepository +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.viewModelScope +import com.example.myapplication.db.database.AppContainer +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch class BookPageViewModel(private val bookRespository: BookRepository, savedStateHandle: SavedStateHandle) : ViewModel(){ @@ -26,7 +36,7 @@ class BookPageViewModel(private val bookRespository: BookRepository, savedStateH bookPageUiState = BookPageUiState(bookRespository.getByUid(bookId)) } suspend fun deleteBook(book: Book) { - bookRespository.delete(book) + bookRespository.deleteBook(book) } } diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/SearchPageViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/SearchPageViewModel.kt index 09e3d19..de351de 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/SearchPageViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/SearchPageViewModel.kt @@ -7,10 +7,13 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData +import androidx.paging.filter import com.example.myapplication.db.model.Book import com.example.myapplication.db.respository.BookRepository import kotlinx.coroutines.launch import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import java.util.Locale class SearchPageViewModel(private val bookRepository: BookRepository, savedStateHandle: SavedStateHandle) : ViewModel(){ private val searchStr: String = checkNotNull(savedStateHandle["searchStr"]) @@ -25,7 +28,11 @@ class SearchPageViewModel(private val bookRepository: BookRepository, savedState searchPageUiState = SearchPageUiState(bookRepository.getBySearch(searchStr)) } - val bookPagedData: Flow> = bookRepository.loadAllBooksPaged() + val bookPagedData: Flow> = bookRepository.loadAllBooksPaged().map{ + x -> x.filter{ + y-> (y.title.lowercase(Locale.ROOT)).contains(searchStr.lowercase(Locale.ROOT)) + } + } } data class SearchPageUiState(val bookList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserEditViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserEditViewModel.kt index 7ddbe2b..80135b9 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserEditViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserEditViewModel.kt @@ -21,7 +21,7 @@ class UserEditViewModel(savedStateHandle: SavedStateHandle, init { viewModelScope.launch { if (userUid > 0) { - userUiState = userRepository.getByUid(userUid) + userUiState = userRepository.getByUid(userUid)!! .toUiState(true) } } diff --git a/app/src/main/java/com/example/myapplication/db/dao/AuthorDao.kt b/app/src/main/java/com/example/myapplication/db/dao/AuthorDao.kt index 85c0748..cb074d8 100644 --- a/app/src/main/java/com/example/myapplication/db/dao/AuthorDao.kt +++ b/app/src/main/java/com/example/myapplication/db/dao/AuthorDao.kt @@ -15,13 +15,15 @@ interface AuthorDao { suspend fun getAll(): List @Query("select * from authors order by author_name collate nocase asc") suspend fun getAllDrop(): List - @Query("select * from authors where authors.uid = :uid") + @Query("select * from authors where authors.id = :uid") suspend fun getByUid(uid: Int): Author @Query("select * from authors order by author_name collate nocase asc") fun loadAllAuthorsPaged(): PagingSource + @Query("DELETE FROM authors") + suspend fun clearAll() @Insert - suspend fun insert(author: Author) + suspend fun insert(vararg author: Author) @Update suspend fun update(author: Author) diff --git a/app/src/main/java/com/example/myapplication/db/dao/BookDao.kt b/app/src/main/java/com/example/myapplication/db/dao/BookDao.kt index 301239d..f66ed55 100644 --- a/app/src/main/java/com/example/myapplication/db/dao/BookDao.kt +++ b/app/src/main/java/com/example/myapplication/db/dao/BookDao.kt @@ -15,23 +15,25 @@ interface BookDao { @Query("select * from books order by title collate nocase asc") suspend fun getAll(): List - @Query("select * from books where books.uid = :uid") + @Query("select * from books where books.id = :uid") suspend fun getByUid(uid: Int): Book @Query("select * from books where books.title LIKE '%' || :searchStr || '%'") suspend fun getBySearch(searchStr: String): List @Query("select * from books where books.user_id = :userId") - fun getByUserId(userId: Int): Flow> + suspend fun getByUserId(userId: Int): List @Query("select * from books where books.author_id = :authorId") - fun getByAuthorId(authorId: Int): Flow> + suspend fun getByAuthorId(authorId: Int): List @Query("select * from books order by title collate nocase asc") fun loadAllBooksPaged(): PagingSource + @Query("DELETE FROM books") + suspend fun clearAll() @Insert - suspend fun insert(book: Book) + suspend fun insert(vararg book: Book) @Update suspend fun update(book: Book) diff --git a/app/src/main/java/com/example/myapplication/db/dao/RemoteKeysDao.kt b/app/src/main/java/com/example/myapplication/db/dao/RemoteKeysDao.kt new file mode 100644 index 0000000..3682cfc --- /dev/null +++ b/app/src/main/java/com/example/myapplication/db/dao/RemoteKeysDao.kt @@ -0,0 +1,20 @@ +package com.example.myapplication.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.example.myapplication.db.model.RemoteKeyType +import com.example.myapplication.db.model.RemoteKeys + +@Dao +interface RemoteKeysDao { + @Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type") + suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(remoteKey: List) + + @Query("DELETE FROM remote_keys WHERE type = :type") + suspend fun clearRemoteKeys(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/dao/UserDao.kt b/app/src/main/java/com/example/myapplication/db/dao/UserDao.kt index 3d59968..2953c59 100644 --- a/app/src/main/java/com/example/myapplication/db/dao/UserDao.kt +++ b/app/src/main/java/com/example/myapplication/db/dao/UserDao.kt @@ -12,13 +12,13 @@ import com.example.myapplication.db.model.Book; @Dao interface UserDao { @Query("select * from users") - fun getAll(): Flow> + suspend fun getAll(): List @Query("select * from users where users.uid = :uid") - suspend fun getByUid(uid: Int): User + suspend fun getByUid(uid: Int): User? @Query("select * from books where books.user_id = :userid") - fun getUserBooks(userid: Int): Flow> + suspend fun getUserBooks(userid: Int): List @Query("select * from users where login = :login and password = :password") suspend fun tryLogin(login: String, password: String): User? diff --git a/app/src/main/java/com/example/myapplication/db/database/AppContainer.kt b/app/src/main/java/com/example/myapplication/db/database/AppContainer.kt index 2613878..ff0c799 100644 --- a/app/src/main/java/com/example/myapplication/db/database/AppContainer.kt +++ b/app/src/main/java/com/example/myapplication/db/database/AppContainer.kt @@ -9,12 +9,17 @@ import com.example.myapplication.db.respository.AuthorRepository import com.example.myapplication.db.respository.UserRepository import com.example.myapplication.db.respository.OfflineBookRepository import com.example.myapplication.db.respository.OfflineAuthorRepository +import com.example.myapplication.db.respository.OfflineRemoteKeyRepository import com.example.myapplication.db.respository.OfflineUserRepository +import com.example.myapplication.api.respositories.RestBookRepository +import com.example.myapplication.api.respositories.RestAuthorRepository +import com.example.myapplication.api.respositories.RestUserRepository +import com.example.myapplication.api.ServerService interface AppContainer { - val bookRepository: BookRepository - val authorRepository: AuthorRepository - val userRepository: UserRepository + val bookRestRepository: RestBookRepository + val authorRestRepository: RestAuthorRepository + val userRestRepository: RestUserRepository companion object { const val TIMEOUT = 5000L const val LIMIT = 10 @@ -22,15 +27,44 @@ interface AppContainer { } class AppDataContainer(private val context: Context) : AppContainer { - override val bookRepository: BookRepository by lazy { + private val bookRepository: OfflineBookRepository by lazy { OfflineBookRepository(AppDatabase.getInstance(context).bookDao()) } - override val authorRepository: AuthorRepository by lazy { + private val authorRepository: OfflineAuthorRepository by lazy { OfflineAuthorRepository(AppDatabase.getInstance(context).authorDao()) } - override val userRepository: UserRepository by lazy { + private val userRepository: OfflineUserRepository by lazy { OfflineUserRepository(AppDatabase.getInstance(context).userDao()) } + private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy { + OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao()) + } + + override val bookRestRepository: RestBookRepository by lazy { + RestBookRepository( + ServerService.getInstance(), + bookRepository, + remoteKeyRepository, + authorRestRepository, + AppDatabase.getInstance(context) + ) + } + + override val authorRestRepository: RestAuthorRepository by lazy { + RestAuthorRepository( + ServerService.getInstance(), + authorRepository, + remoteKeyRepository, + AppDatabase.getInstance(context) + ) + } + override val userRestRepository: RestUserRepository by lazy{ + RestUserRepository( + ServerService.getInstance(), + userRepository, + AppDatabase.getInstance(context) + ) + } companion object { const val TIMEOUT = 5000L diff --git a/app/src/main/java/com/example/myapplication/db/database/AppDatabase.kt b/app/src/main/java/com/example/myapplication/db/database/AppDatabase.kt index 05c0b67..aa426ab 100644 --- a/app/src/main/java/com/example/myapplication/db/database/AppDatabase.kt +++ b/app/src/main/java/com/example/myapplication/db/database/AppDatabase.kt @@ -18,23 +18,26 @@ import com.example.myapplication.db.model.User; import com.example.myapplication.db.model.Book; import com.example.myapplication.db.model.Author; import com.example.myapplication.R +import com.example.myapplication.db.dao.RemoteKeysDao +import com.example.myapplication.db.model.RemoteKeys -@Database(entities = [Book::class, Author::class, User::class], version = 1, exportSchema = false) +@Database(entities = [Book::class, Author::class, User::class, RemoteKeys::class], version = 1, exportSchema = false) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun bookDao(): BookDao abstract fun authorDao(): AuthorDao abstract fun userDao(): UserDao + abstract fun remoteKeysDao(): RemoteKeysDao companion object { - private const val DB_NAME: String = "PMU1" + private const val DB_NAME: String = "PMU4" @Volatile private var INSTANCE: AppDatabase? = null private suspend fun populateDatabase(context: Context) { INSTANCE?.let { database -> - //Users + /* //Users val userDao = database.userDao() val user1 = User(1, "Admin", "Admin", "admin@mail.ru", "ADMIN") val user2 = User(2, "User1", "123", "u1@mail.ru", "USER") @@ -57,7 +60,7 @@ abstract class AppDatabase : RoomDatabase() { bookDao.insert(book1) bookDao.insert(book2) bookDao.insert(book3) - bookDao.insert(book4) + bookDao.insert(book4) */ } } diff --git a/app/src/main/java/com/example/myapplication/db/model/Author.kt b/app/src/main/java/com/example/myapplication/db/model/Author.kt index d49932f..04abea0 100644 --- a/app/src/main/java/com/example/myapplication/db/model/Author.kt +++ b/app/src/main/java/com/example/myapplication/db/model/Author.kt @@ -8,7 +8,7 @@ import androidx.room.PrimaryKey @Entity(tableName = "authors") data class Author( @PrimaryKey(autoGenerate = true) - val uid: Int = 0, + val id: Int = 0, @ColumnInfo(name = "author_name") val name: String ) { @@ -16,11 +16,11 @@ data class Author( if (this === other) return true if (javaClass != other?.javaClass) return false other as Author - if (uid != other.uid) return false + if (id != other.id) return false return true } override fun hashCode(): Int { - return uid ?: -1 + return id ?: -1 } } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/model/Book.kt b/app/src/main/java/com/example/myapplication/db/model/Book.kt index 5b3b422..0e0880f 100644 --- a/app/src/main/java/com/example/myapplication/db/model/Book.kt +++ b/app/src/main/java/com/example/myapplication/db/model/Book.kt @@ -12,10 +12,10 @@ import androidx.room.Ignore tableName = "books", foreignKeys = [ ForeignKey( entity = Author::class, - parentColumns = ["uid"], + parentColumns = ["id"], childColumns = ["author_id"], - onDelete = ForeignKey.RESTRICT, - onUpdate = ForeignKey.RESTRICT + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE ), ForeignKey( entity = User::class, @@ -28,7 +28,7 @@ import androidx.room.Ignore ) data class Book( @PrimaryKey(autoGenerate = true) - val uid: Int = 0, + val id: Int = 0, @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "description") @@ -47,11 +47,11 @@ data class Book( if (this === other) return true if (javaClass != other?.javaClass) return false other as Book - if (uid != other.uid) return false + if (id != other.id) return false return true } override fun hashCode(): Int { - return uid ?: -1 + return id ?: -1 } } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/model/RemoteKeys.kt b/app/src/main/java/com/example/myapplication/db/model/RemoteKeys.kt new file mode 100644 index 0000000..287883a --- /dev/null +++ b/app/src/main/java/com/example/myapplication/db/model/RemoteKeys.kt @@ -0,0 +1,26 @@ +package com.example.myapplication.db.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverter +import androidx.room.TypeConverters + +enum class RemoteKeyType(private val type: String) { + BOOK(Book::class.simpleName ?: "Book"), + AUTHOR(Author::class.simpleName ?: "Author"); + + @TypeConverter + fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value } + + @TypeConverter + fun fromRemoteKeyType(value: RemoteKeyType) = value.type +} + +@Entity(tableName = "remote_keys") +data class RemoteKeys( + @PrimaryKey val entityId: Int, + @TypeConverters(RemoteKeyType::class) + val type: RemoteKeyType, + val prevKey: Int?, + val nextKey: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/respository/AuthorRepository.kt b/app/src/main/java/com/example/myapplication/db/respository/AuthorRepository.kt index 43d38b9..a24eeb3 100644 --- a/app/src/main/java/com/example/myapplication/db/respository/AuthorRepository.kt +++ b/app/src/main/java/com/example/myapplication/db/respository/AuthorRepository.kt @@ -9,7 +9,7 @@ interface AuthorRepository { suspend fun getAllDrop(): List suspend fun getByUid(uid: Int): Author fun loadAllAuthorsPaged(): Flow> - suspend fun insert(author: Author) - suspend fun update(author: Author) - suspend fun delete(author: Author) + suspend fun insertAuthor(author: Author) + suspend fun updateAuthor(author: Author) + suspend fun deleteAuthor(author: Author) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/respository/BookRepository.kt b/app/src/main/java/com/example/myapplication/db/respository/BookRepository.kt index 9b8801a..c50965d 100644 --- a/app/src/main/java/com/example/myapplication/db/respository/BookRepository.kt +++ b/app/src/main/java/com/example/myapplication/db/respository/BookRepository.kt @@ -10,10 +10,10 @@ interface BookRepository { suspend fun getAll(): List suspend fun getByUid(uid: Int): Book suspend fun getBySearch(searchStr: String): List - fun getByUserId(userId: Int): Flow> - fun getByAuthorId(authorId: Int): Flow> + suspend fun getByUserId(userId: Int): List + suspend fun getByAuthorId(authorId: Int): List fun loadAllBooksPaged(): Flow> - suspend fun insert(book: Book) - suspend fun update(book: Book) - suspend fun delete(book: Book) + suspend fun insertBook(book: Book) + suspend fun updateBook(book: Book) + suspend fun deleteBook(book: Book) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/respository/OfflineAuthorRepository.kt b/app/src/main/java/com/example/myapplication/db/respository/OfflineAuthorRepository.kt index d7dd8ae..4cb2011 100644 --- a/app/src/main/java/com/example/myapplication/db/respository/OfflineAuthorRepository.kt +++ b/app/src/main/java/com/example/myapplication/db/respository/OfflineAuthorRepository.kt @@ -13,6 +13,8 @@ class OfflineAuthorRepository(private val authorDao: AuthorDao) : AuthorReposito override suspend fun getAll(): List = authorDao.getAll() override suspend fun getAllDrop(): List = authorDao.getAllDrop() override suspend fun getByUid(uid: Int): Author = authorDao.getByUid(uid) + suspend fun insertAuthors(authors: List) = + authorDao.insert(*authors.toTypedArray()) override fun loadAllAuthorsPaged(): Flow> = Pager( config = PagingConfig( pageSize = AppContainer.LIMIT, @@ -21,9 +23,10 @@ class OfflineAuthorRepository(private val authorDao: AuthorDao) : AuthorReposito pagingSourceFactory = authorDao::loadAllAuthorsPaged ).flow fun loadAuthorsPaged(): PagingSource = authorDao.loadAllAuthorsPaged() - override suspend fun insert(author: Author) = authorDao.insert(author) + suspend fun clearAll() = authorDao.clearAll() + override suspend fun insertAuthor(author: Author) = authorDao.insert(author) - override suspend fun update(author: Author) = authorDao.update(author) + override suspend fun updateAuthor(author: Author) = authorDao.update(author) - override suspend fun delete(author: Author) = authorDao.delete(author) + override suspend fun deleteAuthor(author: Author) = authorDao.delete(author) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/respository/OfflineBookRepository.kt b/app/src/main/java/com/example/myapplication/db/respository/OfflineBookRepository.kt index f372449..036077f 100644 --- a/app/src/main/java/com/example/myapplication/db/respository/OfflineBookRepository.kt +++ b/app/src/main/java/com/example/myapplication/db/respository/OfflineBookRepository.kt @@ -7,7 +7,6 @@ import androidx.paging.PagingSource import com.example.myapplication.db.database.AppContainer import com.example.myapplication.db.dao.BookDao import com.example.myapplication.db.model.Book -import com.example.myapplication.db.model.BookWithAuthor import kotlinx.coroutines.flow.Flow class OfflineBookRepository(private val bookDao: BookDao) : BookRepository { @@ -17,8 +16,10 @@ class OfflineBookRepository(private val bookDao: BookDao) : BookRepository { override suspend fun getBySearch(searchStr: String): List = bookDao.getBySearch(searchStr) - override fun getByUserId(userId: Int): Flow> = bookDao.getByUserId(userId) - override fun getByAuthorId(authorId: Int): Flow> = bookDao.getByAuthorId(authorId) + override suspend fun getByUserId(userId: Int): List = bookDao.getByUserId(userId) + override suspend fun getByAuthorId(authorId: Int): List = bookDao.getByAuthorId(authorId) + suspend fun insertBooks(books: List) = + bookDao.insert(*books.toTypedArray()) override fun loadAllBooksPaged(): Flow> = Pager( config = PagingConfig( pageSize = AppContainer.LIMIT, @@ -27,9 +28,10 @@ class OfflineBookRepository(private val bookDao: BookDao) : BookRepository { pagingSourceFactory = bookDao::loadAllBooksPaged ).flow fun loadBooksPaged(): PagingSource = bookDao.loadAllBooksPaged() - override suspend fun insert(book: Book) = bookDao.insert(book) + suspend fun clearAll() = bookDao.clearAll() + override suspend fun insertBook(book: Book) = bookDao.insert(book) - override suspend fun update(book: Book) = bookDao.update(book) + override suspend fun updateBook(book: Book) = bookDao.update(book) - override suspend fun delete(book: Book) = bookDao.delete(book) + override suspend fun deleteBook(book: Book) = bookDao.delete(book) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/respository/OfflineRemoteKeyRepository.kt b/app/src/main/java/com/example/myapplication/db/respository/OfflineRemoteKeyRepository.kt new file mode 100644 index 0000000..6ad9abe --- /dev/null +++ b/app/src/main/java/com/example/myapplication/db/respository/OfflineRemoteKeyRepository.kt @@ -0,0 +1,16 @@ +package com.example.myapplication.db.respository + +import com.example.myapplication.db.dao.RemoteKeysDao +import com.example.myapplication.db.model.RemoteKeyType +import com.example.myapplication.db.model.RemoteKeys + +class OfflineRemoteKeyRepository( private val remoteKeysDao: RemoteKeysDao) : RemoteKeyRepository { + override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) = + remoteKeysDao.getRemoteKeys(id, type) + + override suspend fun createRemoteKeys(remoteKeys: List) = + remoteKeysDao.insertAll(remoteKeys) + + override suspend fun deleteRemoteKey(type: RemoteKeyType) = + remoteKeysDao.clearRemoteKeys(type) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/respository/OfflineUserRepository.kt b/app/src/main/java/com/example/myapplication/db/respository/OfflineUserRepository.kt index 2eecd37..2a23343 100644 --- a/app/src/main/java/com/example/myapplication/db/respository/OfflineUserRepository.kt +++ b/app/src/main/java/com/example/myapplication/db/respository/OfflineUserRepository.kt @@ -6,11 +6,11 @@ import com.example.myapplication.db.model.Book; import kotlinx.coroutines.flow.Flow class OfflineUserRepository(private val userDao: UserDao) : UserRepository { - override fun getAll(): Flow> = userDao.getAll() + override suspend fun getAll(): List = userDao.getAll() - override suspend fun getByUid(uid: Int): User = userDao.getByUid(uid) + override suspend fun getByUid(uid: Int): User? = userDao.getByUid(uid) - override fun getUserBooks(userid: Int): Flow> = userDao.getUserBooks(userid) + override suspend fun getUserBooks(userid: Int): List = userDao.getUserBooks(userid) override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password) diff --git a/app/src/main/java/com/example/myapplication/db/respository/RemoteKeyRepository.kt b/app/src/main/java/com/example/myapplication/db/respository/RemoteKeyRepository.kt new file mode 100644 index 0000000..e5b6e2b --- /dev/null +++ b/app/src/main/java/com/example/myapplication/db/respository/RemoteKeyRepository.kt @@ -0,0 +1,10 @@ +package com.example.myapplication.db.respository + +import com.example.myapplication.db.model.RemoteKeyType +import com.example.myapplication.db.model.RemoteKeys + +interface RemoteKeyRepository { + suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys? + suspend fun createRemoteKeys(remoteKeys: List) + suspend fun deleteRemoteKey(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/db/respository/UserRepository.kt b/app/src/main/java/com/example/myapplication/db/respository/UserRepository.kt index d14dfce..ce534d2 100644 --- a/app/src/main/java/com/example/myapplication/db/respository/UserRepository.kt +++ b/app/src/main/java/com/example/myapplication/db/respository/UserRepository.kt @@ -4,9 +4,9 @@ import com.example.myapplication.db.model.User; import com.example.myapplication.db.model.Book; import kotlinx.coroutines.flow.Flow interface UserRepository { - fun getAll(): Flow> - suspend fun getByUid(uid: Int): User - fun getUserBooks(userid: Int): Flow> + suspend fun getAll(): List + suspend fun getByUid(uid: Int): User? + suspend fun getUserBooks(userid: Int): List suspend fun tryLogin(login: String, password: String): User? suspend fun insert(user: User) suspend fun update(user: User) diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..1fffd9e --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,7 @@ + + + + 100.87.48.148 + 192.168.56.1 + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 3b1b951..279adfa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,4 +3,5 @@ plugins { id("com.android.application") version "8.1.2" apply false id("org.jetbrains.kotlin.android") version "1.8.20" apply false id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false + id("org.jetbrains.kotlin.plugin.serialization") version "1.8.20" apply false } \ No newline at end of file