diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a38f02b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - main + pull_request: + branches: + - main + release: + types: + - created + workflow_dispatch: + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and Push Docker Image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + tags: | + bacherik/killedby:${{ github.event_name == 'push' && 'latest' || github.event_name == 'release' && github.event.release.tag_name || 'pr-' }}${{ github.event.pull_request.number || '' }} + + - name: Logout from DockerHub + run: docker logout \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 9917562..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Deploy to GitHub Pages - -on: - push: - branches: [main] - workflow_dispatch: - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - - permissions: - contents: write - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - - name: Install dependencies - run: go mod tidy - - - name: Build static site - run: go run main.go - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - # If you're changing the branch from main, - # also change the `main` in `refs/heads/main` - # below accordingly. - if: github.ref == 'refs/heads/main' - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./output \ No newline at end of file diff --git a/.gitignore b/.gitignore index eaa2640..a83080d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -output/ +cache/ # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..24893b5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# Use an official Golang runtime as a parent image +FROM golang:1.22.4-alpine + +# Set the working directory inside the container +WORKDIR /app + +# Copy the go.mod and go.sum files into the working directory +COPY go.mod go.sum ./ + +# Install dependencies +RUN go mod download + +# Copy the entire project and build it +COPY . . + +# Build the Go app +RUN go build -o /killedby + +# Expose port 8080 to the outside world +EXPOSE 8080 + +# Command to run the executable +CMD ["/killedby"] diff --git a/assets/css/custom.css b/assets/css/custom.css deleted file mode 100644 index fb79235..0000000 --- a/assets/css/custom.css +++ /dev/null @@ -1,19 +0,0 @@ -body { - font-family: Arial, sans-serif; -} - -header { - background-color: #f8f8f8; - padding: 10px 0; - text-align: center; -} - -footer { - background-color: #f8f8f8; - padding: 10px 0; - text-align: center; -} - -main { - padding: 20px; -} diff --git a/assets/img/BachErik.png b/assets/img/BachErik.png deleted file mode 100644 index bf1fcc2..0000000 Binary files a/assets/img/BachErik.png and /dev/null differ diff --git a/companies/BachErik.json b/companies/BachErik.json deleted file mode 100644 index 55eab14..0000000 --- a/companies/BachErik.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "name": "AnkiLang", - "description": "", - "link": "https://ankilang.bacherik.de", - "type": "software", - "company": "BachErik", - "dateOpen": "2024.06.05", - "dateClose": "2024.06.08" - }, - { - "name": "Websitetool", - "description": "", - "link": "https://github.com/BachErik/Websitetool", - "type": "software", - "company": "BachErik", - "dateOpen": "2023.01.31", - "dateClose": "2023.03.20" - }, - { - "name": "PABW", - "description": "", - "link": "https://github.com/BachErik/PABW", - "type": "software", - "company": "BachErik", - "dateOpen": "2022.12.20", - "dateClose": "2023.03.18" - }, - { - "name": "WinDoofOS", - "description": "", - "link": "https://github.com/BachErik/WinDoofOS", - "type": "software", - "company": "BachErik", - "dateOpen": "2022.01.14", - "dateClose": "2023.03.18" - }, - { - "name": "PC", - "description": "", - "link": "https://github.com/BachErik/PC", - "type": "software", - "company": "BachErik", - "dateOpen": "2024.06.08", - "dateClose": "2024.06.09" - }, - { - "name": "Partner-Bot", - "description": "", - "link": "https://github.com/BachErik/Partner-Bot", - "type": "software", - "company": "BachErik", - "dateOpen": "2022.03.17", - "dateClose": "2023.02.19" - }, - { - "name": "BanSystem", - "description": "", - "link": "https://github.com/BachErik-Team/BanSystem", - "type": "software", - "company": "BachErik", - "dateOpen": "2023.09.08", - "dateClose": "2024.01.08" - } -] \ No newline at end of file diff --git a/main.go b/main.go index d838e65..71738c1 100644 --- a/main.go +++ b/main.go @@ -2,25 +2,26 @@ package main import ( "encoding/json" + "errors" + "fmt" "html/template" - "image" - "image/png" + "io" + "io/ioutil" "log" + "net/http" "os" "path/filepath" - - _ "image/jpeg" - _ "image/png" - "io" - - "github.com/nfnt/resize" + "sort" + "strings" + "time" ) -type Type struct { - Types map[string]string `json:"types"` - Companies map[string]string `json:"companies"` -} +var githubUsername = "bacherik" +var githubRepository = "killedby.json" +var cacheDir = "cache" +var cacheDuration = 12 * time.Hour +// Define the structs for JSON data type Project struct { Name string `json:"name"` Description string `json:"description"` @@ -31,169 +32,257 @@ type Project struct { DateClose string `json:"dateClose"` } -type Data struct { - Projects []Project - Types map[string]string +type Company struct { + Logo string `json:"logo"` + Description string `json:"description"` + Projects []Project `json:"projects"` +} + +type ProjectType struct { + Name string `json:"name"` + Color string `json:"color"` +} + +type BasePageData struct { + Title string Companies map[string]string } +type IndexPageData struct { + BasePageData + Projects []Project + Types map[string]string +} + +type CompanyPageData struct { + BasePageData + Projects []Project + Types map[string]string +} + +type ProjectPageData struct { + BasePageData + Project Project +} + func main() { - typesFile, err := os.ReadFile("types_and_companies.json") - if err != nil { - log.Fatalf("Error reading types and companies file: %v", err) - } + // URLs for the JSON files and other initial setup + companiesURL := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/main/config/companies.json", githubUsername, githubRepository) + typesURL := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/main/config/types.json", githubUsername, githubRepository) - var types Type - err = json.Unmarshal(typesFile, &types) + // Unmarshal company configs + companyConfig := make(map[string]Company) + err := fetchJSON(companiesURL, "companies.json", &companyConfig) if err != nil { - log.Fatalf("Error unmarshaling types and companies JSON: %v", err) + log.Fatal("Error fetching company config:", err) } - data := Data{ - Types: types.Types, - Companies: types.Companies, + // Unmarshal project type configs + projectTypes := make(map[string]string) + err = fetchJSON(typesURL, "types.json", &projectTypes) + if err != nil { + log.Fatal("Error fetching project type config:", err) } - // Read company project files - for company := range types.Companies { - companyProjectsFile := filepath.Join("companies", company+".json") - projectsFile, err := os.ReadFile(companyProjectsFile) + // Fetch and unmarshal projects for each company + var allProjects []Project + for companyName := range companyConfig { + projectsURL := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/main/companies/%s.json", githubUsername, githubRepository, companyName) + var projects []Project + cacheFileName := fmt.Sprintf("%s.json", companyName) + err := fetchJSON(projectsURL, cacheFileName, &projects) if err != nil { - log.Fatalf("Error reading projects file for company %s: %v", company, err) + log.Fatalf("Error fetching projects for %s: %v", companyName, err) } - var companyProjects []Project - err = json.Unmarshal(projectsFile, &companyProjects) - if err != nil { - log.Fatalf("Error unmarshaling projects JSON for company %s: %v", company, err) - } - - data.Projects = append(data.Projects, companyProjects...) + // Add projects to the company in the map + company := companyConfig[companyName] + company.Projects = projects + companyConfig[companyName] = company + allProjects = append(allProjects, projects...) } - indexTmpl := template.Must(template.ParseFiles("templates/indexTemplate.html")) - projectTmpl := template.Must(template.New("project").Parse(projectTemplate)) + // Sort projects by date + sort.Slice(allProjects, func(i, j int) bool { + return allProjects[i].DateClose > allProjects[j].DateClose + }) - // Create output directory if it doesn't exist - err = os.MkdirAll("output", os.ModePerm) - if err != nil { - log.Fatalf("Error creating output directory: %v", err) + funcMap := template.FuncMap{ + "dict": func(values ...interface{}) (map[string]interface{}, error) { + if len(values)%2 != 0 { + return nil, errors.New("invalid dict call") + } + dict := make(map[string]interface{}) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].(string) + if !ok { + return nil, errors.New("dict keys must be strings") + } + dict[key] = values[i+1] + } + return dict, nil + }, } - // Copy and resize assets to output directory - err = copyAndResizeAssets("assets", "output/assets", 100) // Resize to 100px width + tmpl, err := template.New("").Funcs(funcMap).ParseGlob("templates/*.html") if err != nil { - log.Fatalf("Error copying and resizing assets: %v", err) + log.Fatalf("Error parsing templates: %v", err) } - f, err := os.Create("output/index.html") - if err != nil { - log.Fatalf("Error creating output file: %v", err) - } - defer f.Close() + // Handlers + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + pageData := IndexPageData{ + BasePageData: BasePageData{ + Title: "Companies and Projects", + Companies: make(map[string]string), + }, + Projects: allProjects, + Types: projectTypes, + } - err = indexTmpl.Execute(f, data) - if err != nil { - log.Fatalf("Error executing index template: %v", err) - } + for companyName, company := range companyConfig { + pageData.Companies[companyName] = company.Logo + } - for _, project := range data.Projects { - companyDir := filepath.Join("output", project.Company) - err = os.MkdirAll(companyDir, os.ModePerm) + err := tmpl.ExecuteTemplate(w, "index", pageData) if err != nil { - log.Fatalf("Error creating company directory: %v", err) + log.Printf("Error executing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + }) + + http.HandleFunc("/company/", func(w http.ResponseWriter, r *http.Request) { + companyName := strings.TrimPrefix(r.URL.Path, "/company/") + company, ok := companyConfig[companyName] + if !ok { + http.NotFound(w, r) + return } - projectFile, err := os.Create(filepath.Join(companyDir, project.Name+".html")) + companyPageData := CompanyPageData{ + BasePageData: BasePageData{ + Title: companyName, + Companies: make(map[string]string), + }, + Projects: company.Projects, + Types: projectTypes, + } + + for companyName, company := range companyConfig { + companyPageData.Companies[companyName] = company.Logo + } + + err := tmpl.ExecuteTemplate(w, "company", companyPageData) if err != nil { - log.Fatalf("Error creating project file: %v", err) + log.Printf("Error executing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + }) + + http.HandleFunc("/project/", func(w http.ResponseWriter, r *http.Request) { + projectPath := strings.TrimPrefix(r.URL.Path, "/project/") + parts := strings.SplitN(projectPath, "/", 2) + if len(parts) < 2 { + http.NotFound(w, r) + return + } + + companyName, projectName := parts[0], parts[1] + company, ok := companyConfig[companyName] + if !ok { + http.NotFound(w, r) + return + } + + var project Project + found := false + for _, p := range company.Projects { + if p.Name == projectName { + project = p + found = true + break + } + } + if !found { + http.NotFound(w, r) + return + } + + projectPageData := ProjectPageData{ + BasePageData: BasePageData{ + Title: projectName, + Companies: make(map[string]string), + }, + Project: project, + } + + for companyName, company := range companyConfig { + projectPageData.Companies[companyName] = company.Logo } - defer projectFile.Close() - err = projectTmpl.Execute(projectFile, project) + err := tmpl.ExecuteTemplate(w, "project", projectPageData) if err != nil { - log.Fatalf("Error executing project template: %v", err) + log.Printf("Error executing template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) } - } + }) + + fmt.Println("Starting server at :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) } -func copyAndResizeAssets(src, dst string, width uint) error { - return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { +func fetchJSON(url, cacheFileName string, v interface{}) error { + // Create cache directory if it doesn't exist + if _, err := os.Stat(cacheDir); os.IsNotExist(err) { + err := os.Mkdir(cacheDir, 0755) if err != nil { return err } - relPath := path[len(src):] - targetPath := filepath.Join(dst, relPath) - if info.IsDir() { - return os.MkdirAll(targetPath, info.Mode()) - } else { - if filepath.Ext(path) == ".png" || filepath.Ext(path) == ".jpg" || filepath.Ext(path) == ".jpeg" { - return resizeImage(path, targetPath, width) + } + + cacheFilePath := filepath.Join(cacheDir, cacheFileName) + + // Check if the cached file exists and is still valid + if info, err := os.Stat(cacheFilePath); err == nil { + if time.Since(info.ModTime()) < cacheDuration { + // Read from the cached file + bytes, err := ioutil.ReadFile(cacheFilePath) + if err != nil { + return err } - return copyFile(path, targetPath) + return json.Unmarshal(bytes, v) } - }) -} - -func resizeImage(src, dst string, width uint) error { - file, err := os.Open(src) - if err != nil { - return err } - defer file.Close() - img, _, err := image.Decode(file) + fmt.Print("Fetching ", url, "... ") + + // Fetch from the URL + resp, err := http.Get(url) if err != nil { return err } + defer resp.Body.Close() - m := resize.Resize(width, 0, img, resize.Lanczos3) + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } - out, err := os.Create(dst) + bytes, err := io.ReadAll(resp.Body) if err != nil { return err } - defer out.Close() - - return png.Encode(out, m) -} -func copyFile(src, dst string) error { - sourceFile, err := os.Open(src) + // Unmarshal the JSON data + err = json.Unmarshal(bytes, v) if err != nil { return err } - defer sourceFile.Close() - destFile, err := os.Create(dst) + // Write the data to the cache file + err = ioutil.WriteFile(cacheFilePath, bytes, 0644) if err != nil { return err } - defer destFile.Close() - _, err = io.Copy(destFile, sourceFile) - return err + return nil } - -const projectTemplate = ` - - - - - {{ .Name }} - - - -
-

