diff --git a/.github/workflows/docker-images.yaml b/.github/workflows/docker-images.yaml index be9fbcb..273ea5d 100644 --- a/.github/workflows/docker-images.yaml +++ b/.github/workflows/docker-images.yaml @@ -9,7 +9,7 @@ on: jobs: build: - name: Build container + name: Build image runs-on: ubuntu-latest steps: - name: Checkout @@ -35,13 +35,13 @@ jobs: run: cd server && go test -v ./... - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ github.repository_owner }} password: ${{ secrets.HUB_TOKEN }} - name: Log in to ghcr.io - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -82,7 +82,7 @@ jobs: npm ci install CI=false GENERATE_SOURCEMAP=false npm run build:docker - - name: Build and push + - name: Build docker image and push id: docker_build uses: docker/build-push-action@v2 with: diff --git a/README.md b/README.md index e9c1c80..388d8f0 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,13 @@ docker build . -t shortpaste You can customize the behavior using environment variables:. -| Environment Variable | Default Value | Behaviour | -| -------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------ | -| `API_KEY` | `CHANGE-IT-ASAP` | API key used to communicate with the API. | -| `BASE_PATH` | `/` | App base path (use it if the app does not have run its dedicated domain name). | -| `DEBUG` | | Whether the app runs in debug mode or not (basically just more logs). | -| `PORT` | 8080 | Port on which app is running, **should not be changed**. | +| Environment Variable | Default Value | Behaviour | +| -------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `API_KEY` | `CHANGE-IT-ASAP` | API key used to communicate with the API. | +| `BASE_PATH` | `/` | App base path (use it if the app does not have run its dedicated domain name). | +| `DOMAIN` | | Override shortened URL domain in front, when copy to clipboard (default is the actual page domain). Example: `https://sho.rt` | +| `DEBUG` | | Whether the app runs in debug mode or not (basically just more logs). | +| `PORT` | 8080 | Port on which app is running, **should not be changed**. | ## Securing @@ -137,8 +138,8 @@ Your web browser should open on `http://localhost:3000`. The app is configured t ## TODO -- [ ] optimize build -- [ ] allow user to set the public url for shortened content +- [x] optimize build time +- [x] allow user to set the public url for shortened content - [x] move from sqlite3 to modernc.org/sqlite to compile without CGO (and use scratch, lighter image) - [ ] display status with version - [ ] allow user to change language diff --git a/front/src/App.tsx b/front/src/App.tsx index 890f32f..285b70a 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -1,27 +1,48 @@ import { Route } from "react-router-dom"; -import classes from './app.module.scss'; -import {MemoryRouter as Router} from "react-router-dom"; +import classes from "./app.module.scss"; +import { MemoryRouter as Router } from "react-router-dom"; import { ThemeProvider, createTheme } from "@mui/material"; -import { Content, MainMenu } from './components'; -import { Files, Links, Texts } from './pages'; +import { Content, MainMenu } from "./components"; +import { Files, Links, Texts } from "./pages"; +import AppContext from "./app_context"; +import { useEffect, useState } from "react"; +import { App as ShortApp } from "./common/data.types"; +import { appRepository } from "./repositories"; +import { config } from "./core"; const theme = createTheme({ - spacing: 16, + spacing: 16, }); function App() { - return - -
- - - } /> - } /> - } /> - -
-
-
; + const [isLoading, setIsLoading] = useState(true); + const [appContextValue, setAppContextValue] = useState(); + useEffect(() => { + (async() => { + const app = await appRepository.get() + setAppContextValue(app); + config.domain = app.domain; + setIsLoading(false); + })(); + }, []); + + return ( + + + +
+ {!isLoading ? <> + + } /> + } /> + } /> + + : null} +
+
+
+
+ ); } export default App; diff --git a/front/src/app_context.tsx b/front/src/app_context.tsx new file mode 100644 index 0000000..997b5f2 --- /dev/null +++ b/front/src/app_context.tsx @@ -0,0 +1,6 @@ +import { createContext } from "react"; +import { App as ShortApp } from "./common/data.types"; + +const LinksContext = createContext(undefined); + +export default LinksContext; \ No newline at end of file diff --git a/front/src/common/api.types.ts b/front/src/common/api.types.ts index 6e2ddf7..03bbb9c 100644 --- a/front/src/common/api.types.ts +++ b/front/src/common/api.types.ts @@ -1,8 +1,12 @@ -export type Status = { - status: "up"; +export type Config = { + domain: string; version: string; }; +export type Status = { + status: "up" | "down"; +}; + export type Link = { id: string; link: string; diff --git a/front/src/common/data.types.ts b/front/src/common/data.types.ts index 3fd9a01..33f3502 100644 --- a/front/src/common/data.types.ts +++ b/front/src/common/data.types.ts @@ -2,6 +2,8 @@ import { Link as ShortPasteLink, File as ShortPasteFile, Text as ShortPasteText, + Config as ShortPasteConfig, + Status as ShortPasteStatus, } from "./api.types"; type ShortenedData = { @@ -13,3 +15,5 @@ export type Link = ShortPasteLink & ShortenedData & {}; export type File = ShortPasteFile & ShortenedData & {}; export type Text = ShortPasteText & ShortenedData & {}; + +export type App = ShortPasteConfig & ShortPasteStatus; diff --git a/front/src/core/api.ts b/front/src/core/api.ts index 84f2be5..c7aed01 100644 --- a/front/src/core/api.ts +++ b/front/src/core/api.ts @@ -4,6 +4,7 @@ import { Text as ShortPasteText, File as ShortPasteFile, Status, + Config, } from "../common/api.types"; import config from "./config"; @@ -79,6 +80,10 @@ class Api { public getStatus(): Promise> { return this._axiosInstance.get("/status"); } + + public getConfig(): Promise> { + return this._axiosInstance.get("/config"); + } } export default new Api(); diff --git a/front/src/core/config.ts b/front/src/core/config.ts index ace2741..ce11ecc 100644 --- a/front/src/core/config.ts +++ b/front/src/core/config.ts @@ -1,4 +1,6 @@ export default class Config { + static domain: string = ""; + static get apiKey(): string { return process.env.REACT_APP_API_KEY || "%shortpaste-api-key-placeholder%"; } @@ -19,4 +21,11 @@ export default class Config { } return `${window.location.origin}${baseURL}`; } + + static get shortenURL(): string { + if (this.domain !== "") { + return this.domain; + } + return this.baseURL; + } } diff --git a/front/src/repositories/app_repository.ts b/front/src/repositories/app_repository.ts new file mode 100644 index 0000000..e9b80da --- /dev/null +++ b/front/src/repositories/app_repository.ts @@ -0,0 +1,11 @@ +import { api, config } from "../core"; +import { App as ShortApp } from "../common/data.types"; + +export default class AppRepository { + static async get(): Promise { + const { data: apiStatus } = await api.getStatus(); + const { data: apiConfig } = await api.getConfig(); + + return { ...apiStatus, ...apiConfig }; + } +} diff --git a/front/src/repositories/files_repository.ts b/front/src/repositories/files_repository.ts index f8eb17c..d06fc3d 100644 --- a/front/src/repositories/files_repository.ts +++ b/front/src/repositories/files_repository.ts @@ -24,6 +24,6 @@ export default class FilesRepository { } static getShortURL(f: ShortPasteFile): string { - return `${config.baseURL}/f/${f.id}`; + return `${config.shortenURL}/f/${f.id}`; } } diff --git a/front/src/repositories/index.ts b/front/src/repositories/index.ts index c68d15c..bad35ea 100644 --- a/front/src/repositories/index.ts +++ b/front/src/repositories/index.ts @@ -1,3 +1,4 @@ +export { default as appRepository } from "./app_repository"; export { default as linksRepository } from "./links_repository"; export { default as textsRepository } from "./texts_repository"; export { default as filesRepository } from "./files_repository"; diff --git a/front/src/repositories/links_repository.ts b/front/src/repositories/links_repository.ts index 55d4e7c..17a643c 100644 --- a/front/src/repositories/links_repository.ts +++ b/front/src/repositories/links_repository.ts @@ -24,6 +24,6 @@ export default class LinksRepository { } static getShortURL(l: ShortPasteLink): string { - return `${config.baseURL}/l/${l.id}`; + return `${config.shortenURL}/l/${l.id}`; } } diff --git a/front/src/repositories/texts_repository.ts b/front/src/repositories/texts_repository.ts index f804085..21cd873 100644 --- a/front/src/repositories/texts_repository.ts +++ b/front/src/repositories/texts_repository.ts @@ -24,6 +24,6 @@ export default class TextsRepository { } static getShortURL(t: ShortPasteText): string { - return `${config.baseURL}/t/${t.id}`; + return `${config.shortenURL}/t/${t.id}`; } } diff --git a/server/api/api.go b/server/api/api.go index 629502e..fcb1bd2 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -9,6 +9,9 @@ func Router(r chi.Router) { r.Use(middleware.SetHeader("Content-Type", "application/json")) r.Use(CheckApiKey) + r.Get(`/config`, ConfigList) + r.Get(`/status`, StatusList) + r.Get(`/links`, LinksList) r.Post(`/links`, LinkAdd) r.Delete(`/links/{id}`, LinkDelete) diff --git a/server/api/config.go b/server/api/config.go new file mode 100644 index 0000000..b291b12 --- /dev/null +++ b/server/api/config.go @@ -0,0 +1,16 @@ +package api + +import ( + "encoding/json" + "net/http" + "shortpaste/core/config" +) + +func ConfigList(w http.ResponseWriter, r *http.Request) { + var list = map[string]interface{}{ + "domain": config.GetDomain(), + "version": config.AppVersion(), + } + body, _ := json.Marshal(list) + w.Write(body) +} diff --git a/server/api/status.go b/server/api/status.go index 8917bf6..d0fdc0b 100644 --- a/server/api/status.go +++ b/server/api/status.go @@ -3,13 +3,11 @@ package api import ( "encoding/json" "net/http" - "shortpaste/core/config" ) func StatusList(w http.ResponseWriter, r *http.Request) { var list = map[string]interface{}{ - "status": "up", - "version": config.AppVersion(), + "status": "up", } body, _ := json.Marshal(list) w.Write(body) diff --git a/server/core/config/domain.go b/server/core/config/domain.go new file mode 100644 index 0000000..12290b3 --- /dev/null +++ b/server/core/config/domain.go @@ -0,0 +1,8 @@ +package config + +import "os" + +func GetDomain() string { + domain := os.Getenv("DOMAIN") + return domain +} diff --git a/server/core/config/domain_test.go b/server/core/config/domain_test.go new file mode 100644 index 0000000..f30e00c --- /dev/null +++ b/server/core/config/domain_test.go @@ -0,0 +1,18 @@ +package config + +import ( + "os" + "testing" +) + +func TestGetDomain(t *testing.T) { + os.Setenv("DOMAIN", "") + + domain := GetDomain() + + expectedDomain := "" + + if expectedDomain != domain { + t.Fatalf("Expected base path %s, but got %s", expectedDomain, domain) + } +}