mostly housekeeping
- App - some package clean-up - API - removed commented code that is not needed, postured for JSON outputs - Build - modified the scripts to build Vue into API's wwwroot folder, which will get copied on publish; adjusted FAKE task dependencies to be more granular
This commit is contained in:
parent
196db30cc5
commit
5a7a74c167
3
.gitignore
vendored
3
.gitignore
vendored
@ -253,8 +253,7 @@ paket-files/
|
||||
*.sln.iml
|
||||
|
||||
# Compiled files / application
|
||||
**/vendor-bundle.js
|
||||
**/app-bundle.js*
|
||||
src/api/wwwroot/index.html
|
||||
src/api/wwwroot/static
|
||||
src/api/appsettings.json
|
||||
build/
|
@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
cd .\src\app
|
||||
au build
|
||||
npm run build prod
|
||||
cd ..\..
|
||||
exit %errorlevel%
|
55
build.fsx
55
build.fsx
@ -4,7 +4,7 @@ open System
|
||||
|
||||
let buildDir = "./build/"
|
||||
|
||||
/// Path to the Aurelia app
|
||||
/// Path to the Vue app
|
||||
let appPath = "src" @@ "app"
|
||||
|
||||
/// Path to the Suave API
|
||||
@ -14,27 +14,15 @@ let apiPath = "src" @@ "api"
|
||||
|
||||
Target "Clean" (fun _ ->
|
||||
CleanDir buildDir
|
||||
CleanDir (apiPath @@ "wwwroot")
|
||||
)
|
||||
|
||||
Target "BuildApp" (fun _ ->
|
||||
let result =
|
||||
ExecProcessAndReturnMessages (fun info ->
|
||||
info.UseShellExecute <- false
|
||||
info.FileName <- "." @@ "build-au.bat") (TimeSpan.FromMinutes 2.)
|
||||
match result.ExitCode with
|
||||
| 0 -> Log "AppBuild-Output: " result.Messages
|
||||
| _ -> failwith "Aurelia build failed"
|
||||
)
|
||||
|
||||
Target "CopyApp" (fun _ ->
|
||||
let apiWebPath = apiPath @@ "wwwroot"
|
||||
[ "scripts" @@ "app-bundle.js"
|
||||
"scripts" @@ "vendor-bundle.js"
|
||||
"index.html"
|
||||
]
|
||||
|> List.iter (fun file ->
|
||||
IO.File.Copy (appPath @@ file, apiWebPath @@ file, true)
|
||||
Log "CopyApp--Output: " (Seq.singleton file))
|
||||
info.FileName <- "build-vue.bat") (TimeSpan.FromMinutes 2.)
|
||||
match result.ExitCode with 0 -> Log "AppBuild-Output: " result.Messages | _ -> failwith "Vue build failed"
|
||||
)
|
||||
|
||||
Target "BuildApi" (fun _ ->
|
||||
@ -43,26 +31,24 @@ Target "BuildApi" (fun _ ->
|
||||
info.UseShellExecute <- false
|
||||
info.FileName <- "dotnet"
|
||||
info.Arguments <- "build"
|
||||
info.WorkingDirectory <- "src" @@ "api") (TimeSpan.FromMinutes 2.)
|
||||
info.WorkingDirectory <- apiPath) (TimeSpan.FromMinutes 2.)
|
||||
Log "AppBuild-Output: " result.Messages
|
||||
match result.ExitCode with
|
||||
| 0 -> ()
|
||||
| _ -> failwith "API build failed"
|
||||
(*!! "src/api/*.fsproj"
|
||||
|> MSBuildRelease buildDir "Build"
|
||||
|> Log "ApiBuild-Output: " *)
|
||||
match result.ExitCode with 0 -> () | _ -> failwith "API build failed"
|
||||
)
|
||||
|
||||
Target "Publish" (fun _ ->
|
||||
ExecProcess (fun info ->
|
||||
info.FileName <- "dotnet"
|
||||
info.Arguments <- """publish -o ..\..\build"""
|
||||
info.WorkingDirectory <- apiPath) TimeSpan.MaxValue
|
||||
|> ignore
|
||||
)
|
||||
|
||||
Target "Run" (fun _ ->
|
||||
ExecProcess (fun info ->
|
||||
info.FileName <- "dotnet"
|
||||
info.Arguments <- """publish -o ..\..\build"""
|
||||
info.WorkingDirectory <- "src" @@ "api") TimeSpan.MaxValue
|
||||
|> ignore
|
||||
ExecProcess (fun info ->
|
||||
info.FileName <- "dotnet"
|
||||
info.Arguments <- "myPrayerJournal.dll"
|
||||
info.WorkingDirectory <- "build") TimeSpan.MaxValue
|
||||
info.WorkingDirectory <- buildDir) TimeSpan.MaxValue
|
||||
|> ignore
|
||||
)
|
||||
|
||||
@ -74,11 +60,18 @@ Target "Default" (fun _ ->
|
||||
|
||||
"Clean"
|
||||
==> "BuildApp"
|
||||
==> "CopyApp"
|
||||
|
||||
"BuildApp"
|
||||
==> "BuildApi"
|
||||
==> "Default"
|
||||
|
||||
"BuildApi"
|
||||
==> "Publish"
|
||||
|
||||
"Publish"
|
||||
==> "Run"
|
||||
|
||||
"BuildApi"
|
||||
==> "Default"
|
||||
|
||||
|
||||
RunTargetOrDefault "Default"
|
233
src/api/App.fs
233
src/api/App.fs
@ -1,76 +1,81 @@
|
||||
/// Main server module for myPrayerJournal
|
||||
module MyPrayerJournal.App
|
||||
|
||||
open Auth0.AuthenticationApi
|
||||
open Auth0.AuthenticationApi.Models
|
||||
open Microsoft.EntityFrameworkCore
|
||||
open Newtonsoft.Json
|
||||
open Newtonsoft.Json.Linq
|
||||
open Reader
|
||||
open System
|
||||
open System.IO
|
||||
open Suave
|
||||
open Suave.Filters
|
||||
open Suave.Operators
|
||||
open Suave.Redirection
|
||||
open Suave.RequestErrors
|
||||
open Suave.State.CookieStateStore
|
||||
open Suave.Successful
|
||||
|
||||
let utf8 = System.Text.Encoding.UTF8
|
||||
|
||||
type JsonNetCookieSerializer () =
|
||||
interface CookieSerialiser with
|
||||
member x.serialise m =
|
||||
utf8.GetBytes (JsonConvert.SerializeObject m)
|
||||
member x.deserialise m =
|
||||
JsonConvert.DeserializeObject<Map<string, obj>> (utf8.GetString m)
|
||||
// --- Types ---
|
||||
|
||||
/// Auth0 settings
|
||||
type Auth0Config = {
|
||||
/// The domain used with Auth0
|
||||
Domain : string
|
||||
/// The client Id
|
||||
ClientId : string
|
||||
/// The base64-encoded client secret
|
||||
ClientSecret : string
|
||||
}
|
||||
/// The URL-safe base64-encoded client secret
|
||||
ClientSecretJwt : string
|
||||
}
|
||||
with
|
||||
/// An empty set of Auth0 settings
|
||||
static member empty =
|
||||
{ Domain = ""
|
||||
ClientId = ""
|
||||
ClientSecret = ""
|
||||
ClientSecretJwt = ""
|
||||
}
|
||||
|
||||
/// Application configuration
|
||||
type Config = {
|
||||
/// PostgreSQL connection string
|
||||
Conn : string
|
||||
/// Auth0 settings
|
||||
Auth0 : Auth0Config
|
||||
}
|
||||
}
|
||||
with
|
||||
static member empty =
|
||||
{ Conn = ""
|
||||
Auth0 = Auth0Config.empty
|
||||
}
|
||||
|
||||
|
||||
/// A JSON response as a data property
|
||||
type JsonOkResponse<'a> = {
|
||||
data : 'a
|
||||
}
|
||||
|
||||
|
||||
/// A JSON response indicating an error occurred
|
||||
type JsonErrorResponse = {
|
||||
error : string
|
||||
}
|
||||
|
||||
// --- Support ---
|
||||
|
||||
/// Configuration instance
|
||||
let cfg =
|
||||
try
|
||||
use sr = File.OpenText "appsettings.json"
|
||||
let settings = JToken.ReadFrom(new JsonTextReader(sr)) :?> JObject
|
||||
use tr = new JsonTextReader (sr)
|
||||
let settings = JToken.ReadFrom tr
|
||||
let secret = settings.["auth0"].["client-secret"].ToObject<string>()
|
||||
{ Conn = settings.["conn"].ToObject<string>()
|
||||
Auth0 =
|
||||
{ Domain = settings.["auth0"].["domain"].ToObject<string>()
|
||||
ClientId = settings.["auth0"].["client-id"].ToObject<string>()
|
||||
ClientSecret = settings.["auth0"].["client-secret"].ToObject<string>()
|
||||
ClientSecret = secret
|
||||
ClientSecretJwt = secret.TrimEnd('=').Replace("-", "+").Replace("_", "/")
|
||||
}
|
||||
}
|
||||
with _ -> Config.empty
|
||||
|
||||
/// Data Configuration singleton
|
||||
//let lazyCfg = lazy (DataConfig.FromJson <| try File.ReadAllText "data-config.json" with _ -> "{}")
|
||||
/// RethinkDB connection singleton
|
||||
//let lazyConn = lazy lazyCfg.Force().CreateConnection ()
|
||||
/// Application dependencies
|
||||
//let deps = {
|
||||
// new IDependencies with
|
||||
// member __.Conn with get () = lazyConn.Force ()
|
||||
// }
|
||||
|
||||
/// Get the scheme, host, and port of the URL
|
||||
let schemeHostPort (req : HttpRequest) =
|
||||
sprintf "%s://%s" req.url.Scheme (req.headers |> List.filter (fun x -> fst x = "host") |> List.head |> snd)
|
||||
@ -78,120 +83,46 @@ let schemeHostPort (req : HttpRequest) =
|
||||
/// Authorization functions
|
||||
module Auth =
|
||||
|
||||
(*
|
||||
let exchangeCodeForToken code = context (fun ctx ->
|
||||
async {
|
||||
let client = AuthenticationApiClient (Uri (sprintf "https://%s" cfg.Auth0.Domain))
|
||||
let! req =
|
||||
client.ExchangeCodeForAccessTokenAsync
|
||||
(ExchangeCodeRequest
|
||||
(AuthorizationCode = code,
|
||||
ClientId = cfg.Auth0.ClientId,
|
||||
ClientSecret = cfg.Auth0.ClientSecret,
|
||||
RedirectUri = sprintf "%s/user/log-on" (schemeHostPort ctx.request)))
|
||||
let! user = client.GetUserInfoAsync ((req : AccessToken).AccessToken)
|
||||
return
|
||||
ctx
|
||||
|> HttpContext.state
|
||||
|> function
|
||||
| None -> FORBIDDEN "Cannot sign in without state"
|
||||
| Some state ->
|
||||
state.set "auth-token" req.IdToken
|
||||
>=> Writers.setUserData "user" user
|
||||
}
|
||||
|> Async.RunSynchronously
|
||||
)
|
||||
|
||||
/// Handle the sign-in callback from Auth0
|
||||
let handleSignIn =
|
||||
context (fun ctx ->
|
||||
GET
|
||||
>=> match ctx.request.queryParam "code" with
|
||||
| Choice1Of2 authCode ->
|
||||
exchangeCodeForToken authCode
|
||||
>=> FOUND (sprintf "%s/journal" (schemeHostPort ctx.request))
|
||||
| Choice2Of2 msg -> BAD_REQUEST msg
|
||||
)
|
||||
|
||||
/// Handle signing out a user
|
||||
let handleSignOut =
|
||||
context (fun ctx ->
|
||||
match ctx |> HttpContext.state with
|
||||
| Some state -> state.set "auth-key" null
|
||||
| _ -> succeed
|
||||
>=> FOUND (sprintf "%s/" (schemeHostPort ctx.request))) *)
|
||||
|
||||
/// Shorthand for Console.WriteLine
|
||||
let cw (x : string) = Console.WriteLine x
|
||||
|
||||
/// Convert microtime to ticks, add difference from 1/1/1 to 1/1/1970
|
||||
let jsDate jsTicks =
|
||||
DateTime(jsTicks * 10000000L).AddTicks(DateTime(1970, 1, 1).Ticks)
|
||||
|
||||
let getIdFromToken token =
|
||||
match token with
|
||||
| Some jwt ->
|
||||
try
|
||||
let key = Convert.FromBase64String(cfg.Auth0.ClientSecret.Replace("-", "+").Replace("_", "/"))
|
||||
let payload = Jose.JWT.Decode<JObject>(jwt, key)
|
||||
let tokenExpires = jsDate (payload.["exp"].ToObject<int64>())
|
||||
match tokenExpires > DateTime.UtcNow with
|
||||
| true -> Some (payload.["sub"].ToObject<string>())
|
||||
| _ -> None
|
||||
with ex ->
|
||||
sprintf "Token Deserialization Exception - %s" (ex.GetType().FullName) |> cw
|
||||
sprintf "Message - %s" ex.Message |> cw
|
||||
ex.StackTrace |> cw
|
||||
None
|
||||
| _ -> None
|
||||
/// Get the user Id (sub) from a JSON Web Token
|
||||
let getIdFromToken jwt =
|
||||
try
|
||||
let payload = Jose.JWT.Decode<JObject>(jwt, cfg.Auth0.ClientSecretJwt)
|
||||
let tokenExpires = jsDate (payload.["exp"].ToObject<int64>())
|
||||
match tokenExpires > DateTime.UtcNow with
|
||||
| true -> Some (payload.["sub"].ToObject<string>())
|
||||
| _ -> None
|
||||
with ex ->
|
||||
sprintf "Token Deserialization Exception - %s" (ex.GetType().FullName) |> cw
|
||||
sprintf "Message - %s" ex.Message |> cw
|
||||
ex.StackTrace |> cw
|
||||
None
|
||||
|
||||
/// Add the logged on user Id to the context if it exists
|
||||
let loggedOn = warbler (fun ctx ->
|
||||
match HttpContext.state ctx with
|
||||
| Some state -> Writers.setUserData "user" (state.get "auth-token" |> getIdFromToken)
|
||||
let loggedOn =
|
||||
warbler (fun ctx ->
|
||||
match ctx.request.header "Authorization" with
|
||||
| Choice1Of2 bearer -> Writers.setUserData "user" ((bearer.Split(' ').[1]) |> getIdFromToken)
|
||||
| _ -> Writers.setUserData "user" None)
|
||||
|
||||
/// Create a user context for the currently assigned user
|
||||
//let userCtx ctx = { Id = ctx.userState.["user"] :?> string option }
|
||||
|
||||
/// Serialize an object to JSON
|
||||
let toJson = JsonConvert.SerializeObject
|
||||
|
||||
/// Read an item from the user state, downcast to the expected type
|
||||
let read ctx key : 'value =
|
||||
ctx.userState |> Map.tryFind key |> Option.map (fun x -> x :?> 'value) |> Option.get
|
||||
|
||||
|
||||
/// Create a new data context
|
||||
let dataCtx () =
|
||||
new DataContext (((DbContextOptionsBuilder<DataContext>()).UseNpgsql cfg.Conn).Options)
|
||||
|
||||
/// Return an HTML page
|
||||
let html ctx content =
|
||||
""//Views.page (Auth.userCtx ctx) content
|
||||
|
||||
/// Home page
|
||||
let viewHome = warbler (fun ctx -> OK ("" (*Views.home*) |> html ctx))
|
||||
|
||||
/// Journal page
|
||||
let viewJournal =
|
||||
context (fun ctx ->
|
||||
use dataCtx = dataCtx ()
|
||||
let reqs = Data.Requests.allForUser (defaultArg (read ctx "user") "") dataCtx
|
||||
OK ("" (*Views.journal reqs*) |> html ctx))
|
||||
|
||||
let idx =
|
||||
context (fun ctx ->
|
||||
Console.WriteLine "serving index"
|
||||
succeed)
|
||||
/// Suave application
|
||||
let app =
|
||||
statefulForSession
|
||||
>=> Auth.loggedOn
|
||||
>=> choose [
|
||||
path Route.home >=> Files.browseFileHome "index.html"
|
||||
path Route.journal >=> viewJournal
|
||||
//path Route.User.logOn >=> Auth.handleSignIn
|
||||
//path Route.User.logOff >=> Auth.handleSignOut
|
||||
Writers.setHeader "Cache-Control" "no-cache" >=> Files.browseHome
|
||||
NOT_FOUND "Page not found."
|
||||
]
|
||||
|
||||
/// Ensure the EF context is created in the right format
|
||||
let ensureDatabase () =
|
||||
async {
|
||||
@ -204,16 +135,54 @@ let suaveCfg =
|
||||
{ defaultConfig with
|
||||
homeFolder = Some (Path.GetFullPath "./wwwroot/")
|
||||
serverKey = Text.Encoding.UTF8.GetBytes("12345678901234567890123456789012")
|
||||
cookieSerialiser = JsonNetCookieSerializer ()
|
||||
bindings = [ HttpBinding.createSimple HTTP "127.0.0.1" 8084 ]
|
||||
}
|
||||
open Suave.Utils
|
||||
|
||||
// --- Routes ---
|
||||
|
||||
/// URL routes for myPrayerJournal
|
||||
module Route =
|
||||
|
||||
/// /api/journal ~ All active prayer requests for a user
|
||||
let journal = "/api/journal"
|
||||
|
||||
|
||||
// --- WebParts ---
|
||||
|
||||
/// All WebParts that compose the public API
|
||||
module WebParts =
|
||||
|
||||
let jsonMimeType =
|
||||
warbler (fun ctx -> Writers.setMimeType "application/json; charset=utf8")
|
||||
|
||||
/// WebPart to return a JSON response
|
||||
let JSON payload =
|
||||
jsonMimeType
|
||||
>=> Successful.OK (toJson { data = payload })
|
||||
|
||||
/// WebPart to return an JSON error response
|
||||
let errorJSON code error =
|
||||
jsonMimeType
|
||||
>=> Writers.setStatus code
|
||||
>=> Response.response code ((toJson >> UTF8.bytes) { error = error })
|
||||
|
||||
/// Journal page
|
||||
let viewJournal =
|
||||
context (fun ctx ->
|
||||
use dataCtx = dataCtx ()
|
||||
let reqs = Data.Requests.allForUser (defaultArg (read ctx "user") "") dataCtx
|
||||
JSON reqs)
|
||||
|
||||
/// Suave application
|
||||
let app =
|
||||
Auth.loggedOn
|
||||
>=> choose [
|
||||
path Route.journal >=> viewJournal
|
||||
errorJSON HttpCode.HTTP_404 "Page not found"
|
||||
]
|
||||
|
||||
[<EntryPoint>]
|
||||
let main argv =
|
||||
// Establish the data environment
|
||||
//liftDep getConn (Data.establishEnvironment >> Async.RunSynchronously)
|
||||
//|> run deps
|
||||
|
||||
ensureDatabase ()
|
||||
startWebServer suaveCfg app
|
||||
startWebServer suaveCfg WebParts.app
|
||||
0
|
||||
|
@ -16,7 +16,6 @@
|
||||
<Compile Include="Data.fs" />
|
||||
<Compile Include="Migrations/20170104023341_InitialDb.fs" />
|
||||
<Compile Include="Migrations/DataContextModelSnapshot.fs" />
|
||||
<Compile Include="Route.fs" />
|
||||
<Compile Include="App.fs" />
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
@ -28,9 +27,6 @@
|
||||
|
||||
<!-- Import Project="..\..\.paket\Paket.Restore.targets" / -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Auth0.AuthenticationApi">
|
||||
<Version>4.1.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FSharp.Core">
|
||||
<Version>4.1.17</Version>
|
||||
</PackageReference>
|
||||
@ -38,16 +34,16 @@
|
||||
<Version>1.0.5</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="jose-jwt">
|
||||
<Version>2.3.0</Version>
|
||||
<Version>2.*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>10.0.2</Version>
|
||||
<Version>10.*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL">
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Suave">
|
||||
<Version>2.1.0</Version>
|
||||
<Version>2.*</Version>
|
||||
</PackageReference>
|
||||
|
||||
</ItemGroup>
|
||||
|
@ -1,15 +0,0 @@
|
||||
/// URL routes for myPrayerJournal
|
||||
module MyPrayerJournal.Route
|
||||
|
||||
/// The home page
|
||||
let home = "/"
|
||||
|
||||
/// The main journal page
|
||||
let journal = "/journal"
|
||||
|
||||
/// Routes dealing with users
|
||||
module User =
|
||||
/// The route for user log on response from Auth0
|
||||
let logOn = "/user/log-on"
|
||||
let logOff = "/user/log-off"
|
||||
|
@ -1,35 +0,0 @@
|
||||
body {
|
||||
padding-top: 50px;
|
||||
padding-bottom: 20px;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
|
||||
}
|
||||
|
||||
/* Wrapping element */
|
||||
/* Set some basic padding to keep content from hitting the edges */
|
||||
.body-content {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
.material-icons.md-18 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.material-icons.md-24 {
|
||||
font-size: 24px;
|
||||
}
|
||||
.material-icons.md-36 {
|
||||
font-size: 36px;
|
||||
}
|
||||
.material-icons.md-48 {
|
||||
font-size: 48px;
|
||||
}
|
||||
.material-icons {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.mpj-page-title {
|
||||
border-bottom: solid 1px lightgray;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.mpj-footer {
|
||||
border-top: solid 1px lightgray;
|
||||
margin-top: 20px;
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* myPrayerJournal script file
|
||||
*/
|
||||
var mpj = {
|
||||
lock: new Auth0Lock('Of2s0RQCQ3mt3dwIkOBY5h85J9sXbF2n', 'djs-consulting.auth0.com', {
|
||||
auth: {
|
||||
redirectUrl: 'http://localhost:8080/user/log-on',
|
||||
allowSignUp: false
|
||||
}
|
||||
}),
|
||||
|
||||
signIn: function() {
|
||||
this.lock.show()
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@ var path = require('path')
|
||||
module.exports = {
|
||||
build: {
|
||||
env: require('./prod.env'),
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
index: path.resolve(__dirname, '../../api/wwwroot/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../../api/wwwroot'),
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
productionSourceMap: true,
|
||||
|
1027
src/app/package-lock.json
generated
1027
src/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"auth0-js": "^8.8.0",
|
||||
"axios": "^0.16.2",
|
||||
"element-ui": "^1.4.1",
|
||||
"pug": "^2.0.0-rc.2",
|
||||
"vue": "^2.3.3",
|
||||
|
@ -19,9 +19,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url('../node_modules/bootstrap/dist/css/bootstrap.css');
|
||||
@import url('../node_modules/bootstrap-vue/dist/bootstrap-vue.css');
|
||||
|
||||
body {
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: 'http://localhost:8084'
|
||||
})
|
||||
|
||||
export default {
|
||||
something: http.get('/blah')
|
||||
}
|
Loading…
Reference in New Issue
Block a user