[Closes #44] Merge branch '44-migrate-frontend' into develop
commit
4b3ae8a9a4
|
@ -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
|
||||
|
|
|
@ -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 ]
|
||||
|
|
|
@ -4,6 +4,10 @@ version = "1.0.2"
|
|||
authors = ["Jef Roosens <roosensjef@gmail.com>"]
|
||||
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"]
|
||||
|
|
|
@ -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
|
||||
|
|
3
fejctl
3
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 ;;
|
||||
|
||||
|
|
|
@ -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<Rocket, Rocket> {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
VITE_ENDPOINT=https://fej.roosens.me/api
|
|
@ -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
|
|
@ -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 `<script setup>`
|
||||
|
||||
[`<script setup>`](https://github.com/vuejs/rfcs/pull/227) is a feature that is currently in RFC stage. To get proper IDE support for the syntax, use [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) instead of Vetur (and disable Vetur).
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can use the following:
|
||||
|
||||
### If Using Volar
|
||||
|
||||
Run `Volar: Switch TS Plugin on/off` from VSCode command palette.
|
||||
|
||||
### If Using Vetur
|
||||
|
||||
1. Install and add `@vuedx/typescript-plugin-vue` to the [plugins section](https://www.typescriptlang.org/tsconfig#plugins) in `tsconfig.json`
|
||||
2. Delete `src/shims-vue.d.ts` as it is no longer needed to provide module info to Typescript
|
||||
3. Open `src/main.ts` in VSCode
|
||||
4. Open the VSCode command palette
|
||||
5. Search and run "Select TypeScript version" -> "Use workspace version"
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -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"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<Nav />
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import Nav from './components/Nav.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
Nav,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
|
@ -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<Street[]> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<h1>Fej Frontend</h1>
|
||||
|
||||
<input id="street" type="text" placeholder="street" v-model="street" />
|
||||
<button :onClick="onSubmit">Search</button>
|
||||
|
||||
<ul>
|
||||
<li v-for="res in results">{{res.name}} ({{res.city}})</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { Street, Ivago } from '../api/ivago'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HelloWorld',
|
||||
data() {
|
||||
return {
|
||||
street: "",
|
||||
results: [] as Street[],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
new Ivago(import.meta.env.VITE_ENDPOINT as string).search(this.$data.street)
|
||||
.then(res => {
|
||||
this.$data.results = res;
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #eee;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
color: #304455;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<h1>Fej</h1>
|
||||
<p>Welcome to Fej, my frontend/backend combo.</p>
|
||||
<p>If you can see this, the cicd worked!</p>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Home',
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<h1>Ivago</h1>
|
||||
<input v-model="query" v-on:keyup.enter="search" type="text" placeholder="Street..." />
|
||||
<div id="scroll-list">
|
||||
<ul v-if="msg === ''">
|
||||
<li v-for="item in items">{{ item.name }}</li>
|
||||
</ul>
|
||||
<p v-else>{{ msg }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { Street, Ivago } from '../api/ivago'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Ivago',
|
||||
data() {
|
||||
return {
|
||||
items: [] as Street[],
|
||||
msg: "",
|
||||
query: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
search() {
|
||||
this.items = []
|
||||
this.msg = "Loading..."
|
||||
|
||||
if (this.query === "") {
|
||||
this.msg = ""
|
||||
return
|
||||
}
|
||||
|
||||
new Ivago(import.meta.env.VITE_ENDPOINT as string)
|
||||
.search(this.query)
|
||||
.then((res: Street[]) => {
|
||||
this.items = res
|
||||
this.msg = ""
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#scroll-list {
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div id="menu-wrapper">
|
||||
<nav id="menu">
|
||||
<router-link to="/">Home</router-link>
|
||||
<router-link to="/ivago">Ivago</router-link>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Nav',
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#menu-wrapper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 150px;
|
||||
background-color: #242624;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#menu {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#menu > a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: #5f635f;
|
||||
text-decoration: none;
|
||||
}
|
||||
#menu > a:hover {
|
||||
color: #c2ccc1;
|
||||
}
|
||||
</style>
|
|
@ -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')
|
|
@ -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;
|
|
@ -0,0 +1,5 @@
|
|||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
|
@ -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"]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()]
|
||||
})
|
Loading…
Reference in New Issue