diff --git a/.dockerignore b/.dockerignore index a96bd42..b143673 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,20 +1,42 @@ * -# Source code +# =====BACKEND===== +## Source code !src/ -# Cargo files +## Cargo files !Cargo.toml !Cargo.lock -# Entrypoint for devop container +## Entrypoint for devop container !docker/entrypoint_dev.sh !docker/entrypoint.sh -# Config file +## Config file !Rocket.toml -# Database migrations +## Database migrations !migrations/ +## Crontab for release container !docker/crontab + + +# =====FRONTEND===== +## Source code +!web/src/ + +## Public assets +!web/public/ +!web/index.html + +## Production env file +!web/.env.production + +## Package manager stuff +!web/package.json +!web/yarn.lock + +## Project configs +!web/tsconfig.json +!web/vite.config.ts diff --git a/.woodpecker.yml b/.woodpecker.yml index 78e9241..6199967 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,9 +1,18 @@ pipeline: - test: + build-frontend: + image: node:15-alpine3.13 + pull: true + commands: + - cd web && yarn install + - cd web && yarn run build + + test-backend: image: chewingbever/fej-builder:latest # Always update the builder image pull: true commands: - cargo test + # TODO build dev & rel image, deploy these images + branches: [ master, develop ] diff --git a/Cargo.toml b/Cargo.toml index 97668d2..cf6db71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,10 @@ version = "1.0.2" authors = ["Jef Roosens "] edition = "2018" +[features] +# Enables hosting of the frontend +frontend = [] + [lib] name = "fej" path = "src/fej/lib.rs" @@ -41,4 +45,4 @@ diesel_migrations = "1.4.0" [dependencies.rocket_contrib] version = "0.4.7" default-features = false -features = ["json", "diesel_postgres_pool"] +features = ["json", "diesel_postgres_pool", "serve"] diff --git a/docker/Dockerfile.rel b/docker/Dockerfile.rel index 4bffeae..5a17f05 100644 --- a/docker/Dockerfile.rel +++ b/docker/Dockerfile.rel @@ -1,5 +1,5 @@ # vim: filetype=dockerfile -FROM chewingbever/fej-builder:latest AS builder +FROM chewingbever/fej-builder:latest AS backend-builder COPY --chown=builder:builder Cargo.toml Cargo.lock ./ COPY --chown=builder:builder src/ ./src/ @@ -19,7 +19,18 @@ COPY --chown=builder:builder migrations/ ./migrations/ RUN cargo install \ --path . \ --root /app/output \ - --target x86_64-unknown-linux-musl + --target x86_64-unknown-linux-musl \ + --features frontend + + +FROM node:15-alpine3.13 AS frontend-builder + +COPY ./web /app +WORKDIR /app + +# Build the frontend +RUN yarn install && \ + yarn run build # Now, we create the actual image @@ -41,8 +52,9 @@ RUN apk update && \ # Switch to non-root user USER fej:fej -# Copy binary over to final image -COPY --from=builder --chown=fej:fej /app/output/bin /app/bin +# Copy binary & frontend over to final image +COPY --from=backend-builder --chown=fej:fej /app/output/bin /app/bin +COPY --from=frontend-builder --chown=fej:fej /app/dist /app/dist # Embed config file inside container # The workdir is changed so that the config file is read properly diff --git a/fejctl b/fejctl index 6f5c8a9..57f7443 100755 --- a/fejctl +++ b/fejctl @@ -1,6 +1,7 @@ #!/usr/bin/env bash image='chewingbever/fej' +web_dir='web' # Small wrapper around the docker-compose command # @@ -98,10 +99,12 @@ function main() { # Building b | build ) dcr build --bin "$bin" && dc -- logs -f app ;; br | build-release ) dc -br build ;; + bf | build-frontend ) cd "$web_dir" && yarn run build ;; # Running r | run ) dcr run --bin "$bin" && dc -- logs -f app ;; rr | run-release ) dc -br -- up --build --detach && dc -r -- logs -f app ;; + rf | run-frontend ) dcr run --bin server && cd "$web_dir" && yarn run dev ;; s | stop ) dc down ;; sr | stop-release ) dc -r stop ;; diff --git a/src/server/main.rs b/src/server/main.rs index 0e84ff8..2a5bd2f 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -20,6 +20,8 @@ use rocket::fairing::{Fairing, Info, Kind}; use rocket::http::Header; use rocket::{Request, Response, Rocket}; use rocket_contrib::databases::diesel; +#[cfg(feature = "frontend")] +use rocket_contrib::serve::StaticFiles; pub struct CORS; @@ -60,12 +62,21 @@ fn run_db_migrations(rocket: Rocket) -> Result { } fn rocket() -> rocket::Rocket { - rocket::ignite() + // This needs to be muted for the frontend feature + let mut rocket = rocket::ignite() .attach(CORS) .attach(FejDbConn::fairing()) .attach(AdHoc::on_attach("Database Migrations", run_db_migrations)) - .mount("/ivago", routes::ivago()) - .register(catchers![catchers::not_found]) + .mount("/api/ivago", routes::ivago()) // /api being hardcoded is temporary + .register(catchers![catchers::not_found]); + + // TODO make all of this not hard-coded + #[cfg(feature = "frontend")] + { + rocket = rocket.mount("/", StaticFiles::from("/app/dist")); + } + + rocket } fn main() { diff --git a/web/.env b/web/.env new file mode 100644 index 0000000..18f38c1 --- /dev/null +++ b/web/.env @@ -0,0 +1 @@ +VITE_ENDPOINT=http://localhost:8000/api diff --git a/web/.env.production b/web/.env.production new file mode 100644 index 0000000..cf6e652 --- /dev/null +++ b/web/.env.production @@ -0,0 +1 @@ +VITE_ENDPOINT=https://fej.roosens.me/api diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..04459e4 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,130 @@ +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +# End of https://www.toptal.com/developers/gitignore/api/node diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..a797a27 --- /dev/null +++ b/web/README.md @@ -0,0 +1,27 @@ +# Vue 3 + Typescript + Vite + +This template should help get you started developing with Vue 3 and Typescript in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur). Make sure to enable `vetur.experimental.templateInterpolationService` in settings! + +### If Using ` + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..ae1647d --- /dev/null +++ b/web/package.json @@ -0,0 +1,20 @@ +{ + "name": "fej-frontend", + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "serve": "vite preview" + }, + "dependencies": { + "vue": "^3.0.5", + "vue-router": "^4.0.6" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^1.2.1", + "@vue/compiler-sfc": "^3.0.5", + "typescript": "^4.1.3", + "vite": "^2.2.1", + "vue-tsc": "^0.0.25" + } +} diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 0000000..ca4df88 --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/web/src/api/ivago.ts b/web/src/api/ivago.ts new file mode 100644 index 0000000..662e531 --- /dev/null +++ b/web/src/api/ivago.ts @@ -0,0 +1,32 @@ +export class Street { + name: string; + city: string; + + constructor(name: string, city: string) { + this.name = name; + this.city = city; + } +} + +export class Ivago { + base_url: string; + + constructor(url: string) { + this.base_url = url; + } + + async search(search_term: string): Promise { + var r = await fetch(`${this.base_url}/ivago/search?` + new URLSearchParams({ + q: search_term, + })); + + if (!r.ok) { + return Promise.reject(); + } + + var json = await r.json(); + + return json.map((o: {name: string, city: string}) => new Street(o.name, o.city)); + } +} + diff --git a/web/src/assets/logo.png b/web/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/web/src/assets/logo.png differ diff --git a/web/src/components/HelloWorld.vue b/web/src/components/HelloWorld.vue new file mode 100644 index 0000000..12f05c8 --- /dev/null +++ b/web/src/components/HelloWorld.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/web/src/components/Home.vue b/web/src/components/Home.vue new file mode 100644 index 0000000..f5cc87a --- /dev/null +++ b/web/src/components/Home.vue @@ -0,0 +1,13 @@ + + + diff --git a/web/src/components/Ivago.vue b/web/src/components/Ivago.vue new file mode 100644 index 0000000..923f85c --- /dev/null +++ b/web/src/components/Ivago.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/web/src/components/Nav.vue b/web/src/components/Nav.vue new file mode 100644 index 0000000..fcf9c26 --- /dev/null +++ b/web/src/components/Nav.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 0000000..af7463e --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import router from './router'; + +const app = createApp(App) +app.use(router) + +app.mount('#app') diff --git a/web/src/router.ts b/web/src/router.ts new file mode 100644 index 0000000..2d7fa58 --- /dev/null +++ b/web/src/router.ts @@ -0,0 +1,21 @@ +import { createWebHistory, createRouter } from "vue-router"; +import Home from './components/Home.vue'; +import Ivago from './components/Ivago.vue'; + +const routes = [ + { + path: "/", + component: Home, + }, + { + path: "/ivago", + component: Ivago, + }, +]; + +const router = createRouter({ + history: createWebHistory(), + routes, +}); + +export default router; diff --git a/web/src/shims-vue.d.ts b/web/src/shims-vue.d.ts new file mode 100644 index 0000000..ac1ded7 --- /dev/null +++ b/web/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..e754e65 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "types": ["vite/client"] + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 0000000..315212d --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()] +})