初始化项目
This commit is contained in:
11
.dockerignore
Normal file
11
.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
||||||
|
build/
|
||||||
|
.dart_tool/
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.gitignore
|
||||||
|
.idea/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
data
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
data
|
||||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
1490
.idea/caches/deviceStreaming.xml
generated
Normal file
1490
.idea/caches/deviceStreaming.xml
generated
Normal file
File diff suppressed because it is too large
Load Diff
420
.idea/libraries/Dart_Packages.xml
generated
Normal file
420
.idea/libraries/Dart_Packages.xml
generated
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="Dart Packages" type="DartPackagesLibraryType">
|
||||||
|
<properties>
|
||||||
|
<option name="packageNameToDirsMap">
|
||||||
|
<entry key="_fe_analyzer_shared">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/_fe_analyzer_shared-96.0.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="analyzer">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/analyzer-10.2.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="args">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/args-2.7.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="async">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/async-2.13.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="boolean_selector">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/boolean_selector-2.1.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="cli_config">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/cli_config-0.2.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="collection">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/collection-1.19.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="convert">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/convert-3.1.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="coverage">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/coverage-1.15.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="crypto">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/crypto-3.0.7/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="file">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/file-7.0.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="frontend_server_client">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/frontend_server_client-4.0.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="glob">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/glob-2.1.3/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="http">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/http-1.6.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="http_methods">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/http_methods-1.1.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="http_multi_server">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/http_multi_server-3.2.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="http_parser">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/http_parser-4.1.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="io">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/io-1.0.5/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="lints">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/lints-6.1.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="logging">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/logging-1.3.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="matcher">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/matcher-0.12.19/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="meta">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/meta-1.18.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="mime">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/mime-2.0.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="node_preamble">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/node_preamble-2.0.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="package_config">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/package_config-2.2.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="path">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/path-1.9.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="pool">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/pool-1.5.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="pub_semver">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/pub_semver-2.2.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="shelf">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf-1.4.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="shelf_packages_handler">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf_packages_handler-3.0.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="shelf_router">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf_router-1.1.4/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="shelf_static">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf_static-1.1.3/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="shelf_web_socket">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf_web_socket-3.0.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="source_map_stack_trace">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/source_map_stack_trace-2.1.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="source_maps">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/source_maps-0.10.13/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="source_span">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/source_span-1.10.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="stack_trace">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/stack_trace-1.12.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="stream_channel">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/stream_channel-2.1.4/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="string_scanner">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/string_scanner-1.4.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="term_glyph">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/term_glyph-1.2.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="test">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/test-1.30.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="test_api">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/test_api-0.7.10/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="test_core">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/test_core-0.6.16/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="typed_data">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/typed_data-1.4.0/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="vm_service">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/vm_service-15.0.2/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="watcher">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/watcher-1.2.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="web">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/web-1.1.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="web_socket">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/web_socket-1.0.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="web_socket_channel">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/web_socket_channel-3.0.3/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="webkit_inspection_protocol">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/webkit_inspection_protocol-1.2.1/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="yaml">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/yaml-3.1.3/lib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</option>
|
||||||
|
</properties>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/_fe_analyzer_shared-96.0.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/analyzer-10.2.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/args-2.7.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/async-2.13.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/boolean_selector-2.1.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/cli_config-0.2.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/collection-1.19.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/convert-3.1.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/coverage-1.15.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/crypto-3.0.7/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/file-7.0.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/frontend_server_client-4.0.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/glob-2.1.3/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/http-1.6.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/http_methods-1.1.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/http_multi_server-3.2.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/http_parser-4.1.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/io-1.0.5/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/lints-6.1.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/logging-1.3.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/matcher-0.12.19/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/meta-1.18.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/mime-2.0.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/node_preamble-2.0.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/package_config-2.2.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/path-1.9.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/pool-1.5.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/pub_semver-2.2.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf-1.4.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf_packages_handler-3.0.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf_router-1.1.4/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf_static-1.1.3/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/shelf_web_socket-3.0.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/source_map_stack_trace-2.1.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/source_maps-0.10.13/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/source_span-1.10.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/stack_trace-1.12.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/stream_channel-2.1.4/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/string_scanner-1.4.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/term_glyph-1.2.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/test-1.30.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/test_api-0.7.10/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/test_core-0.6.16/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/typed_data-1.4.0/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/vm_service-15.0.2/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/watcher-1.2.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/web-1.1.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/web_socket-1.0.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/web_socket_channel-3.0.3/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/webkit_inspection_protocol-1.2.1/lib" />
|
||||||
|
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/mirrors.tuna.tsinghua.edu.cn%47dart-pub%47/yaml-3.1.3/lib" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
31
.idea/libraries/Dart_SDK.xml
generated
Normal file
31
.idea/libraries/Dart_SDK.xml
generated
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="Dart SDK">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/_internal" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/async" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/cli" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/collection" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/concurrent" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/convert" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/core" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/developer" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/ffi" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/html" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/indexed_db" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/io" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/isolate" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/js" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/js_interop" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/js_interop_unsafe" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/js_util" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/math" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/mirrors" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/svg" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/typed_data" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/web_audio" />
|
||||||
|
<root url="file://$PROJECT_DIR$/../../../Repository/Scoop/apps/dart/3.10.7/lib/web_gl" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
8
.idea/markdown.xml
generated
Normal file
8
.idea/markdown.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MarkdownSettings">
|
||||||
|
<option name="previewPanelProviderInfo">
|
||||||
|
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
5
.idea/misc.xml
generated
Normal file
5
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/loongyan.iml" filepath="$PROJECT_DIR$/loongyan.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/studiobot.xml
generated
Normal file
6
.idea/studiobot.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="StudioBotProjectSettings">
|
||||||
|
<option name="shareContext" value="OptedOut" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
||||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Use latest stable channel SDK.
|
||||||
|
FROM dart:stable AS build
|
||||||
|
|
||||||
|
# Resolve app dependencies.
|
||||||
|
WORKDIR /app
|
||||||
|
COPY pubspec.* ./
|
||||||
|
RUN dart pub get
|
||||||
|
|
||||||
|
# Copy app source code (except anything in .dockerignore) and AOT compile app.
|
||||||
|
COPY . .
|
||||||
|
RUN dart compile exe bin/server.dart -o bin/server
|
||||||
|
|
||||||
|
# Build minimal serving image from AOT-compiled `/server`
|
||||||
|
# and the pre-built AOT-runtime in the `/runtime/` directory of the base image.
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=build /runtime/ /
|
||||||
|
COPY --from=build /app/bin/server /app/bin/
|
||||||
|
|
||||||
|
# Start server.
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["/app/bin/server"]
|
||||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
A server app built using [Shelf](https://pub.dev/packages/shelf),
|
||||||
|
configured to enable running with [Docker](https://www.docker.com/).
|
||||||
|
|
||||||
|
This sample code handles HTTP GET requests to `/` and `/echo/<message>`
|
||||||
|
|
||||||
|
# Running the sample
|
||||||
|
|
||||||
|
## Running with the Dart SDK
|
||||||
|
|
||||||
|
You can run the example with the [Dart SDK](https://dart.dev/get-dart)
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ dart run bin/server.dart
|
||||||
|
Server listening on port 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
And then from a second terminal:
|
||||||
|
```
|
||||||
|
$ curl http://0.0.0.0:8080
|
||||||
|
Hello, World!
|
||||||
|
$ curl http://0.0.0.0:8080/echo/I_love_Dart
|
||||||
|
I_love_Dart
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running with Docker
|
||||||
|
|
||||||
|
If you have [Docker Desktop](https://www.docker.com/get-started) installed, you
|
||||||
|
can build and run with the `docker` command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker build . -t myserver
|
||||||
|
$ docker run -it -p 8080:8080 myserver
|
||||||
|
Server listening on port 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
And then from a second terminal:
|
||||||
|
```
|
||||||
|
$ curl http://0.0.0.0:8080
|
||||||
|
Hello, World!
|
||||||
|
$ curl http://0.0.0.0:8080/echo/I_love_Dart
|
||||||
|
I_love_Dart
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see the logging printed in the first terminal:
|
||||||
|
```
|
||||||
|
2021-05-06T15:47:04.620417 0:00:00.000158 GET [200] /
|
||||||
|
2021-05-06T15:47:08.392928 0:00:00.001216 GET [200] /echo/I_love_Dart
|
||||||
|
```
|
||||||
30
analysis_options.yaml
Normal file
30
analysis_options.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
22
bin/domain/entities/album.dart
Normal file
22
bin/domain/entities/album.dart
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
class Album {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
|
||||||
|
Album({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'createdAt': createdAt.toIso8601String(),
|
||||||
|
'updatedAt': updatedAt.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
37
bin/domain/entities/photo.dart
Normal file
37
bin/domain/entities/photo.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
class Photo {
|
||||||
|
final int id;
|
||||||
|
final int albumId;
|
||||||
|
final String filePath;
|
||||||
|
final String fileName;
|
||||||
|
final int fileSize;
|
||||||
|
final String mimeType;
|
||||||
|
final int? width;
|
||||||
|
final int? height;
|
||||||
|
final DateTime createdAt;
|
||||||
|
|
||||||
|
Photo({
|
||||||
|
required this.id,
|
||||||
|
required this.albumId,
|
||||||
|
required this.filePath,
|
||||||
|
required this.fileName,
|
||||||
|
required this.fileSize,
|
||||||
|
required this.mimeType,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
required this.createdAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'albumId': albumId,
|
||||||
|
'filePath': filePath,
|
||||||
|
'fileName': fileName,
|
||||||
|
'fileSize': fileSize,
|
||||||
|
'mimeType': mimeType,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'createdAt': createdAt.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
41
bin/domain/repositories/album_repository.dart
Normal file
41
bin/domain/repositories/album_repository.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
|
import '../entities/album.dart';
|
||||||
|
|
||||||
|
class AlbumRepository {
|
||||||
|
final String basePath;
|
||||||
|
|
||||||
|
AlbumRepository({required this.basePath});
|
||||||
|
|
||||||
|
Future<List<Album>> getAllAlbums() async {
|
||||||
|
final dir = Directory(basePath);
|
||||||
|
if (!await dir.exists()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var albums = await dir
|
||||||
|
.list()
|
||||||
|
.where((d) => d is Directory)
|
||||||
|
.map((d) => d as Directory)
|
||||||
|
.map((dir) {
|
||||||
|
final name = p.basename(dir.path);
|
||||||
|
return Album(
|
||||||
|
id: name.hashCode,
|
||||||
|
name: name,
|
||||||
|
createdAt: dir.statSync().changed,
|
||||||
|
updatedAt: dir.statSync().changed,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return albums;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Album?> getAlbumById(int id) async {
|
||||||
|
final albums = await getAllAlbums();
|
||||||
|
return albums.where((a) => a.id == id).firstOrNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
317
bin/domain/repositories/photo_repository.dart
Normal file
317
bin/domain/repositories/photo_repository.dart
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import '../entities/photo.dart';
|
||||||
|
|
||||||
|
class PhotoRepository {
|
||||||
|
final String basePath;
|
||||||
|
|
||||||
|
PhotoRepository(this.basePath);
|
||||||
|
|
||||||
|
Future<List<Photo>> getPhotosByAlbumId(int id) async {
|
||||||
|
final dir = Directory(basePath);
|
||||||
|
|
||||||
|
if (!await dir.exists()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory? albumDir;
|
||||||
|
try {
|
||||||
|
albumDir = await dir
|
||||||
|
.list()
|
||||||
|
.where((f) => f is Directory)
|
||||||
|
.where((d) => p.basename(d.path).hashCode == id)
|
||||||
|
.first
|
||||||
|
as Directory;
|
||||||
|
} on StateError {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取相册中的所有文件
|
||||||
|
final files = await albumDir
|
||||||
|
.list()
|
||||||
|
.where((f) => f is File)
|
||||||
|
.map((f) => f as File)
|
||||||
|
.where((f) => _isImageOrVideo(f.path))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 并行处理所有文件的尺寸获取
|
||||||
|
final photoFutures = files.map((file) async {
|
||||||
|
final stat = file.statSync();
|
||||||
|
final fileName = p.basename(file.path);
|
||||||
|
final mimeType = _getMimeType(file.path);
|
||||||
|
final dimensions = _getImageDimensions(file, mimeType);
|
||||||
|
|
||||||
|
return Photo(
|
||||||
|
id: fileName.hashCode,
|
||||||
|
albumId: id,
|
||||||
|
filePath: file.path,
|
||||||
|
fileName: fileName,
|
||||||
|
fileSize: stat.size,
|
||||||
|
mimeType: mimeType,
|
||||||
|
width: dimensions?.$1,
|
||||||
|
height: dimensions?.$2,
|
||||||
|
createdAt: stat.changed,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Future.wait(photoFutures);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Photo?> getPhotoById(int id) async {
|
||||||
|
final dir = Directory(basePath);
|
||||||
|
|
||||||
|
if (!await dir.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file;
|
||||||
|
try {
|
||||||
|
file = await dir
|
||||||
|
.list(recursive: true)
|
||||||
|
.where((f) => f is File)
|
||||||
|
.map((f) => f as File)
|
||||||
|
.firstWhere((f) => p.basename(f.path).hashCode == id);
|
||||||
|
} on StateError {
|
||||||
|
// firstWhere throws StateError when no element is found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final stat = file.statSync();
|
||||||
|
final mimeType = _getMimeType(file.path);
|
||||||
|
final dimensions = _getImageDimensions(file, mimeType);
|
||||||
|
|
||||||
|
return Photo(
|
||||||
|
id: p.basename(file.path).hashCode,
|
||||||
|
albumId: p.basename(file.parent.path).hashCode,
|
||||||
|
filePath: file.path,
|
||||||
|
fileName: p.basename(file.path),
|
||||||
|
fileSize: stat.size,
|
||||||
|
mimeType: mimeType,
|
||||||
|
width: dimensions?.$1,
|
||||||
|
height: dimensions?.$2,
|
||||||
|
createdAt: stat.changed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取图片的宽度和高度
|
||||||
|
/// 返回 (width, height) 或 null(如果是视频或无法解析)
|
||||||
|
(int, int)? _getImageDimensions(File file, String mimeType) {
|
||||||
|
// 视频文件不返回尺寸
|
||||||
|
if (mimeType.startsWith('video/')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 只读取文件头部的一小部分来获取尺寸
|
||||||
|
final raf = file.openSync();
|
||||||
|
try {
|
||||||
|
// 读取前 32KB 应该足够获取图片尺寸
|
||||||
|
final bytesToRead = 32 * 1024;
|
||||||
|
final buffer = Uint8List(bytesToRead);
|
||||||
|
final bytesRead = raf.readIntoSync(buffer);
|
||||||
|
final data = buffer.sublist(0, bytesRead);
|
||||||
|
return _parseImageDimensions(data, mimeType);
|
||||||
|
} finally {
|
||||||
|
raf.closeSync();
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// 如果无法解析图片,返回 null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析图片尺寸
|
||||||
|
/// 支持 JPEG, PNG, GIF, BMP, WebP 格式
|
||||||
|
(int, int)? _parseImageDimensions(Uint8List data, String mimeType) {
|
||||||
|
try {
|
||||||
|
switch (mimeType) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
return _parseJpegDimensions(data);
|
||||||
|
case 'image/png':
|
||||||
|
return _parsePngDimensions(data);
|
||||||
|
case 'image/gif':
|
||||||
|
return _parseGifDimensions(data);
|
||||||
|
case 'image/bmp':
|
||||||
|
return _parseBmpDimensions(data);
|
||||||
|
case 'image/webp':
|
||||||
|
return _parseWebpDimensions(data);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析 JPEG 图片尺寸
|
||||||
|
(int, int)? _parseJpegDimensions(Uint8List data) {
|
||||||
|
if (data.length < 2 || data[0] != 0xFF || data[1] != 0xD8) {
|
||||||
|
return null; // 不是有效的 JPEG
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = 2;
|
||||||
|
while (offset < data.length - 1) {
|
||||||
|
if (data[offset] != 0xFF) {
|
||||||
|
offset++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final marker = data[offset + 1];
|
||||||
|
|
||||||
|
// SOF 标记 (Start Of Frame)
|
||||||
|
if (marker >= 0xC0 && marker <= 0xC3 ||
|
||||||
|
marker >= 0xC5 && marker <= 0xC7 ||
|
||||||
|
marker >= 0xC9 && marker <= 0xCB ||
|
||||||
|
marker >= 0xCD && marker <= 0xCF) {
|
||||||
|
if (offset + 9 < data.length) {
|
||||||
|
final height = (data[offset + 5] << 8) | data[offset + 6];
|
||||||
|
final width = (data[offset + 7] << 8) | data[offset + 8];
|
||||||
|
return (width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过这个标记
|
||||||
|
if (marker >= 0xD0 && marker <= 0xD9) {
|
||||||
|
offset += 2;
|
||||||
|
} else if (marker == 0xFF) {
|
||||||
|
offset += 2;
|
||||||
|
} else {
|
||||||
|
if (offset + 3 < data.length) {
|
||||||
|
final length = (data[offset + 2] << 8) | data[offset + 3];
|
||||||
|
offset += 2 + length;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析 PNG 图片尺寸
|
||||||
|
(int, int)? _parsePngDimensions(Uint8List data) {
|
||||||
|
if (data.length < 24 ||
|
||||||
|
data[0] != 0x89 || data[1] != 0x50 ||
|
||||||
|
data[2] != 0x4E || data[3] != 0x47 ||
|
||||||
|
data[4] != 0x0D || data[5] != 0x0A ||
|
||||||
|
data[6] != 0x1A || data[7] != 0x0A) {
|
||||||
|
return null; // 不是有效的 PNG
|
||||||
|
}
|
||||||
|
|
||||||
|
// IHDR chunk 在第 16-23 字节
|
||||||
|
final width = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19];
|
||||||
|
final height = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23];
|
||||||
|
return (width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析 GIF 图片尺寸
|
||||||
|
(int, int)? _parseGifDimensions(Uint8List data) {
|
||||||
|
if (data.length < 10 ||
|
||||||
|
data[0] != 0x47 || data[1] != 0x49 ||
|
||||||
|
data[2] != 0x46 || data[3] != 0x38) {
|
||||||
|
return null; // 不是有效的 GIF
|
||||||
|
}
|
||||||
|
|
||||||
|
final width = data[6] | (data[7] << 8);
|
||||||
|
final height = data[8] | (data[9] << 8);
|
||||||
|
return (width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析 BMP 图片尺寸
|
||||||
|
(int, int)? _parseBmpDimensions(Uint8List data) {
|
||||||
|
if (data.length < 26 || data[0] != 0x42 || data[1] != 0x4D) {
|
||||||
|
return null; // 不是有效的 BMP
|
||||||
|
}
|
||||||
|
|
||||||
|
final width = data[18] | (data[19] << 8) | (data[20] << 16) | (data[21] << 24);
|
||||||
|
final height = data[22] | (data[23] << 8) | (data[24] << 16) | (data[25] << 24);
|
||||||
|
return (width, height.abs()); // BMP 高度可能是负数
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析 WebP 图片尺寸
|
||||||
|
(int, int)? _parseWebpDimensions(Uint8List data) {
|
||||||
|
if (data.length < 30 ||
|
||||||
|
data[0] != 0x52 || data[1] != 0x49 ||
|
||||||
|
data[2] != 0x46 || data[3] != 0x46 ||
|
||||||
|
data[8] != 0x57 || data[9] != 0x45 ||
|
||||||
|
data[10] != 0x42 || data[11] != 0x50) {
|
||||||
|
return null; // 不是有效的 WebP
|
||||||
|
}
|
||||||
|
|
||||||
|
final type = String.fromCharCodes(data.sublist(12, 16));
|
||||||
|
|
||||||
|
if (type == 'VP8 ') {
|
||||||
|
// Lossy WebP
|
||||||
|
if (data.length < 30) return null;
|
||||||
|
final key = (data[23] << 8) | data[22];
|
||||||
|
if (key != 0x9D01) return null;
|
||||||
|
final width = data[26] | (data[27] << 8);
|
||||||
|
final height = data[28] | (data[29] << 8);
|
||||||
|
return (width, height);
|
||||||
|
} else if (type == 'VP8L') {
|
||||||
|
// Lossless WebP
|
||||||
|
if (data.length < 29) return null;
|
||||||
|
if (data[21] != 0x2F) return null;
|
||||||
|
final width = 1 + ((data[22] | (data[23] << 8)) & 0x3FFF);
|
||||||
|
final height = 1 + (((data[23] >> 6) | (data[24] << 2) | (data[25] << 10)) & 0x3FFF);
|
||||||
|
return (width, height);
|
||||||
|
} else if (type == 'VP8X') {
|
||||||
|
// Extended WebP
|
||||||
|
if (data.length < 30) return null;
|
||||||
|
final width = 1 + (data[24] | (data[25] << 8) | (data[26] << 16));
|
||||||
|
final height = 1 + (data[27] | (data[28] << 8) | (data[29] << 16));
|
||||||
|
return (width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deletePhoto(int id) {
|
||||||
|
// TODO: implement deletePhoto
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isImageOrVideo(String path) {
|
||||||
|
final ext = p.extension(path).toLowerCase();
|
||||||
|
return [
|
||||||
|
'.jpg',
|
||||||
|
'.jpeg',
|
||||||
|
'.png',
|
||||||
|
'.gif',
|
||||||
|
'.bmp',
|
||||||
|
'.webp',
|
||||||
|
'.mp4',
|
||||||
|
'.avi',
|
||||||
|
'.mov',
|
||||||
|
'.mkv',
|
||||||
|
].contains(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getMimeType(String path) {
|
||||||
|
final ext = p.extension(path).toLowerCase();
|
||||||
|
switch (ext) {
|
||||||
|
case '.jpg':
|
||||||
|
case '.jpeg':
|
||||||
|
return 'image/jpeg';
|
||||||
|
case '.png':
|
||||||
|
return 'image/png';
|
||||||
|
case '.gif':
|
||||||
|
return 'image/gif';
|
||||||
|
case '.bmp':
|
||||||
|
return 'image/bmp';
|
||||||
|
case '.webp':
|
||||||
|
return 'image/webp';
|
||||||
|
case '.mp4':
|
||||||
|
return 'video/mp4';
|
||||||
|
case '.avi':
|
||||||
|
return 'video/avi';
|
||||||
|
case '.mov':
|
||||||
|
return 'video/quicktime';
|
||||||
|
case '.mkv':
|
||||||
|
return 'video/x-matroska';
|
||||||
|
default:
|
||||||
|
return 'application/octet-stream';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
bin/middleware/exception_handler.dart
Normal file
43
bin/middleware/exception_handler.dart
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
/// 全局异常处理中间件
|
||||||
|
/// 捕获所有未处理的异常并返回适当的 HTTP 响应
|
||||||
|
Middleware exceptionHandler() {
|
||||||
|
return (Handler innerHandler) {
|
||||||
|
return (Request request) async {
|
||||||
|
try {
|
||||||
|
return await innerHandler(request);
|
||||||
|
} on FormatException catch (e) {
|
||||||
|
// 处理参数格式错误,如无效的 ID
|
||||||
|
return Response.badRequest(
|
||||||
|
body: 'Invalid parameter format: ${e.message}',
|
||||||
|
headers: {'content-type': 'text/plain'},
|
||||||
|
);
|
||||||
|
} on PathNotFoundException catch (e) {
|
||||||
|
// 处理路径未找到
|
||||||
|
return Response.notFound('Resource not found: ${e.message}');
|
||||||
|
} on FileSystemException catch (e) {
|
||||||
|
// 处理文件系统错误
|
||||||
|
return Response.internalServerError(
|
||||||
|
body: 'File system error: ${e.message}',
|
||||||
|
headers: {'content-type': 'text/plain'},
|
||||||
|
);
|
||||||
|
} on Exception catch (e) {
|
||||||
|
// 处理其他已知异常
|
||||||
|
return Response.internalServerError(
|
||||||
|
body: 'Internal server error: ${e.toString()}',
|
||||||
|
headers: {'content-type': 'text/plain'},
|
||||||
|
);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
// 处理未知异常
|
||||||
|
print('Unhandled exception: $e');
|
||||||
|
print('Stack trace: $stackTrace');
|
||||||
|
return Response.internalServerError(
|
||||||
|
body: 'An unexpected error occurred',
|
||||||
|
headers: {'content-type': 'text/plain'},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
138
bin/router.dart
Normal file
138
bin/router.dart
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
import 'package:shelf_router/shelf_router.dart';
|
||||||
|
|
||||||
|
import 'domain/repositories/photo_repository.dart';
|
||||||
|
import 'domain/repositories/album_repository.dart';
|
||||||
|
|
||||||
|
final albumRepository = AlbumRepository(basePath: 'data');
|
||||||
|
final photoRepository = PhotoRepository('data');
|
||||||
|
|
||||||
|
Response jsonResponse(dynamic data) {
|
||||||
|
return Response.ok(
|
||||||
|
jsonEncode(data),
|
||||||
|
headers: {'content-type': 'application/json'},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Router createRouter() {
|
||||||
|
final router = Router()
|
||||||
|
..get('/', _rootHandler)
|
||||||
|
..get("/album", _listAlbumHandler)
|
||||||
|
..get("/album/<id>", _getAlbumHandler)
|
||||||
|
..get("/album/<id>/photo", _getPhotosOfAlbumHander)
|
||||||
|
..get('/photo/<id>', _getPhotoHandler)
|
||||||
|
..get('/photo/<id>/file', _getPhotoFileHandler)
|
||||||
|
..get('/echo/<message>', _echoHandler);
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response _rootHandler(Request req) {
|
||||||
|
return Response.ok('Hello, World!\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
Response _echoHandler(Request request) {
|
||||||
|
final message = request.params['message'];
|
||||||
|
return Response.ok('$message\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _listAlbumHandler(Request req) async {
|
||||||
|
final albums = await albumRepository.getAllAlbums();
|
||||||
|
final albumList = albums
|
||||||
|
.map(
|
||||||
|
(a) => {
|
||||||
|
'id': a.id,
|
||||||
|
'name': a.name,
|
||||||
|
'createdAt': a.createdAt.toIso8601String(),
|
||||||
|
'updatedAt': a.updatedAt.toIso8601String(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
return jsonResponse(albumList);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _getAlbumHandler(Request req) async {
|
||||||
|
final idParam = req.params['id'];
|
||||||
|
if (idParam == null) {
|
||||||
|
return Response.badRequest(body: 'Missing album id');
|
||||||
|
}
|
||||||
|
final id = int.tryParse(idParam);
|
||||||
|
if (id == null) {
|
||||||
|
return Response.badRequest(body: 'Invalid album id: $idParam');
|
||||||
|
}
|
||||||
|
final album = await albumRepository.getAlbumById(id);
|
||||||
|
if (album == null) {
|
||||||
|
return Response.notFound('Album not found');
|
||||||
|
}
|
||||||
|
return jsonResponse({
|
||||||
|
'id': album.id,
|
||||||
|
'name': album.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _getPhotoHandler(Request req) async {
|
||||||
|
final idParam = req.params['id'];
|
||||||
|
if (idParam == null) {
|
||||||
|
return Response.badRequest(body: 'Missing photo id');
|
||||||
|
}
|
||||||
|
final id = int.tryParse(idParam);
|
||||||
|
if (id == null) {
|
||||||
|
return Response.badRequest(body: 'Invalid photo id: $idParam');
|
||||||
|
}
|
||||||
|
|
||||||
|
final photo = await photoRepository.getPhotoById(id);
|
||||||
|
if (photo == null) {
|
||||||
|
return Response.notFound('Photo not found');
|
||||||
|
}
|
||||||
|
return jsonResponse(photo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _getPhotoFileHandler(Request req) async {
|
||||||
|
final idParam = req.params['id'];
|
||||||
|
if (idParam == null) {
|
||||||
|
return Response.badRequest(body: 'Missing photo id');
|
||||||
|
}
|
||||||
|
final id = int.tryParse(idParam);
|
||||||
|
if (id == null) {
|
||||||
|
return Response.badRequest(body: 'Invalid photo id: $idParam');
|
||||||
|
}
|
||||||
|
|
||||||
|
final photo = await photoRepository.getPhotoById(id);
|
||||||
|
if (photo == null) {
|
||||||
|
return Response.notFound('Photo not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
final filePath = photo.filePath;
|
||||||
|
final file = File(filePath);
|
||||||
|
|
||||||
|
if (!await file.exists()) {
|
||||||
|
return Response.notFound('File not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok(
|
||||||
|
file.openRead(),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': photo.mimeType,
|
||||||
|
'Content-Length': photo.fileSize.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Future<Response> _getPhotosOfAlbumHander(Request req) async {
|
||||||
|
final idParam = req.params['id'];
|
||||||
|
if (idParam == null) {
|
||||||
|
return Response.badRequest(body: 'Missing album id');
|
||||||
|
}
|
||||||
|
final albumId = int.tryParse(idParam);
|
||||||
|
if (albumId == null) {
|
||||||
|
return Response.badRequest(body: 'Invalid album id: $idParam');
|
||||||
|
}
|
||||||
|
final photos = await photoRepository.getPhotosByAlbumId(albumId);
|
||||||
|
return jsonResponse(photos);
|
||||||
|
}
|
||||||
23
bin/server.dart
Normal file
23
bin/server.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:shelf/shelf.dart';
|
||||||
|
import 'package:shelf/shelf_io.dart';
|
||||||
|
|
||||||
|
import 'router.dart';
|
||||||
|
import 'middleware/exception_handler.dart';
|
||||||
|
|
||||||
|
void main(List<String> args) async {
|
||||||
|
// Use any available host or container IP (usually `0.0.0.0`).
|
||||||
|
final ip = InternetAddress.anyIPv4;
|
||||||
|
|
||||||
|
// Configure a pipeline that logs requests and handles exceptions.
|
||||||
|
final handler = Pipeline()
|
||||||
|
.addMiddleware(logRequests())
|
||||||
|
.addMiddleware(exceptionHandler())
|
||||||
|
.addHandler(createRouter().call);
|
||||||
|
|
||||||
|
// For running in containers, we respect the PORT environment variable.
|
||||||
|
final port = int.parse(Platform.environment['PORT'] ?? '8080');
|
||||||
|
final server = await serve(handler, ip, port);
|
||||||
|
print('Server listening on port ${server.port}');
|
||||||
|
}
|
||||||
14
loongyan.iml
Normal file
14
loongyan.iml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
413
pubspec.lock
Normal file
413
pubspec.lock
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "3b19a47f6ea7c2632760777c78174f47f6aec1e05f0cd611380d4593b8af1dbc"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "96.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "0c516bc4ad36a1a75759e54d5047cb9d15cded4459df01aa35a0b5ec7db2c2a0"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "10.2.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
cli_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_config
|
||||||
|
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.15.0"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
http:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http_methods:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_methods
|
||||||
|
sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.19"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "9f29b9bcc8ee287b1a31e0d01be0eae99a930dbffdaecf04b3f3d82a969f296f"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.1"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
path:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
shelf:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_router:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shelf_router
|
||||||
|
sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.30.0"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.10"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.16"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.2"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.10.0-0 <4.0.0"
|
||||||
17
pubspec.yaml
Normal file
17
pubspec.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: loongyan
|
||||||
|
description: A server app using the shelf package and Docker.
|
||||||
|
version: 1.0.0
|
||||||
|
# repository: https://github.com/my_org/my_repo
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.10.7
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
shelf: ^1.4.2
|
||||||
|
shelf_router: ^1.1.2
|
||||||
|
path: ^1.9.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
http: ^1.2.2
|
||||||
|
lints: ^6.0.0
|
||||||
|
test: ^1.25.6
|
||||||
179
test/server_test.dart
Normal file
179
test/server_test.dart
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final port = '8080';
|
||||||
|
final host = 'http://localhost:$port';
|
||||||
|
late Process p;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
p = await Process.start(
|
||||||
|
'dart',
|
||||||
|
['run', 'bin/server.dart'],
|
||||||
|
environment: {'PORT': port},
|
||||||
|
);
|
||||||
|
// Wait for server to start (give it some time to initialize)
|
||||||
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
p.kill();
|
||||||
|
// Wait for process to exit
|
||||||
|
try {
|
||||||
|
await p.exitCode.timeout(const Duration(seconds: 5), onTimeout: () => 0);
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Root endpoint', () {
|
||||||
|
test('GET / returns Hello World', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/'));
|
||||||
|
expect(response.statusCode, 200);
|
||||||
|
expect(response.body, 'Hello, World!\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Echo endpoint', () {
|
||||||
|
test('GET /echo/<message> returns the message', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/echo/hello'));
|
||||||
|
expect(response.statusCode, 200);
|
||||||
|
expect(response.body, 'hello\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /echo/<message> with special characters', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/echo/test%20message'));
|
||||||
|
expect(response.statusCode, 200);
|
||||||
|
expect(response.body, contains('test'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Album endpoints', () {
|
||||||
|
test('GET /album returns list of albums', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/album'));
|
||||||
|
expect(response.statusCode, 200);
|
||||||
|
expect(response.headers['content-type'], contains('application/json'));
|
||||||
|
final albums = jsonDecode(response.body) as List;
|
||||||
|
expect(albums, isA<List>());
|
||||||
|
if (albums.isNotEmpty) {
|
||||||
|
expect(albums.first, containsPair('id', isA<int>()));
|
||||||
|
expect(albums.first, containsPair('name', isA<String>()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /album/<id> with invalid id returns 400', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/album/invalid_id'));
|
||||||
|
expect(response.statusCode, 400);
|
||||||
|
expect(response.body, contains('Invalid album id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /album/<id> with non-existent id returns 404', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/album/999999'));
|
||||||
|
expect(response.statusCode, 404);
|
||||||
|
expect(response.body, contains('Album not found'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /album/<id>/photo with invalid id returns 400', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/album/invalid_id/photo'));
|
||||||
|
expect(response.statusCode, 400);
|
||||||
|
expect(response.body, contains('Invalid album id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /album/<id>/photo with valid id returns photos', () async {
|
||||||
|
// First get the list to find a valid album id
|
||||||
|
final listResponse = await http.get(Uri.parse('$host/album'));
|
||||||
|
expect(listResponse.statusCode, 200);
|
||||||
|
final albums = jsonDecode(listResponse.body) as List;
|
||||||
|
|
||||||
|
// Skip test if no albums exist
|
||||||
|
if (albums.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final album = albums.first as Map<String, dynamic>;
|
||||||
|
final albumId = album['id'];
|
||||||
|
|
||||||
|
final response = await http.get(Uri.parse('$host/album/$albumId/photo'));
|
||||||
|
expect(response.statusCode, 200);
|
||||||
|
expect(response.headers['content-type'], contains('application/json'));
|
||||||
|
final photos = jsonDecode(response.body) as List;
|
||||||
|
expect(photos, isA<List>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Photo endpoints', () {
|
||||||
|
test('GET /photo/<id> with invalid id returns 400', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/photo/abc'));
|
||||||
|
expect(response.statusCode, 400);
|
||||||
|
expect(response.body, contains('Invalid photo id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /photo/<id> with non-existent id returns 404', () async {
|
||||||
|
// Use a hash that won't match any file
|
||||||
|
final response = await http.get(Uri.parse('$host/photo/12345'));
|
||||||
|
expect(response.statusCode, 404);
|
||||||
|
expect(response.body, contains('Photo not found'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /photo/<id>/file with invalid id returns 400', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/photo/abc/file'));
|
||||||
|
expect(response.statusCode, 400);
|
||||||
|
expect(response.body, contains('Invalid photo id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /photo/<id>/file with non-existent id returns 404', () async {
|
||||||
|
// Use a hash that won't match any file
|
||||||
|
final response = await http.get(Uri.parse('$host/photo/12345/file'));
|
||||||
|
expect(response.statusCode, 404);
|
||||||
|
expect(response.body, contains('Photo not found'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /photo/<id>/file with valid id returns file', () async {
|
||||||
|
// First get a valid photo id from an album
|
||||||
|
final albumResponse = await http.get(Uri.parse('$host/album'));
|
||||||
|
expect(albumResponse.statusCode, 200);
|
||||||
|
final albums = jsonDecode(albumResponse.body) as List;
|
||||||
|
|
||||||
|
// Skip test if no albums exist
|
||||||
|
if (albums.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final album = albums.first as Map<String, dynamic>;
|
||||||
|
final albumId = album['id'];
|
||||||
|
|
||||||
|
final photosResponse = await http.get(
|
||||||
|
Uri.parse('$host/album/$albumId/photo'),
|
||||||
|
);
|
||||||
|
expect(photosResponse.statusCode, 200);
|
||||||
|
final photos = jsonDecode(photosResponse.body) as List;
|
||||||
|
|
||||||
|
// Skip test if no photos exist
|
||||||
|
if (photos.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final photo = photos.first as Map<String, dynamic>;
|
||||||
|
final photoId = photo['id'];
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse('$host/photo/$photoId/file'),
|
||||||
|
);
|
||||||
|
expect(response.statusCode, 200);
|
||||||
|
expect(response.contentLength, greaterThan(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('404 handling', () {
|
||||||
|
test('Unknown route returns 404', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/foobar'));
|
||||||
|
expect(response.statusCode, 404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Unknown nested route returns 404', () async {
|
||||||
|
final response = await http.get(Uri.parse('$host/api/v1/unknown'));
|
||||||
|
expect(response.statusCode, 404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
9
web/.env.example
Normal file
9
web/.env.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Drizzle
|
||||||
|
DATABASE_URL=local.db
|
||||||
|
|
||||||
|
ORIGIN=""
|
||||||
|
|
||||||
|
# Better Auth
|
||||||
|
# For production use 32 characters and generated with high entropy
|
||||||
|
# https://www.better-auth.com/docs/installation
|
||||||
|
BETTER_AUTH_SECRET=""
|
||||||
28
web/.gitignore
vendored
Normal file
28
web/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.wrangler
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
# Paraglide
|
||||||
|
src/lib/paraglide
|
||||||
|
project.inlang/cache/
|
||||||
|
# SQLite
|
||||||
|
*.db
|
||||||
1
web/.npmrc
Normal file
1
web/.npmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
engine-strict=true
|
||||||
10
web/.prettierignore
Normal file
10
web/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Package Managers
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
||||||
|
bun.lock
|
||||||
|
bun.lockb
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/static/
|
||||||
|
/drizzle/
|
||||||
15
web/.prettierrc
Normal file
15
web/.prettierrc
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
web/.vscode/extensions.json
vendored
Normal file
3
web/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["svelte.svelte-vscode", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
|
||||||
|
}
|
||||||
42
web/README.md
Normal file
42
web/README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# sv
|
||||||
|
|
||||||
|
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# create a new project
|
||||||
|
npx sv create my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
To recreate this project with the same configuration:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# recreate this project
|
||||||
|
pnpm dlx sv@0.12.6 create --template minimal --types jsdoc --add prettier vitest="usages:unit,component" eslint sveltekit-adapter="adapter:auto" devtools-json better-auth="demo:password" mdsvex paraglide="languageTags:en, zh+demo:yes" drizzle="database:sqlite+sqlite:better-sqlite3" --install pnpm web/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||||
11
web/drizzle.config.js
Normal file
11
web/drizzle.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
|
||||||
|
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: './src/lib/server/db/schema.js',
|
||||||
|
dialect: 'sqlite',
|
||||||
|
dbCredentials: { url: process.env.DATABASE_URL },
|
||||||
|
verbose: true,
|
||||||
|
strict: true
|
||||||
|
});
|
||||||
28
web/eslint.config.js
Normal file
28
web/eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { includeIgnoreFile } from '@eslint/compat';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import svelte from 'eslint-plugin-svelte';
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import globals from 'globals';
|
||||||
|
import svelteConfig from './svelte.config.js';
|
||||||
|
|
||||||
|
const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
[
|
||||||
|
includeIgnoreFile(gitignorePath),
|
||||||
|
js.configs.recommended,
|
||||||
|
svelte.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: { globals: { ...globals.browser, ...globals.node } }
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte', '**/*.svelte.js'],
|
||||||
|
languageOptions: { parserOptions: { svelteConfig } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
prettier,
|
||||||
|
svelte.configs.prettier
|
||||||
|
);
|
||||||
19
web/jsconfig.json
Normal file
19
web/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
}
|
||||||
|
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||||
|
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
|
}
|
||||||
4
web/messages/en.json
Normal file
4
web/messages/en.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
|
"hello_world": "Hello, {name} from en!"
|
||||||
|
}
|
||||||
4
web/messages/zh.json
Normal file
4
web/messages/zh.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
|
"hello_world": "Hello, {name} from zh!"
|
||||||
|
}
|
||||||
49
web/package.json
Normal file
49
web/package.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
||||||
|
"lint": "prettier --check . && eslint .",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"test": "npm run test:unit -- --run",
|
||||||
|
"auth:schema": "better-auth generate --config src/lib/server/auth.js --output src/lib/server/db/auth.schema.js --yes"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@better-auth/cli": "~1.4.21",
|
||||||
|
"@eslint/compat": "^2.0.2",
|
||||||
|
"@eslint/js": "^9.39.2",
|
||||||
|
"@inlang/paraglide-js": "^2.10.0",
|
||||||
|
"@sveltejs/adapter-auto": "^7.0.0",
|
||||||
|
"@sveltejs/kit": "^2.50.2",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||||
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
|
"@types/node": "^22",
|
||||||
|
"@vitest/browser-playwright": "^4.0.18",
|
||||||
|
"better-auth": "~1.4.21",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-svelte": "^3.14.0",
|
||||||
|
"globals": "^17.3.0",
|
||||||
|
"mdsvex": "^0.12.6",
|
||||||
|
"playwright": "^1.58.2",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"prettier-plugin-svelte": "^3.4.1",
|
||||||
|
"svelte": "^5.51.0",
|
||||||
|
"svelte-check": "^4.4.2",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vite": "^7.3.1",
|
||||||
|
"vite-plugin-devtools-json": "^1.0.0",
|
||||||
|
"vitest": "^4.0.18",
|
||||||
|
"vitest-browser-svelte": "^2.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
}
|
||||||
|
}
|
||||||
4711
web/pnpm-lock.yaml
generated
Normal file
4711
web/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
web/pnpm-workspace.yaml
Normal file
2
web/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
12
web/project.inlang/settings.json
Normal file
12
web/project.inlang/settings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://inlang.com/schema/project-settings",
|
||||||
|
"modules": [
|
||||||
|
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
|
||||||
|
],
|
||||||
|
"plugin.inlang.messageFormat": {
|
||||||
|
"pathPattern": "./messages/{locale}.json"
|
||||||
|
},
|
||||||
|
"baseLocale": "en",
|
||||||
|
"locales": ["en", "zh"]
|
||||||
|
}
|
||||||
19
web/src/app.d.ts
vendored
Normal file
19
web/src/app.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { User, Session } from 'better-auth/minimal';
|
||||||
|
|
||||||
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
interface Locals {
|
||||||
|
user?: User;
|
||||||
|
session?: Session;
|
||||||
|
}
|
||||||
|
|
||||||
|
// interface Error {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
15
web/src/app.html
Normal file
15
web/src/app.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html lang="%paraglide.lang%" dir="%paraglide.dir%">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
web/src/hooks.js
Normal file
3
web/src/hooks.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { deLocalizeUrl } from '$lib/paraglide/runtime';
|
||||||
|
|
||||||
|
export const reroute = (request) => deLocalizeUrl(request.url).pathname;
|
||||||
40
web/src/hooks.server.js
Normal file
40
web/src/hooks.server.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { sequence } from '@sveltejs/kit/hooks';
|
||||||
|
import { building } from '$app/environment';
|
||||||
|
import { auth } from '$lib/server/auth';
|
||||||
|
import { svelteKitHandler } from 'better-auth/svelte-kit';
|
||||||
|
import { getTextDirection } from '$lib/paraglide/runtime';
|
||||||
|
import { paraglideMiddleware } from '$lib/paraglide/server';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Handle} */ const handleParaglide = ({ event, resolve }) =>
|
||||||
|
paraglideMiddleware(event.request, ({ request, locale }) => {
|
||||||
|
event.request = request;
|
||||||
|
|
||||||
|
return resolve(event, {
|
||||||
|
transformPageChunk: ({ html }) =>
|
||||||
|
html
|
||||||
|
.replace('%paraglide.lang%', locale)
|
||||||
|
.replace('%paraglide.dir%', getTextDirection(locale))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Handle} */ const handleBetterAuth = async ({
|
||||||
|
event,
|
||||||
|
resolve
|
||||||
|
}) => {
|
||||||
|
const session = await auth.api.getSession({
|
||||||
|
/** @type {import('@sveltejs/kit').Handle} */ headers: event.request.headers
|
||||||
|
});
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
event.locals.session = session.session;
|
||||||
|
event.locals.user = session.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return svelteKitHandler({ event, resolve, auth, building });
|
||||||
|
};
|
||||||
|
|
||||||
|
export /** @type {import('@sveltejs/kit').Handle} */ const handle = sequence(
|
||||||
|
handleParaglide,
|
||||||
|
handleBetterAuth
|
||||||
|
);
|
||||||
|
/** @type {import('@sveltejs/kit').Handle} */
|
||||||
1
web/src/lib/assets/favicon.svg
Normal file
1
web/src/lib/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
1
web/src/lib/index.js
Normal file
1
web/src/lib/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// place files you want to import through the `$lib` alias in this folder.
|
||||||
13
web/src/lib/server/auth.js
Normal file
13
web/src/lib/server/auth.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { betterAuth } from 'better-auth/minimal';
|
||||||
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
||||||
|
import { sveltekitCookies } from 'better-auth/svelte-kit';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
import { getRequestEvent } from '$app/server';
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
baseURL: env.ORIGIN,
|
||||||
|
secret: env.BETTER_AUTH_SECRET,
|
||||||
|
// database: drizzleAdapter(db, { provider: 'sqlite' }),
|
||||||
|
emailAndPassword: { enabled: true },
|
||||||
|
plugins: [sveltekitCookies(getRequestEvent)] // make sure this is the last plugin in the array
|
||||||
|
});
|
||||||
1
web/src/lib/server/db/auth.schema.js
Normal file
1
web/src/lib/server/db/auth.schema.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// If you see this file, you have not run the auth:schema script yet, but you should!
|
||||||
10
web/src/lib/server/db/index.js
Normal file
10
web/src/lib/server/db/index.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||||
|
import Database from 'better-sqlite3';
|
||||||
|
import * as schema from './schema';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
|
if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||||
|
|
||||||
|
const client = new Database(env.DATABASE_URL);
|
||||||
|
|
||||||
|
export const db = drizzle(client, { schema });
|
||||||
11
web/src/lib/server/db/schema.js
Normal file
11
web/src/lib/server/db/schema.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||||
|
|
||||||
|
export const task = sqliteTable('task', {
|
||||||
|
id: text('id')
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => crypto.randomUUID()),
|
||||||
|
title: text('title').notNull(),
|
||||||
|
priority: integer('priority').notNull().default(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
export * from './auth.schema';
|
||||||
8
web/src/lib/vitest-examples/Welcome.svelte
Normal file
8
web/src/lib/vitest-examples/Welcome.svelte
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<script>
|
||||||
|
import { greet } from './greet';
|
||||||
|
|
||||||
|
let { host = 'SvelteKit', guest = 'Vitest' } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>{greet(host)}</h1>
|
||||||
|
<p>{greet(guest)}</p>
|
||||||
15
web/src/lib/vitest-examples/Welcome.svelte.spec.js
Normal file
15
web/src/lib/vitest-examples/Welcome.svelte.spec.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { page } from 'vitest/browser';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
import Welcome from './Welcome.svelte';
|
||||||
|
|
||||||
|
describe('Welcome.svelte', () => {
|
||||||
|
it('renders greetings for host and guest', async () => {
|
||||||
|
render(Welcome, { host: 'SvelteKit', guest: 'Vitest' });
|
||||||
|
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('heading', { level: 1 }))
|
||||||
|
.toHaveTextContent('Hello, SvelteKit!');
|
||||||
|
await expect.element(page.getByText('Hello, Vitest!')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
3
web/src/lib/vitest-examples/greet.js
Normal file
3
web/src/lib/vitest-examples/greet.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function greet(name) {
|
||||||
|
return 'Hello, ' + name + '!';
|
||||||
|
}
|
||||||
8
web/src/lib/vitest-examples/greet.spec.js
Normal file
8
web/src/lib/vitest-examples/greet.spec.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { greet } from './greet';
|
||||||
|
|
||||||
|
describe('greet', () => {
|
||||||
|
it('returns a greeting', () => {
|
||||||
|
expect(greet('Svelte')).toBe('Hello, Svelte!');
|
||||||
|
});
|
||||||
|
});
|
||||||
16
web/src/routes/+layout.svelte
Normal file
16
web/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script>
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { locales, localizeHref } from '$lib/paraglide/runtime';
|
||||||
|
import favicon from '$lib/assets/favicon.svg';
|
||||||
|
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
|
||||||
|
{@render children()}
|
||||||
|
|
||||||
|
<div style="display:none">
|
||||||
|
{#each locales as locale}
|
||||||
|
<a href={localizeHref(page.url.pathname, { locale })}>{locale}</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
2
web/src/routes/+page.svelte
Normal file
2
web/src/routes/+page.svelte
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<h1>Welcome to SvelteKit</h1>
|
||||||
|
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||||
6
web/src/routes/demo/+page.svelte
Normal file
6
web/src/routes/demo/+page.svelte
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a href={resolve('/demo/better-auth')}>better-auth</a>
|
||||||
|
<a href={resolve('/demo/paraglide')}>paraglide</a>
|
||||||
19
web/src/routes/demo/better-auth/+page.server.js
Normal file
19
web/src/routes/demo/better-auth/+page.server.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
import { auth } from '$lib/server/auth';
|
||||||
|
|
||||||
|
export const load = async (event) => {
|
||||||
|
if (!event.locals.user) {
|
||||||
|
return redirect(302, '/demo/better-auth/login');
|
||||||
|
}
|
||||||
|
return { user: event.locals.user };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
signOut: async (event) => {
|
||||||
|
await auth.api.signOut({
|
||||||
|
headers: event.request.headers
|
||||||
|
});
|
||||||
|
return redirect(302, '/demo/better-auth/login');
|
||||||
|
}
|
||||||
|
};
|
||||||
11
web/src/routes/demo/better-auth/+page.svelte
Normal file
11
web/src/routes/demo/better-auth/+page.svelte
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Hi, {data.user.name}!</h1>
|
||||||
|
<p>Your user ID is {data.user.id}.</p>
|
||||||
|
<form method="post" action="?/signOut" use:enhance>
|
||||||
|
<button>Sign out</button>
|
||||||
|
</form>
|
||||||
60
web/src/routes/demo/better-auth/login/+page.server.js
Normal file
60
web/src/routes/demo/better-auth/login/+page.server.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
import { auth } from '$lib/server/auth';
|
||||||
|
import { APIError } from 'better-auth/api';
|
||||||
|
|
||||||
|
export const load = async (event) => {
|
||||||
|
if (event.locals.user) {
|
||||||
|
return redirect(302, '/demo/better-auth');
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
signInEmail: async (event) => {
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const email = formData.get('email')?.toString() ?? '';
|
||||||
|
const password = formData.get('password')?.toString() ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await auth.api.signInEmail({
|
||||||
|
body: {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
callbackURL: '/auth/verification-success'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof APIError) {
|
||||||
|
return fail(400, { message: error.message || 'Signin failed' });
|
||||||
|
}
|
||||||
|
return fail(500, { message: 'Unexpected error' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(302, '/demo/better-auth');
|
||||||
|
},
|
||||||
|
signUpEmail: async (event) => {
|
||||||
|
const formData = await event.request.formData();
|
||||||
|
const email = formData.get('email')?.toString() ?? '';
|
||||||
|
const password = formData.get('password')?.toString() ?? '';
|
||||||
|
const name = formData.get('name')?.toString() ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await auth.api.signUpEmail({
|
||||||
|
body: {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
name,
|
||||||
|
callbackURL: '/auth/verification-success'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof APIError) {
|
||||||
|
return fail(400, { message: error.message || 'Registration failed' });
|
||||||
|
}
|
||||||
|
return fail(500, { message: 'Unexpected error' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(302, '/demo/better-auth');
|
||||||
|
}
|
||||||
|
};
|
||||||
24
web/src/routes/demo/better-auth/login/+page.svelte
Normal file
24
web/src/routes/demo/better-auth/login/+page.svelte
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
|
||||||
|
let { form } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Login</h1>
|
||||||
|
<form method="post" action="?/signInEmail" use:enhance>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input type="email" name="email" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password
|
||||||
|
<input type="password" name="password" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Name (for registration)
|
||||||
|
<input name="name" />
|
||||||
|
</label>
|
||||||
|
<button>Login</button>
|
||||||
|
<button formaction="?/signUpEmail">Register</button>
|
||||||
|
</form>
|
||||||
|
<p style="color: red">{form?.message ?? ''}</p>
|
||||||
22
web/src/routes/demo/paraglide/+page.svelte
Normal file
22
web/src/routes/demo/paraglide/+page.svelte
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script>
|
||||||
|
import { setLocale } from '$lib/paraglide/runtime';
|
||||||
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button onclick={() => setLocale('en')}>en</button>
|
||||||
|
<button onclick={() => setLocale('zh')}>zh</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you use VSCode, install the
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://marketplace.visualstudio.com/items?itemName=inlang.vs-code-extension"
|
||||||
|
target="_blank">Sherlock i18n extension</a
|
||||||
|
>
|
||||||
|
|
||||||
|
for a better i18n experience.
|
||||||
|
</p>
|
||||||
3
web/static/robots.txt
Normal file
3
web/static/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# allow crawling everything by default
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
19
web/svelte.config.js
Normal file
19
web/svelte.config.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { mdsvex } from 'mdsvex';
|
||||||
|
import adapter from '@sveltejs/adapter-auto';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
|
adapter: adapter()
|
||||||
|
},
|
||||||
|
vitePlugin: {
|
||||||
|
dynamicCompileOptions: ({ filename }) => ({ runes: !filename.includes('node_modules') })
|
||||||
|
},
|
||||||
|
preprocess: [mdsvex()],
|
||||||
|
extensions: ['.svelte', '.svx']
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
41
web/vite.config.js
Normal file
41
web/vite.config.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { paraglideVitePlugin } from '@inlang/paraglide-js';
|
||||||
|
import devtoolsJson from 'vite-plugin-devtools-json';
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
import { playwright } from '@vitest/browser-playwright';
|
||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
sveltekit(),
|
||||||
|
devtoolsJson(),
|
||||||
|
paraglideVitePlugin({ project: './project.inlang', outdir: './src/lib/paraglide' })
|
||||||
|
],
|
||||||
|
test: {
|
||||||
|
expect: { requireAssertions: true },
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
extends: './vite.config.js',
|
||||||
|
test: {
|
||||||
|
name: 'client',
|
||||||
|
browser: {
|
||||||
|
enabled: true,
|
||||||
|
provider: playwright(),
|
||||||
|
instances: [{ browser: 'chromium', headless: true }]
|
||||||
|
},
|
||||||
|
include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||||
|
exclude: ['src/lib/server/**']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
extends: './vite.config.js',
|
||||||
|
test: {
|
||||||
|
name: 'server',
|
||||||
|
environment: 'node',
|
||||||
|
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||||
|
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user