{{ .Name }}

-
-
-

{{ .Description }}

-

{{ .Link }}

-

Opened: {{ .DateOpen }} Closed: {{ .DateClose }}

-
- - -` diff --git a/templates/company.html b/templates/company.html new file mode 100644 index 0000000..fbf5fdf --- /dev/null +++ b/templates/company.html @@ -0,0 +1,10 @@ +{{ define "company" }} +{{ template "header" . }} +

Projects by {{ .Title }}

+
+ {{ range .Projects }} + {{ template "project_card" dict "Project" . "Companies" $.Companies "Types" $.Types }} + {{ end }} +
+{{ template "footer" . }} +{{ end }} diff --git a/templates/footer.html b/templates/footer.html index fb55ff3..7d95c23 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -1,6 +1,8 @@ - - +{{ define "footer" }} + + +{{ end }} diff --git a/templates/header.html b/templates/header.html index 4edfc17..3be0249 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,19 +1,117 @@ +{{ define "header" }} - My Static Site + {{ .Title }} +
-

Welcome to My Static Site

- +

Killed by

+
+{{ end }} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..2353306 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,10 @@ +{{ define "index" }} +{{ template "header" . }} +

Projects

+
+ {{ range .Projects }} + {{ template "project_card" dict "Project" . "Companies" $.Companies "Types" $.Types }} + {{ end }} +
+{{ template "footer" . }} +{{ end }} diff --git a/templates/indexTemplate.html b/templates/indexTemplate.html deleted file mode 100644 index bd27d95..0000000 --- a/templates/indexTemplate.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Projects - - -
-

Companies

- -
-
-

Projects

- -
- - - diff --git a/templates/project.html b/templates/project.html new file mode 100644 index 0000000..720fe99 --- /dev/null +++ b/templates/project.html @@ -0,0 +1,38 @@ +{{ define "project" }} +{{ template "header" . }} +
+

{{ .Project.Name }}

+
+

Description: {{ .Project.Description }}

+

Released: {{ .Project.DateOpen }}

+

Discontinued: {{ .Project.DateClose }}

+

Company: {{ .Project.Company }}

+

Source: {{ .Project.Link }}

+
+
+{{ template "footer" . }} + +{{ end }} diff --git a/templates/project_card.html b/templates/project_card.html new file mode 100644 index 0000000..0edb9bf --- /dev/null +++ b/templates/project_card.html @@ -0,0 +1,19 @@ +{{ define "project_card" }} +
+ + +

{{ .Project.Name }}

+

+ {{ if gt (len .Project.Description) 100 }} + {{ slice .Project.Description 0 100 }}... + {{ else }} + {{ .Project.Description }} + {{ end }} +

+
+ {{ .Project.DateOpen }} - {{ .Project.DateClose }} + {{ .Project.Type }} +
+
+
+{{ end }} diff --git a/types_and_companies.json b/types_and_companies.json deleted file mode 100644 index 92b9a28..0000000 --- a/types_and_companies.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "types": { - "software": "#ff0000", - "hardware": "#00ff00", - "other": "#0000ff" - }, - "companies": { - "BachErik": "assets/img/BachErik.png" - } -}