First import
This commit is contained in:
46 files changed
+1341
No files matched your search
@@ -0,0 +1,32 @@
|
||||
name: Build Template
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PROJECT_NAME: testgen
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Run cargo generate
|
||||
uses: cargo-generate/cargo-generate-action@v0.13.0
|
||||
with:
|
||||
name: ${{ env.PROJECT_NAME }}
|
||||
subfolder: template
|
||||
template_values_file: .github/workflows/template_values.toml
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Cargo check
|
||||
run: |
|
||||
mv $PROJECT_NAME ${{ runner.temp }}/
|
||||
cd ${{ runner.temp }}/$PROJECT_NAME
|
||||
cargo check --tests --all-features --workspace
|
||||
@@ -0,0 +1,2 @@
|
||||
[values]
|
||||
gh-username = "dax"
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 David Rousselie
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Actix API + Yew application Template
|
||||
|
||||
[](https://www.apache.org/licenses/)
|
||||
[](https://github.com/dax/actix-yew-app-template/actions)
|
||||
|
||||
A [cargo generate](https://github.com/cargo-generate/cargo-generate) template for web application:
|
||||
- backend is based on [Actix](https://actix.rs)
|
||||
- frontend use [Yew](https://yew.rs)
|
||||
@@ -0,0 +1,7 @@
|
||||
.git
|
||||
.github
|
||||
target/
|
||||
tests/
|
||||
Dockerfile
|
||||
web/dist
|
||||
api/config/local.*
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
name: Security audit
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
|
||||
jobs:
|
||||
security_audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- uses: actions-rs/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
rustfmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: rustfmt
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
- name: Install Wasm Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
target: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
sharedKey: ci
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --all-features --workspace
|
||||
|
||||
test:
|
||||
needs: build
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
sharedKey: ci
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features --workspace
|
||||
|
||||
clippy:
|
||||
needs: build
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
with:
|
||||
sharedKey: ci
|
||||
- name: Clippy check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --all-features --workspace -- -D warnings
|
||||
|
||||
coverage:
|
||||
name: Code coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Run cargo-tarpaulin
|
||||
uses: actions-rs/tarpaulin@v0.1
|
||||
with:
|
||||
args: '--all-features --workspace --ignore-tests --out Lcov'
|
||||
- name: Upload to Coveralls
|
||||
# upload only if push
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: './lcov.info'
|
||||
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
api/config/local.*
|
||||
web/dist
|
||||
@@ -0,0 +1,23 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: format
|
||||
name: format
|
||||
language: system
|
||||
pass_filenames: false
|
||||
entry: cargo make format-flow
|
||||
- id: format-toml
|
||||
name: format-toml
|
||||
language: system
|
||||
pass_filenames: false
|
||||
entry: cargo make format-toml-flow
|
||||
- id: check
|
||||
name: check
|
||||
language: system
|
||||
pass_filenames: false
|
||||
entry: cargo make check-tests
|
||||
- id: clippy
|
||||
name: clippy
|
||||
language: system
|
||||
pass_filenames: false
|
||||
entry: cargo make clippy-flow
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "{{project-name}}"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["{{authors}}"]
|
||||
|
||||
[workspace]
|
||||
members = ["api", "web"]
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "0.8.0", features = ["serde"] }
|
||||
chrono = { version = "0.4.0", features = ["serde"] }
|
||||
@@ -0,0 +1,31 @@
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1.57.0 as chef
|
||||
WORKDIR /app
|
||||
|
||||
FROM chef as planner
|
||||
COPY . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
|
||||
FROM chef as dep-builder
|
||||
RUN cargo install cargo-make
|
||||
RUN cargo install trunk
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
RUN cargo chef cook -p {{project-name}} --release --recipe-path recipe.json
|
||||
RUN cargo chef cook -p {{project-name}}-api --release --recipe-path recipe.json
|
||||
RUN cargo chef cook -p {{project-name}}-web --release --recipe-path recipe.json --target wasm32-unknown-unknown
|
||||
|
||||
FROM dep-builder as release-builder
|
||||
COPY . .
|
||||
RUN cargo make build-release
|
||||
RUN sed -i 's#http://localhost:8000/api#/api#' web/dist/snippets/{{project-name}}-web-*/js/api.js
|
||||
|
||||
FROM debian:bullseye-slim AS runtime
|
||||
WORKDIR /app
|
||||
RUN mkdir /data
|
||||
COPY --from=release-builder /app/target/release/{{project-name}}-api {{project-name}}
|
||||
COPY --from=release-builder /app/api/config/default.toml config/default.toml
|
||||
COPY --from=release-builder /app/web/dist/ .
|
||||
ENV {{crate_name | upcase}}_APPLICATION.API_PATH /api
|
||||
ENV {{crate_name | upcase}}_APPLICATION.STATIC_PATH /
|
||||
CMD ["/app/{{project-name}}"]
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 David Rousselie
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,57 @@
|
||||
[env]
|
||||
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = "true"
|
||||
CARGO_MAKE_COVERAGE_PROVIDER = "tarpaulin"
|
||||
CARGO_MAKE_CLIPPY_ARGS = "--tests -- -D warnings"
|
||||
|
||||
[tasks.default]
|
||||
clear = true
|
||||
alias = "watch"
|
||||
|
||||
[tasks.test]
|
||||
install_crate = "cargo-nextest"
|
||||
args = [
|
||||
"nextest",
|
||||
"run",
|
||||
"@@remove-empty(CARGO_MAKE_CARGO_VERBOSE_FLAGS)",
|
||||
"@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )",
|
||||
]
|
||||
|
||||
[tasks.audit]
|
||||
condition = {}
|
||||
workspace = false
|
||||
|
||||
[tasks.run-api]
|
||||
command = "bash"
|
||||
args = ["-c", "cd api; cargo make run"]
|
||||
workspace = false
|
||||
watch = { watch = ["./api/"], no_git_ignore = true }
|
||||
|
||||
[tasks.run-web]
|
||||
command = "bash"
|
||||
args = ["-c", "cd web; cargo make run"]
|
||||
workspace = false
|
||||
|
||||
[tasks.run]
|
||||
run_task = { name = ["run-api", "run-web"], parallel = true, fork = true }
|
||||
workspace = false
|
||||
|
||||
[tasks.watch-api]
|
||||
command = "bash"
|
||||
args = ["-c", "cd api; cargo make dev-test-flow"]
|
||||
workspace = false
|
||||
watch = { watch = ["./api/"], no_git_ignore = true }
|
||||
|
||||
[tasks.watch-web]
|
||||
command = "bash"
|
||||
args = ["-c", "cd web; cargo make dev-test-flow"]
|
||||
workspace = false
|
||||
watch = { watch = ["./web/"], no_git_ignore = true }
|
||||
|
||||
[tasks.watch-root]
|
||||
watch = { watch = ["./src/"], no_git_ignore = true }
|
||||
run_task = "dev-test-flow"
|
||||
workspace = false
|
||||
|
||||
[tasks.watch]
|
||||
run_task = { name = ["watch-api", "watch-web", "watch-root"], parallel = true, fork = true }
|
||||
workspace = false
|
||||
@@ -0,0 +1,73 @@
|
||||
# {{project-name | capitalize}}
|
||||
|
||||
[](http://www.gnu.org/licenses/agpl-3.0)
|
||||
[](https://coveralls.io/github/{{gh-username}}/{{project-name}}?branch=main)
|
||||
[](https://github.com/{{gh-username}}/{{project-name}}/actions)
|
||||
|
||||
{{project-name}} is ...
|
||||
|
||||
## Features
|
||||
|
||||
- [ ]
|
||||
|
||||
## Installation
|
||||
|
||||
### Using cargo (for development)
|
||||
|
||||
```bash
|
||||
cargo make run
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
1. Get the code
|
||||
|
||||
```bash
|
||||
git clone https://github.com/{{gh-username}}/{{project-name}}
|
||||
```
|
||||
|
||||
2. Build api and web release assets
|
||||
|
||||
```bash
|
||||
cargo make build-release
|
||||
```
|
||||
|
||||
It will produce a `target/release/{{project-name}}-api` backend binary and frontend assets in the `web/dist` directory.
|
||||
|
||||
3. Deploy assets
|
||||
|
||||
```bash
|
||||
mkdir -p $DEPLOY_DIR/config
|
||||
cp -a target/release/{{project-name}}-api $DEPLOY_DIR
|
||||
cp -a web/dist/* $DEPLOY_DIR
|
||||
cp -a api/config/{default.toml, prod.toml} $DEPLOY_DIR/config
|
||||
```
|
||||
|
||||
4. Run server
|
||||
|
||||
```bash
|
||||
cd $DEPLOY_DIR
|
||||
env CONFIG_FILE=$DEPLOY_DIR/config/prod.toml ./{{project-name}}-api
|
||||
```
|
||||
|
||||
### Using Docker
|
||||
|
||||
#### Build Docker image
|
||||
|
||||
```bash
|
||||
docker build -t {{project-name}} .
|
||||
```
|
||||
|
||||
#### Run {{project-name | capitalize}} using Docker
|
||||
|
||||
```bash
|
||||
docker run --rm -ti -p 8000:8000 project-name
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Access {{project-name | capitalize}} using [http://localhost:8000](http://localhost:8000)
|
||||
|
||||
## License
|
||||
|
||||
[AGPL](LICENSE)
|
||||
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "{{project-name}}-api"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["{{authors}}"]
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
path = "src/main.rs"
|
||||
name = "{{project-name}}-api"
|
||||
|
||||
[dependencies]
|
||||
{{project-name}} = { path = ".." }
|
||||
actix-web = "4.0.0"
|
||||
actix-http = "3.0.0"
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
serde = { version = "1.0.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "0.8.0", features = ["serde"] }
|
||||
chrono = { version = "0.4.0", features = ["serde"] }
|
||||
mktemp = "0.4.0"
|
||||
configparser = "3.0.0"
|
||||
tracing = { version = "0.1.0", features = ["log"] }
|
||||
tracing-subscriber = { version = "0.3.0", features = [
|
||||
"std",
|
||||
"env-filter",
|
||||
"fmt",
|
||||
"json",
|
||||
] }
|
||||
tracing-log = "0.1.0"
|
||||
tracing-actix-web = "0.5.0"
|
||||
regex = "1.5.0"
|
||||
lazy_static = "1.4.0"
|
||||
tracing-bunyan-formatter = "0.3.0"
|
||||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
http = "0.2.0"
|
||||
config = "0.12.0"
|
||||
actix-files = "0.6.0"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1.0.0"
|
||||
reqwest = { version = "0.11.0", features = ["json"] }
|
||||
rstest = "0.12.0"
|
||||
@@ -0,0 +1,8 @@
|
||||
extend = "../Makefile.toml"
|
||||
|
||||
[tasks.run]
|
||||
clear = true
|
||||
install_crate = { crate_name = "bunyan", binary = "bunyan" }
|
||||
env = { "TASKRC" = "$PWD/taskrc" }
|
||||
command = "bash"
|
||||
args = ["-c", "cargo run | bunyan"]
|
||||
@@ -0,0 +1,6 @@
|
||||
[application]
|
||||
port = 8000
|
||||
# See https://docs.rs/tracing-subscriber/latest/tracing_subscriber/struct.EnvFilter.html
|
||||
log_directive = "info"
|
||||
api_path = ""
|
||||
front_base_url = "http://localhost:8080"
|
||||
@@ -0,0 +1,5 @@
|
||||
[application]
|
||||
log_directive = "debug"
|
||||
static_dir = "../web/dist"
|
||||
api_path = "/api"
|
||||
static_path = ""
|
||||
@@ -0,0 +1,4 @@
|
||||
[application]
|
||||
static_dir = "."
|
||||
api_path = "/api"
|
||||
static_path = ""
|
||||
@@ -0,0 +1,2 @@
|
||||
[application]
|
||||
log_directive = "debug"
|
||||
@@ -0,0 +1,48 @@
|
||||
use config::{Config, ConfigError, Environment, File};
|
||||
use serde::Deserialize;
|
||||
use std::env;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Settings {
|
||||
pub application: ApplicationSettings,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ApplicationSettings {
|
||||
pub port: u16,
|
||||
pub log_directive: String,
|
||||
pub front_base_url: String,
|
||||
pub api_path: String,
|
||||
pub static_path: Option<String>,
|
||||
pub static_dir: Option<String>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn new_from_file(file: Option<String>) -> Result<Self, ConfigError> {
|
||||
let config_file_required = file.is_some();
|
||||
let config_path = env::var("CONFIG_PATH").unwrap_or_else(|_| "config".into());
|
||||
let config_file = file.unwrap_or_else(|| {
|
||||
env::var("CONFIG_FILE").unwrap_or_else(|_| format!("{}/dev", &config_path))
|
||||
});
|
||||
|
||||
let default_config_file = format!("{}/default", config_path);
|
||||
let local_config_file = format!("{}/local", config_path);
|
||||
println!(
|
||||
"Trying to load {:?} config files",
|
||||
vec![&default_config_file, &local_config_file, &config_file]
|
||||
);
|
||||
|
||||
let config = Config::builder()
|
||||
.add_source(File::with_name(&default_config_file))
|
||||
.add_source(File::with_name(&local_config_file).required(false))
|
||||
.add_source(File::with_name(&config_file).required(config_file_required))
|
||||
.add_source(Environment::with_prefix("{{crate_name}}"))
|
||||
.build()?;
|
||||
|
||||
config.try_deserialize()
|
||||
}
|
||||
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
Settings::new_from_file(None)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use actix_files as fs;
|
||||
use actix_web::{dev::Server, http, middleware, web, App, HttpServer};
|
||||
use configuration::Settings;
|
||||
use core::time::Duration;
|
||||
use std::net::TcpListener;
|
||||
use tracing::info;
|
||||
use tracing_actix_web::TracingLogger;
|
||||
|
||||
pub mod configuration;
|
||||
pub mod {{crate_name}};
|
||||
pub mod observability;
|
||||
pub mod routes;
|
||||
|
||||
pub fn run(listener: TcpListener, settings: &Settings) -> Result<Server, std::io::Error> {
|
||||
let api_path = settings.application.api_path.clone();
|
||||
let front_base_url = settings.application.front_base_url.clone();
|
||||
let static_path = settings.application.static_path.clone();
|
||||
let static_dir = settings
|
||||
.application
|
||||
.static_dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| ".".to_string());
|
||||
|
||||
let server = HttpServer::new(move || {
|
||||
info!(
|
||||
"Mounting API on {}",
|
||||
if api_path.is_empty() { "/" } else { &api_path }
|
||||
);
|
||||
let api_scope = web::scope(&api_path)
|
||||
.wrap(
|
||||
middleware::DefaultHeaders::new()
|
||||
.add(("Access-Control-Allow-Origin", front_base_url.as_bytes()))
|
||||
.add((
|
||||
"Access-Control-Allow-Methods",
|
||||
"POST, GET, OPTIONS".as_bytes(),
|
||||
))
|
||||
.add(("Access-Control-Allow-Headers", "content-type".as_bytes())),
|
||||
)
|
||||
.route("/hello", web::get().to(routes::hello))
|
||||
.route(
|
||||
"/TODO*",
|
||||
web::method(http::Method::OPTIONS).to(routes::option_wildcard),
|
||||
);
|
||||
|
||||
let mut app = App::new()
|
||||
.wrap(TracingLogger::default())
|
||||
.wrap(middleware::Compress::default())
|
||||
.route("/ping", web::get().to(routes::ping))
|
||||
.service(api_scope);
|
||||
if let Some(path) = &static_path {
|
||||
info!(
|
||||
"Mounting static files on {}",
|
||||
if path.is_empty() { "/" } else { &path }
|
||||
);
|
||||
let static_scope = fs::Files::new(path, &static_dir)
|
||||
.use_last_modified(true)
|
||||
.index_file("index.html");
|
||||
app = app.service(static_scope);
|
||||
}
|
||||
app
|
||||
})
|
||||
.keep_alive(http::KeepAlive::Timeout(Duration::from_secs(60)))
|
||||
.shutdown_timeout(60)
|
||||
.listen(listener)?;
|
||||
|
||||
Ok(server.run())
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use {{crate_name}}_api::configuration::Settings;
|
||||
use {{crate_name}}_api::observability::{get_subscriber, init_subscriber};
|
||||
use {{crate_name}}_api::run;
|
||||
use std::net::TcpListener;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let settings = Settings::new().expect("Cannot load {{project-name | capitalize}} configuration");
|
||||
let subscriber = get_subscriber(&settings.application.log_directive);
|
||||
init_subscriber(subscriber);
|
||||
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", settings.application.port))
|
||||
.expect("Failed to bind port");
|
||||
run(listener, &settings)?.await
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use tracing::{subscriber::set_global_default, Subscriber};
|
||||
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::fmt::TestWriter;
|
||||
use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
|
||||
|
||||
pub fn get_subscriber(env_filter_str: &str) -> impl Subscriber + Send + Sync {
|
||||
let formatting_layer =
|
||||
BunyanFormattingLayer::new("{{project-name}}-api".into(), TestWriter::new);
|
||||
|
||||
let env_filter =
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(env_filter_str));
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(env_filter)
|
||||
.with(JsonStorageLayer)
|
||||
.with(formatting_layer)
|
||||
}
|
||||
|
||||
pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) {
|
||||
LogTracer::init().expect("Failed to set logger");
|
||||
set_global_default(subscriber).expect("Failed to set subscriber");
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use crate::{{crate_name}};
|
||||
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
|
||||
use anyhow::Context;
|
||||
use ::{{crate_name}}::Object;
|
||||
|
||||
impl ResponseError for {{crate_name}}::{{project-name | capitalize}}Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
{{crate_name}}::{{project-name | capitalize}}Error::InvalidDataError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
{{crate_name}}::{{project-name | capitalize}}Error::UnexpectedError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub async fn hello() -> Result<HttpResponse, {{crate_name}}::{{project-name | capitalize}}Error> {
|
||||
let object: Object = {{crate_name}}::build_object()?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(serde_json::to_string(&object).context("Cannot serialize object")?))
|
||||
}
|
||||
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub async fn option_wildcard() -> HttpResponse {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
use actix_web::HttpResponse;
|
||||
|
||||
pub async fn ping() -> HttpResponse {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod default;
|
||||
mod health_check;
|
||||
|
||||
pub use default::*;
|
||||
pub use health_check::*;
|
||||
@@ -0,0 +1,40 @@
|
||||
use chrono::Utc;
|
||||
use serde_json;
|
||||
use {{crate_name}}::Object;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn error_chain_fmt(
|
||||
e: &impl std::error::Error,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
writeln!(f, "{}\n", e)?;
|
||||
let mut current = e.source();
|
||||
while let Some(cause) = current {
|
||||
writeln!(f, "Caused by:\n\t{}", cause)?;
|
||||
current = cause.source();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for {{project-name | capitalize}}Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
error_chain_fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error)]
|
||||
pub enum {{project-name | capitalize}}Error {
|
||||
#[error("Invalid {{project-name | capitalize}} data")]
|
||||
InvalidDataError(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
UnexpectedError(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn build_object() -> Result<Object, {{project-name | capitalize}}Error> {
|
||||
Ok(Object {
|
||||
name: "test".to_string(),
|
||||
id: Uuid::new_v4(),
|
||||
modified: Utc::now(),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod api;
|
||||
|
||||
pub use api::*;
|
||||
@@ -0,0 +1,22 @@
|
||||
use crate::helpers::app_address;
|
||||
use ::{{crate_name}}::Object;
|
||||
use rstest::*;
|
||||
|
||||
mod get_object {
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_hello(app_address: &str) {
|
||||
let object: Object = reqwest::Client::new()
|
||||
.get(&format!("{}/hello", &app_address))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to execute request")
|
||||
.json()
|
||||
.await
|
||||
.expect("Cannot parse JSON result");
|
||||
|
||||
assert_eq!(object.name, "test");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use crate::helpers::app_address;
|
||||
use rstest::*;
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn health_check_works(app_address: &str) {
|
||||
let response = reqwest::Client::new()
|
||||
.get(&format!("{}/ping", &app_address))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to execute request.");
|
||||
|
||||
assert!(response.status().is_success());
|
||||
assert_eq!(Some(0), response.content_length());
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use {{crate_name}}_api::configuration::Settings;
|
||||
use {{crate_name}}_api::observability::{get_subscriber, init_subscriber};
|
||||
use rstest::*;
|
||||
use std::net::TcpListener;
|
||||
use tracing::info;
|
||||
|
||||
fn setup_tracing(settings: &Settings) {
|
||||
info!("Setting up tracing");
|
||||
let subscriber = get_subscriber(&settings.application.log_directive);
|
||||
init_subscriber(subscriber);
|
||||
}
|
||||
|
||||
fn setup_server(settings: &Settings) -> String {
|
||||
info!("Setting up server");
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
|
||||
let server = {{crate_name}}_api::run(listener, settings).expect("Failed to bind address");
|
||||
let _ = tokio::spawn(server);
|
||||
format!("http://127.0.0.1:{}", port)
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
#[once]
|
||||
pub fn app_address() -> String {
|
||||
let settings = Settings::new_from_file(Some("config/test".to_string()))
|
||||
.expect("Cannot load test configuration");
|
||||
setup_tracing(&settings);
|
||||
let address = setup_server(&settings);
|
||||
address
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod default;
|
||||
mod health_check;
|
||||
mod helpers;
|
||||
@@ -0,0 +1,6 @@
|
||||
[template]
|
||||
cargo_generate_version = ">=0.13.0"
|
||||
|
||||
[placeholders.gh-username]
|
||||
type = "string"
|
||||
prompt = "GitHub username"
|
||||
@@ -0,0 +1,35 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq)]
|
||||
pub struct Object {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
#[serde(with = "tw_date_format")]
|
||||
pub modified: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub mod tw_date_format {
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||
|
||||
const FORMAT: &str = "%Y%m%dT%H%M%SZ";
|
||||
|
||||
pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let s = format!("{}", date.format(FORMAT));
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Utc.datetime_from_str(&s, FORMAT)
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "{{project-name}}-web"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["{{authors}}"]
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
path = "src/main.rs"
|
||||
name = "{{project-name}}-web"
|
||||
|
||||
[dependencies]
|
||||
{{project-name}} = { path = ".." }
|
||||
yew = "0.19"
|
||||
reqwasm = "0.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
uikit-rs = { git = "https://github.com/dax/uikit-rs.git" }
|
||||
wasm-bindgen = "0.2.79"
|
||||
@@ -0,0 +1,12 @@
|
||||
extend = "../Makefile.toml"
|
||||
|
||||
[tasks.build-release]
|
||||
install_crate = { crate_name = "trunk", binary = "trunk" }
|
||||
command = "trunk"
|
||||
args = ["build", "--release"]
|
||||
|
||||
[tasks.run]
|
||||
clear = true
|
||||
install_crate = { crate_name = "trunk", binary = "trunk" }
|
||||
command = "trunk"
|
||||
args = ["serve"]
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/*! UIkit 3.11.1 | https://www.getuikit.com | (c) 2014 - 2022 YOOtheme | MIT License */html{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:16px;font-weight:400;line-height:1.5;-webkit-text-size-adjust:100%;background:#fff;color:#666}body{margin:0}.uk-link,a{color:#1e87f0;text-decoration:none;cursor:pointer}.uk-link-toggle:hover .uk-link,.uk-link:hover,a:hover{color:#0f6ecd;text-decoration:underline}abbr[title]{text-decoration:underline dotted;-webkit-text-decoration-style:dotted}b,strong{font-weight:bolder}:not(pre)>code,:not(pre)>kbd,:not(pre)>samp{font-family:Consolas,monaco,monospace;font-size:.875rem;color:#f0506e;white-space:nowrap;padding:2px 6px;background:#f8f8f8}em{color:#f0506e}ins{background:#ffd;color:#666;text-decoration:none}mark{background:#ffd;color:#666}q{font-style:italic}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}audio,canvas,iframe,img,svg,video{vertical-align:middle}canvas,img,video{max-width:100%;height:auto;box-sizing:border-box}@supports (display:block){svg{max-width:100%;height:auto;box-sizing:border-box}}svg:not(:root){overflow:hidden}img:not([src]){min-width:1px;visibility:hidden}iframe{border:0}address,dl,fieldset,figure,ol,p,pre,ul{margin:0 0 20px 0}*+address,*+dl,*+fieldset,*+figure,*+ol,*+p,*+pre,*+ul{margin-top:20px}.uk-h1,.uk-h2,.uk-h3,.uk-h4,.uk-h5,.uk-h6,.uk-heading-2xlarge,.uk-heading-large,.uk-heading-medium,.uk-heading-small,.uk-heading-xlarge,h1,h2,h3,h4,h5,h6{margin:0 0 20px 0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-weight:400;color:#333;text-transform:none}*+.uk-h1,*+.uk-h2,*+.uk-h3,*+.uk-h4,*+.uk-h5,*+.uk-h6,*+.uk-heading-2xlarge,*+.uk-heading-large,*+.uk-heading-medium,*+.uk-heading-small,*+.uk-heading-xlarge,*+h1,*+h2,*+h3,*+h4,*+h5,*+h6{margin-top:40px}.uk-h1,h1{font-size:2.23125rem;line-height:1.2}.uk-h2,h2{font-size:1.7rem;line-height:1.3}.uk-h3,h3{font-size:1.5rem;line-height:1.4}.uk-h4,h4{font-size:1.25rem;line-height:1.4}.uk-h5,h5{font-size:16px;line-height:1.4}.uk-h6,h6{font-size:.875rem;line-height:1.4}@media (min-width:960px){.uk-h1,h1{font-size:2.625rem}.uk-h2,h2{font-size:2rem}}ol,ul{padding-left:30px}ol>li>ol,ol>li>ul,ul>li>ol,ul>li>ul{margin:0}dt{font-weight:700}dd{margin-left:0}.uk-hr,hr{overflow:visible;text-align:inherit;margin:0 0 20px 0;border:0;border-top:1px solid #e5e5e5}*+.uk-hr,*+hr{margin-top:20px}address{font-style:normal}blockquote{margin:0 0 20px 0;font-size:1.25rem;line-height:1.5;font-style:italic;color:#333}*+blockquote{margin-top:20px}blockquote p:last-of-type{margin-bottom:0}blockquote footer{margin-top:10px;font-size:.875rem;line-height:1.5;color:#666}blockquote footer::before{content:"— "}pre{font:0.875rem/1.5 Consolas,monaco,monospace;color:#666;-moz-tab-size:4;tab-size:4;overflow:auto;padding:10px;border:1px solid #e5e5e5;border-radius:3px;background:#fff}pre code{font-family:Consolas,monaco,monospace}:focus{outline:0}:focus:not([tabindex^='-']){outline:2px dotted #333;outline-offset:1px}:focus:not(:focus-visible){outline:0}:focus-visible{outline:2px dotted #333}::selection{background:#39f;color:#fff;text-shadow:none}details,main{display:block}summary{display:list-item}template{display:none}.uk-breakpoint-s::before{content:'640px'}.uk-breakpoint-m::before{content:'960px'}.uk-breakpoint-l::before{content:'1200px'}.uk-breakpoint-xl::before{content:'1600px'}:root{--uk-breakpoint-s:640px;--uk-breakpoint-m:960px;--uk-breakpoint-l:1200px;--uk-breakpoint-xl:1600px}.uk-link-muted a,.uk-link-toggle .uk-link-muted,a.uk-link-muted{color:#999}.uk-link-muted a:hover,.uk-link-toggle:hover .uk-link-muted,a.uk-link-muted:hover{color:#666}.uk-link-text a,.uk-link-toggle .uk-link-text,a.uk-link-text{color:inherit}.uk-link-text a:hover,.uk-link-toggle:hover .uk-link-text,a.uk-link-text:hover{color:#999}.uk-link-heading a,.uk-link-toggle .uk-link-heading,a.uk-link-heading{color:inherit}.uk-link-heading a:hover,.uk-link-toggle:hover .uk-link-heading,a.uk-link-heading:hover{color:#1e87f0;text-decoration:none}.uk-link-reset a,a.uk-link-reset{color:inherit!important;text-decoration:none!important}.uk-link-toggle{color:inherit!important;text-decoration:none!important}.uk-heading-small{font-size:2.6rem;line-height:1.2}.uk-heading-medium{font-size:2.8875rem;line-height:1.1}.uk-heading-large{font-size:3.4rem;line-height:1.1}.uk-heading-xlarge{font-size:4rem;line-height:1}.uk-heading-2xlarge{font-size:6rem;line-height:1}@media (min-width:960px){.uk-heading-small{font-size:3.25rem}.uk-heading-medium{font-size:3.5rem}.uk-heading-large{font-size:4rem}.uk-heading-xlarge{font-size:6rem}.uk-heading-2xlarge{font-size:8rem}}@media (min-width:1200px){.uk-heading-medium{font-size:4rem}.uk-hLine truncated
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{{project-name | capitalize}}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="css/uikit.min.css" />
|
||||
<script src="js/uikit.min.js"></script>
|
||||
<script src="js/uikit-icons.min.js"></script>
|
||||
<link data-trunk rel="copy-dir" href="css" />
|
||||
<link data-trunk rel="copy-dir" href="js" />
|
||||
</head>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
export function get_api_base_url() {
|
||||
return "http://localhost:8000/api";
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/*! UIkit 3.11.1 | https://www.getuikit.com | (c) 2014 - 2022 YOOtheme | MIT License */(function(t,i){typeof exports=="object"&&typeof module<"u"?module.exports=i():typeof define=="function"&&define.amd?define("uikiticons",i):(t=typeof globalThis<"u"?globalThis:t||self,t.UIkitIcons=i())})(this,function(){"use strict";function t(i){t.installed||i.icon.add({"500px":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M9.624,11.866c-0.141,0.132,0.479,0.658,0.662,0.418c0.051-0.046,0.607-0.61,0.662-0.664c0,0,0.738,0.719,0.814,0.719 c0.1,0,0.207-0.055,0.322-0.17c0.27-0.269,0.135-0.416,0.066-0.495l-0.631-0.616l0.658-0.668c0.146-0.156,0.021-0.314-0.1-0.449 c-0.182-0.18-0.359-0.226-0.471-0.125l-0.656,0.654l-0.654-0.654c-0.033-0.034-0.08-0.045-0.124-0.045 c-0.079,0-0.191,0.068-0.307,0.181c-0.202,0.202-0.247,0.351-0.133,0.462l0.665,0.665L9.624,11.866z"/><path d="M11.066,2.884c-1.061,0-2.185,0.248-3.011,0.604c-0.087,0.034-0.141,0.106-0.15,0.205C7.893,3.784,7.919,3.909,7.982,4.066 c0.05,0.136,0.187,0.474,0.452,0.372c0.844-0.326,1.779-0.507,2.633-0.507c0.963,0,1.9,0.191,2.781,0.564 c0.695,0.292,1.357,0.719,2.078,1.34c0.051,0.044,0.105,0.068,0.164,0.068c0.143,0,0.273-0.137,0.389-0.271 c0.191-0.214,0.324-0.395,0.135-0.575c-0.686-0.654-1.436-1.138-2.363-1.533C13.24,3.097,12.168,2.884,11.066,2.884z"/><path d="M16.43,15.747c-0.092-0.028-0.242,0.05-0.309,0.119l0,0c-0.652,0.652-1.42,1.169-2.268,1.521 c-0.877,0.371-1.814,0.551-2.779,0.551c-0.961,0-1.896-0.189-2.775-0.564c-0.848-0.36-1.612-0.879-2.268-1.53 c-0.682-0.688-1.196-1.455-1.529-2.268c-0.325-0.799-0.471-1.643-0.471-1.643c-0.045-0.24-0.258-0.249-0.567-0.203 c-0.128,0.021-0.519,0.079-0.483,0.36v0.01c0.105,0.644,0.289,1.284,0.545,1.895c0.417,0.969,1.002,1.849,1.756,2.604 c0.757,0.754,1.636,1.34,2.604,1.757C8.901,18.785,9.97,19,11.088,19c1.104,0,2.186-0.215,3.188-0.645 c1.838-0.896,2.604-1.757,2.604-1.757c0.182-0.204,0.227-0.317-0.1-0.643C16.779,15.956,16.525,15.774,16.43,15.747z"/><path d="M5.633,13.287c0.293,0.71,0.723,1.341,1.262,1.882c0.54,0.54,1.172,0.971,1.882,1.264c0.731,0.303,1.509,0.461,2.298,0.461 c0.801,0,1.578-0.158,2.297-0.461c0.711-0.293,1.344-0.724,1.883-1.264c0.543-0.541,0.971-1.172,1.264-1.882 c0.314-0.721,0.463-1.5,0.463-2.298c0-0.79-0.148-1.569-0.463-2.289c-0.293-0.699-0.721-1.329-1.264-1.881 c-0.539-0.541-1.172-0.959-1.867-1.263c-0.721-0.303-1.5-0.461-2.299-0.461c-0.802,0-1.613,0.159-2.322,0.461 c-0.577,0.25-1.544,0.867-2.119,1.454v0.012V2.108h8.16C15.1,2.104,15.1,1.69,15.1,1.552C15.1,1.417,15.1,1,14.809,1H5.915 C5.676,1,5.527,1.192,5.527,1.384v6.84c0,0.214,0.273,0.372,0.529,0.428c0.5,0.105,0.614-0.056,0.737-0.224l0,0 c0.18-0.273,0.776-0.884,0.787-0.894c0.901-0.905,2.117-1.408,3.416-1.408c1.285,0,2.5,0.501,3.412,1.408 c0.914,0.914,1.408,2.122,1.408,3.405c0,1.288-0.508,2.496-1.408,3.405c-0.9,0.896-2.152,1.406-3.438,1.406 c-0.877,0-1.711-0.229-2.433-0.671v-4.158c0-0.553,0.237-1.151,0.643-1.614c0.462-0.519,1.094-0.799,1.782-0.799 c0.664,0,1.293,0.253,1.758,0.715c0.459,0.459,0.709,1.071,0.709,1.723c0,1.385-1.094,2.468-2.488,2.468 c-0.273,0-0.769-0.121-0.781-0.125c-0.281-0.087-0.405,0.306-0.438,0.436c-0.159,0.496,0.079,0.585,0.123,0.607 c0.452,0.137,0.743,0.157,1.129,0.157c1.973,0,3.572-1.6,3.572-3.57c0-1.964-1.6-3.552-3.572-3.552c-0.97,0-1.872,0.36-2.546,1.038 c-0.656,0.631-1.027,1.487-1.027,2.322v3.438v-0.011c-0.372-0.42-0.732-1.041-0.981-1.682c-0.102-0.248-0.315-0.202-0.607-0.113 c-0.135,0.035-0.519,0.157-0.44,0.439C5.372,12.799,5.577,13.164,5.633,13.287z"/></svg>',album:'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><rect x="5" y="2" width="10" height="1"/><rect x="3" y="4" width="14" height="1"/><rect fill="none" stroke="#000" x="1.5" y="6.5" width="17" height="11"/></svg>',"arrow-down":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><polygon points="10.5,16.08 5.63,10.66 6.37,10 10.5,14.58 14.63,10 15.37,10.66"/><line fill="none" stroke="#000" x1="10.5" y1="4" x2="10.5" y2="15"/></svg>',"arrow-left":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><polyline fill="none" stroke="#000" points="10 14 5 9.5 10 5"/><line fill="none" stroke="#000" x1="16" y1="9.5" x2="5" y2="9.52"/></svg>',"arrow-right":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><polyline fill="none" stroke="#000" points="10 5 15 9.5 10 14"/><line fill="none" stroke="#000" x1="4" y1="9.5" x2="15" y2="9.5"/></svg>',"arrow-up":'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><polygon points="10.5,4 15.37,9.4 14.63,10.08 10.5,5.49 6.37,10.08 5.63,9.4"/><line fill="none" stroke="#000" x1="10.5" y1="16" x2="10.5" y2="5"/></svg>',bag:'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="#000" d="M7.5,7.5V4A2.48,2.48,0,0,1,10,1.5,2.54,2.54,0,0,1,12.5,4V7.5"/><polygon fill="none" stroke="#000" points="16.5 7.5 3.5 7.5 2.5 18.5Line truncated
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/*! UIkit 3.11.1 | https://www.getuikit.com | (c) 2014 - 2022 YOOtheme | MIT License */(function(Kt,ge){typeof exports=="object"&&typeof module<"u"?module.exports=ge():typeof define=="function"&&define.amd?define("uikit",ge):(Kt=typeof globalThis<"u"?globalThis:Kt||self,Kt.UIkit=ge())})(this,function(){"use strict";var Kt=Object.prototype,ge=Kt.hasOwnProperty;function At(t,e){return ge.call(t,e)}var ns=/\B([A-Z])/g,zt=pt(function(t){return t.replace(ns,"-$1").toLowerCase()}),rs=/-(\w)/g,Xt=pt(function(t){return t.replace(rs,gn)}),pe=pt(function(t){return t.length?gn(null,t.charAt(0))+t.slice(1):""});function gn(t,e){return e?e.toUpperCase():""}var Ai=String.prototype,ss=Ai.startsWith||function(t){return this.lastIndexOf(t,0)===0};function vt(t,e){return ss.call(t,e)}var os=Ai.endsWith||function(t){return this.substr(-t.length)===t};function gt(t,e){return os.call(t,e)}var Pi=Array.prototype,pn=function(t,e){return!!~this.indexOf(t,e)},as=Ai.includes||pn,us=Pi.includes||pn;function b(t,e){return t&&(H(t)?as:us).call(t,e)}var hs=Pi.findIndex||function(t){for(var e=arguments,i=0;i<this.length;i++)if(t.call(e[1],this[i],i,this))return i;return-1};function Pt(t,e){return hs.call(t,e)}var ht=Array.isArray;function ct(t){return typeof t=="function"}function Tt(t){return t!==null&&typeof t=="object"}var cs=Kt.toString;function Nt(t){return cs.call(t)==="[object Object]"}function me(t){return Tt(t)&&t===t.window}function Jt(t){return Mi(t)===9}function Ni(t){return Mi(t)>=1}function Ot(t){return Mi(t)===1}function Mi(t){return!me(t)&&Tt(t)&&t.nodeType}function we(t){return typeof t=="boolean"}function H(t){return typeof t=="string"}function Zt(t){return typeof t=="number"}function Et(t){return Zt(t)||H(t)&&!isNaN(t-parseFloat(t))}function Qt(t){return!(ht(t)?t.length:Tt(t)?Object.keys(t).length:!1)}function Z(t){return t===void 0}function Di(t){return we(t)?t:t==="true"||t==="1"||t===""?!0:t==="false"||t==="0"?!1:t}function Mt(t){var e=Number(t);return isNaN(e)?!1:e}function x(t){return parseFloat(t)||0}var mn=Array.from||function(t){return Pi.slice.call(t)};function J(t){return T(t)[0]}function T(t){return t&&(Ni(t)?[t]:mn(t).filter(Ni))||[]}function Ft(t){return me(t)?t:(t=J(t),t?(Jt(t)?t:t.ownerDocument).defaultView:window)}function Hi(t){return t?gt(t,"ms")?x(t):x(t)*1e3:0}function qe(t,e){return t===e||Tt(t)&&Tt(e)&&Object.keys(t).length===Object.keys(e).length&&it(t,function(i,n){return i===e[n]})}function Bi(t,e,i){return t.replace(new RegExp(e+"|"+i,"g"),function(n){return n===e?i:e})}var k=Object.assign||function(t){for(var e=[],i=arguments.length-1;i-- >0;)e[i]=arguments[i+1];t=Object(t);for(var n=0;n<e.length;n++){var r=e[n];if(r!==null)for(var s in r)At(r,s)&&(t[s]=r[s])}return t};function be(t){return t[t.length-1]}function it(t,e){for(var i in t)if(e(t[i],i)===!1)return!1;return!0}function $e(t,e){return t.slice().sort(function(i,n){var r=i[e];r===void 0&&(r=0);var s=n[e];return s===void 0&&(s=0),r>s?1:s>r?-1:0})}function wn(t,e){var i=new Set;return t.filter(function(n){var r=n[e];return i.has(r)?!1:i.add(r)||!0})}function $t(t,e,i){return e===void 0&&(e=0),i===void 0&&(i=1),Math.min(Math.max(Mt(t)||0,e),i)}function N(){}function bn(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];return[["bottom","top"],["right","left"]].every(function(i){var n=i[0],r=i[1];return Math.min.apply(Math,t.map(function(s){var o=s[n];return o}))-Math.max.apply(Math,t.map(function(s){var o=s[r];return o}))>0})}function Ve(t,e){return t.x<=e.right&&t.x>=e.left&&t.y<=e.bottom&&t.y>=e.top}var Ut={ratio:function(t,e,i){var n,r=e==="width"?"height":"width";return n={},n[r]=t[e]?Math.round(i*t[r]/t[e]):t[r],n[e]=i,n},contain:function(t,e){var i=this;return t=k({},t),it(t,function(n,r){return t=t[r]>e[r]?i.ratio(t,r,e[r]):t}),t},cover:function(t,e){var i=this;return t=this.contain(t,e),it(t,function(n,r){return t=t[r]<e[r]?i.ratio(t,r,e[r]):t}),t}};function te(t,e,i,n){i===void 0&&(i=0),n===void 0&&(n=!1),e=T(e);var r=e.length;return r?(t=Et(t)?Mt(t):t==="next"?i+1:t==="previous"?i-1:e.indexOf(J(t)),n?$t(t,0,r-1):(t%=r,t<0?t+r:t)):-1}function pt(t){var e=Object.create(null);return function(i){return e[i]||(e[i]=t(i))}}function _(t,e,i){if(Tt(e)){for(var n in e)_(t,n,e[n]);return}if(Z(i))return t=J(t),t&&t.getAttribute(e);T(t).forEach(function(r){ct(i)&&(i=i.call(r,_(r,e))),i===null?zi(r,e):r.setAttribute(e,i)})}function ee(t,e){return T(t).some(function(i){return i.hasAttribute(e)})}function zi(t,e){t=T(t),e.split(" ").forEach(function(i){return t.forEach(function(n){return n.hasAttribute(i)&&n.removeAttribute(i)})})}function ft(t,e){for(var i=0,n=[e,"data-"+e];i<n.length;i++)if(ee(t,n[i]))return _(t,n[i])}var lt=typeof window<"u",xe=lt&&/msie|trident/i.test(window.navigator.userAgent),U=lt&&_(document.documentElement,"dir")==="rtl",ie=lt&&"ontouchstart"in window,ne=lt&&window.PointerEvent,$n=lt&&(ie||window.DocumentTouch&&document instanceof DocumentTouch||navigator.maxTouchPoints),dt=ne?"pointerdown":ie?"touchstart":"moLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
pub mod object;
|
||||
@@ -0,0 +1,23 @@
|
||||
use {{crate_name}};
|
||||
use uikit_rs as uk;
|
||||
use yew::{classes, function_component, html, Properties};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ObjectProps {
|
||||
#[prop_or_default]
|
||||
pub object: Option<{{crate_name}}::Object>,
|
||||
}
|
||||
|
||||
#[function_component(Object)]
|
||||
pub fn object(ObjectProps { object }: &ObjectProps) -> Html {
|
||||
html! {
|
||||
<span class={classes!(uk::Text::Meta)}>
|
||||
{
|
||||
match object {
|
||||
Some(obj) => format!("object: {}", obj.name ),
|
||||
None => "No object".to_string()
|
||||
}
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use components::object;
|
||||
use ::{{crate_name}}::Object;
|
||||
use reqwasm::http::Request;
|
||||
use uikit_rs as uk;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use yew::prelude::*;
|
||||
|
||||
mod components;
|
||||
|
||||
#[wasm_bindgen(module = "/js/api.js")]
|
||||
extern "C" {
|
||||
fn get_api_base_url() -> String;
|
||||
}
|
||||
|
||||
#[function_component(App)]
|
||||
pub fn app() -> Html {
|
||||
let object = use_state(|| None);
|
||||
{
|
||||
let object = object.clone();
|
||||
use_effect_with_deps(
|
||||
move |_| {
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let fetched_object: Object =
|
||||
Request::get(&format!("{}/hello", get_api_base_url()))
|
||||
.send()
|
||||
.await
|
||||
.unwrap() // TODO
|
||||
.json()
|
||||
.await
|
||||
.unwrap(); // TODO
|
||||
object.set(Some(fetched_object));
|
||||
});
|
||||
|| ()
|
||||
},
|
||||
(),
|
||||
);
|
||||
}
|
||||
|
||||
html! {
|
||||
<uk::Section style={uk::SectionStyle::Default}>
|
||||
<uk::Container size={uk::ContainerSize::Small}>
|
||||
<object::Object object={(*object).clone()} />
|
||||
</uk::Container>
|
||||
</uk::Section>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
use {{crate_name}}_web::App;
|
||||
|
||||
fn main() {
|
||||
yew::start_app::<App>();
|
||||
}
|
||||
Reference in new issue
Block a user