switch to go backend
All checks were successful
Build and Deploy Website / build (push) Successful in 2m26s
@@ -5,11 +5,8 @@ on:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'Sources/**'
|
||||
- 'Public/**'
|
||||
- 'Resources/**'
|
||||
- 'Package.swift'
|
||||
- 'Package.resolved'
|
||||
- 'Backend/**'
|
||||
- 'Frontend/**'
|
||||
- 'Dockerfile'
|
||||
- '.gitea/workflows/build-and-deploy.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
10
.gitignore
vendored
@@ -1,2 +1,10 @@
|
||||
.vscode
|
||||
.build
|
||||
.DS_Store
|
||||
|
||||
# Frontend
|
||||
Frontend/node_modules
|
||||
Frontend/dist
|
||||
|
||||
# Backend
|
||||
Backend/server
|
||||
Backend/public/app/
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>smparkin.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>AppTests</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>Run</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
3
Backend/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module smpark.in
|
||||
|
||||
go 1.23
|
||||
31
Backend/main.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
publicDir := "./public"
|
||||
spaIndex := filepath.Join(publicDir, "app", "index.html")
|
||||
|
||||
fs := http.FileServer(http.Dir(publicDir))
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Try to serve the static file
|
||||
path := filepath.Join(publicDir, filepath.Clean(r.URL.Path))
|
||||
if info, err := os.Stat(path); err == nil && !info.IsDir() {
|
||||
fs.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Fall back to SPA index for client-side routing
|
||||
http.ServeFile(w, r, spaIndex)
|
||||
})
|
||||
|
||||
addr := ":8080"
|
||||
log.Printf("Listening on %s", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, nil))
|
||||
}
|
||||
4
Backend/public/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<rect width="100" height="100" rx="16" fill="#9370db"/>
|
||||
<text x="50" y="68" font-family="system-ui, sans-serif" font-size="48" font-weight="bold" fill="white" text-anchor="middle">SP</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 544 KiB After Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
90
Dockerfile
@@ -1,77 +1,41 @@
|
||||
# ================================
|
||||
# Build image
|
||||
# Frontend build
|
||||
# ================================
|
||||
FROM swift:6.1.0-noble AS build
|
||||
FROM node:22-slim AS frontend
|
||||
|
||||
# Install OS updates and, if needed, sqlite3
|
||||
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
||||
&& apt-get -q update \
|
||||
&& apt-get -q dist-upgrade -y\
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /frontend
|
||||
COPY Frontend/package.json Frontend/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY Frontend/ ./
|
||||
RUN npm run build -- --outDir dist --emptyOutDir
|
||||
|
||||
# ================================
|
||||
# Backend build
|
||||
# ================================
|
||||
FROM golang:1.23-alpine AS backend
|
||||
|
||||
# Set up a build area
|
||||
WORKDIR /build
|
||||
|
||||
# First just resolve dependencies.
|
||||
# This creates a cached layer that can be reused
|
||||
# as long as your Package.swift/Package.resolved
|
||||
# files do not change.
|
||||
COPY ./Package.* ./
|
||||
RUN swift package resolve
|
||||
|
||||
# Copy entire repo into container
|
||||
COPY . .
|
||||
|
||||
# Build everything, with optimizations
|
||||
RUN swift build -c release --static-swift-stdlib
|
||||
|
||||
# Switch to the staging area
|
||||
WORKDIR /staging
|
||||
|
||||
# Copy main executable to staging area
|
||||
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/Run" ./
|
||||
|
||||
# Copy resources bundled by SPM to staging area
|
||||
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;
|
||||
|
||||
# Copy any resources from the public directory and views directory if the directories exist
|
||||
# Ensure that by default, neither the directory nor any of its contents are writable.
|
||||
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
|
||||
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true
|
||||
COPY Backend/go.mod ./
|
||||
COPY Backend/main.go ./
|
||||
RUN CGO_ENABLED=0 go build -o server .
|
||||
|
||||
# ================================
|
||||
# Run image
|
||||
# Runtime
|
||||
# ================================
|
||||
FROM ubuntu:noble
|
||||
FROM alpine:3.21
|
||||
|
||||
# Make sure all system packages are up to date, and install only essential packages.
|
||||
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
||||
&& apt-get -q update \
|
||||
&& apt-get -q dist-upgrade -y \
|
||||
&& apt-get -q install -y \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
|
||||
# libcurl4 \
|
||||
# If your app or its dependencies import FoundationXML, also install `libxml2`.
|
||||
# libxml2 \
|
||||
&& rm -r /var/lib/apt/lists/*
|
||||
|
||||
# Create a vapor user and group with /app as its home directory
|
||||
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor
|
||||
|
||||
# Switch to the new home directory
|
||||
RUN adduser -D -h /app app
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built executable and any staged resources from builder
|
||||
COPY --from=build --chown=vapor:vapor /staging /app
|
||||
# Copy Go binary
|
||||
COPY --from=backend --chown=app:app /build/server ./
|
||||
|
||||
# Ensure all further commands run as the vapor user
|
||||
USER vapor:vapor
|
||||
# Copy static assets
|
||||
COPY --chown=app:app Backend/public/ ./public/
|
||||
|
||||
# Let Docker bind to port 8080
|
||||
# Copy built frontend
|
||||
COPY --from=frontend --chown=app:app /frontend/dist ./public/app/
|
||||
|
||||
USER app
|
||||
EXPOSE 8080
|
||||
|
||||
# Start the Vapor service when the image is run, default to listening on 8080 in production environment
|
||||
ENTRYPOINT ["./Run"]
|
||||
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
|
||||
ENTRYPOINT ["./server"]
|
||||
|
||||
24
Frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
7
Frontend/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100
|
||||
}
|
||||
23
Frontend/eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
16
Frontend/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Stephen Parkinson</title>
|
||||
</head>
|
||||
<body style="margin: 0">
|
||||
<script>
|
||||
document.body.style.backgroundColor = window.matchMedia("(prefers-color-scheme: dark)").matches ? "#161d26" : "#ffffff";
|
||||
</script>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
3740
Frontend/package-lock.json
generated
Normal file
36
Frontend/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "prettier --write src/ && tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write src/",
|
||||
"format:check": "prettier --check src/",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudscape-design/components": "^3.0.1200",
|
||||
"@cloudscape-design/global-styles": "^1.0.50",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.48.0",
|
||||
"vite": "^7.3.1"
|
||||
}
|
||||
}
|
||||
19
Frontend/src/App.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.profile-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
display: block;
|
||||
}
|
||||
48
Frontend/src/App.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { BrowserRouter, Routes, Route, useNavigate, useLocation } from "react-router-dom";
|
||||
import AppLayout from "@cloudscape-design/components/app-layout";
|
||||
import SideNavigation from "@cloudscape-design/components/side-navigation";
|
||||
import "@cloudscape-design/global-styles/index.css";
|
||||
import "./App.css";
|
||||
import Home from "./pages/Home";
|
||||
import Resume from "./pages/Resume";
|
||||
import NotFound from "./pages/NotFound";
|
||||
|
||||
function AppContent() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<AppLayout
|
||||
toolsHide
|
||||
navigation={
|
||||
<SideNavigation
|
||||
header={{ text: "Stephen Parkinson", href: "/" }}
|
||||
activeHref={location.pathname}
|
||||
onFollow={(event) => {
|
||||
event.preventDefault();
|
||||
navigate(event.detail.href);
|
||||
}}
|
||||
items={[
|
||||
{ type: "link", text: "Home", href: "/" },
|
||||
{ type: "link", text: "Resume", href: "/resume" },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
content={
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/resume" element={<Resume />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<AppContent />
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
17
Frontend/src/main.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { applyMode, Mode } from "@cloudscape-design/global-styles";
|
||||
|
||||
import App from "./App.tsx";
|
||||
|
||||
const darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
applyMode(darkQuery.matches ? Mode.Dark : Mode.Light);
|
||||
darkQuery.addEventListener("change", (e) => {
|
||||
applyMode(e.matches ? Mode.Dark : Mode.Light);
|
||||
});
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
42
Frontend/src/pages/Home.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import ContentLayout from "@cloudscape-design/components/content-layout";
|
||||
import Header from "@cloudscape-design/components/header";
|
||||
import Container from "@cloudscape-design/components/container";
|
||||
import SpaceBetween from "@cloudscape-design/components/space-between";
|
||||
import Link from "@cloudscape-design/components/link";
|
||||
import Box from "@cloudscape-design/components/box";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<ContentLayout
|
||||
header={
|
||||
<div className="page-header">
|
||||
<img src="/images/me.jpg" alt="Stephen Parkinson" className="profile-image" />
|
||||
<Header variant="h1" description="Systems Development Engineer">
|
||||
Stephen Parkinson
|
||||
</Header>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SpaceBetween size="l">
|
||||
<Container header={<Header variant="h2">About Me</Header>}>
|
||||
<Box variant="p">I'm doing a lot of stuff with computers right now.</Box>
|
||||
</Container>
|
||||
|
||||
<Container>
|
||||
<img src="/images/trees.jpg" alt="Trees" className="hero-image" />
|
||||
</Container>
|
||||
|
||||
<Container header={<Header variant="h2">Contact</Header>}>
|
||||
<SpaceBetween size="s" direction="horizontal">
|
||||
<Link href="https://www.linkedin.com/in/stephen-parkinson" external>
|
||||
LinkedIn
|
||||
</Link>
|
||||
<Link href="https://github.com/smparkin" external>
|
||||
GitHub
|
||||
</Link>
|
||||
</SpaceBetween>
|
||||
</Container>
|
||||
</SpaceBetween>
|
||||
</ContentLayout>
|
||||
);
|
||||
}
|
||||
14
Frontend/src/pages/NotFound.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import ContentLayout from "@cloudscape-design/components/content-layout";
|
||||
import Header from "@cloudscape-design/components/header";
|
||||
import Box from "@cloudscape-design/components/box";
|
||||
import Link from "@cloudscape-design/components/link";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<ContentLayout header={<Header variant="h1">404</Header>}>
|
||||
<Box variant="p">
|
||||
The page you're looking for doesn't exist. <Link href="/">Go home</Link>
|
||||
</Box>
|
||||
</ContentLayout>
|
||||
);
|
||||
}
|
||||
137
Frontend/src/pages/Resume.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import ContentLayout from "@cloudscape-design/components/content-layout";
|
||||
import Header from "@cloudscape-design/components/header";
|
||||
import Container from "@cloudscape-design/components/container";
|
||||
import SpaceBetween from "@cloudscape-design/components/space-between";
|
||||
import Box from "@cloudscape-design/components/box";
|
||||
import ColumnLayout from "@cloudscape-design/components/column-layout";
|
||||
|
||||
export default function Resume() {
|
||||
return (
|
||||
<ContentLayout header={<Header variant="h1">Resume</Header>}>
|
||||
<SpaceBetween size="l">
|
||||
<Container header={<Header variant="h2">Employment</Header>}>
|
||||
<SpaceBetween size="l">
|
||||
<SpaceBetween size="xxs">
|
||||
<Header variant="h3">Systems Development Engineer, Amazon Security</Header>
|
||||
<Box variant="small" color="text-body-secondary">
|
||||
San Luis Obispo, CA · April 2020 – Present (Promoted to full-time July
|
||||
2022)
|
||||
</Box>
|
||||
<Box variant="p">
|
||||
<ul>
|
||||
<li>
|
||||
Create and deploy serverless applications to collect and enrich data for
|
||||
analysis.
|
||||
</li>
|
||||
<li>
|
||||
Add monitoring and alarming to existing services to allow for easier diagnosing
|
||||
of potential issues.
|
||||
</li>
|
||||
<li>
|
||||
Work with customers to understand requirements and deliver results quickly.
|
||||
</li>
|
||||
</ul>
|
||||
</Box>
|
||||
</SpaceBetween>
|
||||
|
||||
<SpaceBetween size="xxs">
|
||||
<Header variant="h3">Student Assistant, On-Site Support Cal Poly ITS</Header>
|
||||
<Box variant="small" color="text-body-secondary">
|
||||
San Luis Obispo, CA · May 2019 – February 2020
|
||||
</Box>
|
||||
<Box variant="p">
|
||||
<ul>
|
||||
<li>
|
||||
Responsible for support of computer services and equipment across multiple
|
||||
departments.
|
||||
</li>
|
||||
<li>Troubleshooting, detecting, and solving of technical problems.</li>
|
||||
<li>
|
||||
Managing macOS and Windows based computers using Active Directory, SCCM, and
|
||||
Jamf.
|
||||
</li>
|
||||
</ul>
|
||||
</Box>
|
||||
</SpaceBetween>
|
||||
</SpaceBetween>
|
||||
</Container>
|
||||
|
||||
<Container header={<Header variant="h2">Education</Header>}>
|
||||
<SpaceBetween size="l">
|
||||
<SpaceBetween size="xxs">
|
||||
<Header variant="h3">California Polytechnic State University</Header>
|
||||
<Box variant="small" color="text-body-secondary">
|
||||
2017 – 2022
|
||||
</Box>
|
||||
<Box variant="p">
|
||||
<strong>Software Engineering</strong>
|
||||
<br />
|
||||
Relevant Coursework: Introduction to Computing, Fundamentals of Computer Science,
|
||||
Data Structures, Project-Based Object-Oriented Programming & Design,
|
||||
Introduction to Computer Organization, Systems Programming, and Introduction to
|
||||
Operating Systems.
|
||||
</Box>
|
||||
</SpaceBetween>
|
||||
|
||||
<SpaceBetween size="xxs">
|
||||
<Header variant="h3">Cal Poly Security Education Club — President</Header>
|
||||
<Box variant="small" color="text-body-secondary">
|
||||
Spring 2020 – Spring 2021
|
||||
</Box>
|
||||
<Box variant="p">
|
||||
<ul>
|
||||
<li>
|
||||
Responsible for planning and logistics of events including iFixit Triathlon and
|
||||
Security Career Fair.
|
||||
</li>
|
||||
<li>
|
||||
Manage a team of student officers to introduce students to security topics at
|
||||
all skill levels.
|
||||
</li>
|
||||
<li>Coordinate with companies for presentations and special events.</li>
|
||||
<li>
|
||||
Presented technical talks such as "iOS Security and Jailbreaking", "machswap, a
|
||||
vulnerability in XNU IPC", "Intro to SSH", "Nintendo Switch Security", "macOS
|
||||
Security", and "Mac File Systems and APFS Security".
|
||||
</li>
|
||||
</ul>
|
||||
</Box>
|
||||
</SpaceBetween>
|
||||
</SpaceBetween>
|
||||
</Container>
|
||||
|
||||
<Container header={<Header variant="h2">Skills</Header>}>
|
||||
<ColumnLayout columns={3} variant="text-grid">
|
||||
<SpaceBetween size="xxs">
|
||||
<Box variant="h3">Languages & Tools</Box>
|
||||
<Box variant="p">
|
||||
Python, TypeScript, React, JavaScript, C, Swift, git, zsh, Burp Suite
|
||||
</Box>
|
||||
</SpaceBetween>
|
||||
<SpaceBetween size="xxs">
|
||||
<Box variant="h3">AWS</Box>
|
||||
<Box variant="p">Lambda, S3, DynamoDB, CloudWatch, KMS, SSM, SES, SQS, SNS, CDK</Box>
|
||||
</SpaceBetween>
|
||||
<SpaceBetween size="xxs">
|
||||
<Box variant="h3">Operating Systems</Box>
|
||||
<Box variant="p">macOS, Windows, Linux</Box>
|
||||
</SpaceBetween>
|
||||
</ColumnLayout>
|
||||
</Container>
|
||||
|
||||
<Container header={<Header variant="h2">Projects</Header>}>
|
||||
<ul>
|
||||
<li>
|
||||
Developed a watchOS app to show the status of the White Hat lab on an Apple Watch.
|
||||
</li>
|
||||
<li>Created a Python script to control Spotify's API from the command line.</li>
|
||||
<li>
|
||||
Upgraded White Hat's network infrastructure to take advantage of the 1Gbps connection
|
||||
to the internet.
|
||||
</li>
|
||||
</ul>
|
||||
</Container>
|
||||
</SpaceBetween>
|
||||
</ContentLayout>
|
||||
);
|
||||
}
|
||||
28
Frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
Frontend/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
26
Frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
20
Frontend/vite.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: '/app/',
|
||||
build: {
|
||||
outDir: '../Backend/public/app',
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['react', 'react-dom'],
|
||||
cloudscape: ['@cloudscape-design/components', '@cloudscape-design/global-styles'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
18
Makefile
Normal file
@@ -0,0 +1,18 @@
|
||||
.PHONY: all frontend backend run dev clean
|
||||
|
||||
all: frontend backend
|
||||
|
||||
frontend:
|
||||
cd Frontend && npm run build
|
||||
|
||||
backend:
|
||||
cd Backend && go build -o server .
|
||||
|
||||
run: all
|
||||
cd Backend && ./server
|
||||
|
||||
dev:
|
||||
cd Frontend && npm run dev
|
||||
|
||||
clean:
|
||||
rm -rf Backend/server Backend/public/app
|
||||
249
Package.resolved
@@ -1,249 +0,0 @@
|
||||
{
|
||||
"originHash" : "6a244abdea5ff1ff36734246a0ac5fbc6830084dfc1322e83f9683689f7bb3fc",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "async-http-client",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/swift-server/async-http-client.git",
|
||||
"state" : {
|
||||
"revision" : "3b265e6a00fc5c3fdb8f91f773e506990c704337",
|
||||
"version" : "1.26.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "async-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/async-kit.git",
|
||||
"state" : {
|
||||
"revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31",
|
||||
"version" : "1.20.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "console-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/console-kit.git",
|
||||
"state" : {
|
||||
"revision" : "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b",
|
||||
"version" : "4.15.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "leaf",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/leaf.git",
|
||||
"state" : {
|
||||
"revision" : "d469584b9186851c5a4012d11325fb9db3207ebb",
|
||||
"version" : "4.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "leaf-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/leaf-kit.git",
|
||||
"state" : {
|
||||
"revision" : "cf186d8f2ef33e16fd1dd78df36466c22c2e632f",
|
||||
"version" : "1.13.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "multipart-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/multipart-kit.git",
|
||||
"state" : {
|
||||
"revision" : "3498e60218e6003894ff95192d756e238c01f44e",
|
||||
"version" : "4.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "routing-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/routing-kit.git",
|
||||
"state" : {
|
||||
"revision" : "93f7222c8e195cbad39fafb5a0e4cc85a8def7ea",
|
||||
"version" : "4.9.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-algorithms",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-algorithms.git",
|
||||
"state" : {
|
||||
"revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
|
||||
"version" : "1.2.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-asn1",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-asn1.git",
|
||||
"state" : {
|
||||
"revision" : "a54383ada6cecde007d374f58f864e29370ba5c3",
|
||||
"version" : "1.3.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-atomics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-atomics.git",
|
||||
"state" : {
|
||||
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections.git",
|
||||
"state" : {
|
||||
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
|
||||
"version" : "1.1.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-crypto",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-crypto.git",
|
||||
"state" : {
|
||||
"revision" : "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed",
|
||||
"version" : "3.12.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-distributed-tracing",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-distributed-tracing.git",
|
||||
"state" : {
|
||||
"revision" : "a64a0abc2530f767af15dd88dda7f64d5f1ff9de",
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-http-structured-headers",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-http-structured-headers.git",
|
||||
"state" : {
|
||||
"revision" : "f280fc7676b9940ff2c6598642751ea333c6544f",
|
||||
"version" : "1.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-http-types",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-http-types.git",
|
||||
"state" : {
|
||||
"revision" : "a0a57e949a8903563aba4615869310c0ebf14c03",
|
||||
"version" : "1.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-log",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-log.git",
|
||||
"state" : {
|
||||
"revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa",
|
||||
"version" : "1.6.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-metrics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-metrics.git",
|
||||
"state" : {
|
||||
"revision" : "4c83e1cdf4ba538ef6e43a9bbd0bcc33a0ca46e3",
|
||||
"version" : "2.7.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio.git",
|
||||
"state" : {
|
||||
"revision" : "34d486b01cd891297ac615e40d5999536a1e138d",
|
||||
"version" : "2.83.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-extras",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-extras.git",
|
||||
"state" : {
|
||||
"revision" : "f1f6f772198bee35d99dd145f1513d8581a54f2c",
|
||||
"version" : "1.26.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-http2",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-http2.git",
|
||||
"state" : {
|
||||
"revision" : "5e9e99ec96c53bc2c18ddd10c1e25a3cd97c55e5",
|
||||
"version" : "1.38.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-ssl",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-ssl.git",
|
||||
"state" : {
|
||||
"revision" : "4b38f35946d00d8f6176fe58f96d83aba64b36c7",
|
||||
"version" : "2.31.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-transport-services",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-transport-services.git",
|
||||
"state" : {
|
||||
"revision" : "cd1e89816d345d2523b11c55654570acd5cd4c56",
|
||||
"version" : "1.24.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-numerics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-numerics.git",
|
||||
"state" : {
|
||||
"revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8",
|
||||
"version" : "1.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-service-context",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-service-context.git",
|
||||
"state" : {
|
||||
"revision" : "8946c930cae601452149e45d31d8ddfac973c3c7",
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-system",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-system.git",
|
||||
"state" : {
|
||||
"revision" : "a34201439c74b53f0fd71ef11741af7e7caf01e1",
|
||||
"version" : "1.4.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "vapor",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/vapor.git",
|
||||
"state" : {
|
||||
"revision" : "87b0edd2633c35de543cb7573efe5fbf456181bc",
|
||||
"version" : "4.114.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "websocket-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/websocket-kit.git",
|
||||
"state" : {
|
||||
"revision" : "8666c92dbbb3c8eefc8008c9c8dcf50bfd302167",
|
||||
"version" : "2.16.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// swift-tools-version:6.1.0
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "smparkin",
|
||||
platforms: [
|
||||
.macOS(.v13)
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.114.0")),
|
||||
.package(url: "https://github.com/vapor/leaf.git", .upToNextMajor(from: "4.5.0")),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "App",
|
||||
dependencies: [
|
||||
.product(name: "Vapor", package: "vapor"),
|
||||
.product(name: "Leaf", package: "leaf")
|
||||
],
|
||||
swiftSettings: [
|
||||
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
|
||||
]
|
||||
),
|
||||
.executableTarget(
|
||||
name: "Run",
|
||||
dependencies: [
|
||||
.target(name: "App")
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "AppTests",
|
||||
dependencies: [
|
||||
.target(name: "App"),
|
||||
.product(name: "XCTVapor", package: "vapor")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -1,96 +0,0 @@
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#notfound {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#notfound .notfound {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.notfound {
|
||||
max-width: 410px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.notfound .notfound-404 {
|
||||
height: 280px;
|
||||
position: relative;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.notfound .notfound-404 h1 {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 230px;
|
||||
margin: 0px;
|
||||
font-weight: 900;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
-webkit-transform: translateX(-50%);
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
background: url('../images/bg.jpg') no-repeat;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
|
||||
.notfound h2 {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
color: #000;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.notfound p {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
color: #000;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.notfound a {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
background: #0046d5;
|
||||
display: inline-block;
|
||||
padding: 15px 30px;
|
||||
border-radius: 40px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
-webkit-box-shadow: 0px 4px 15px -5px #0046d5;
|
||||
box-shadow: 0px 4px 15px -5px #0046d5;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.notfound .notfound-404 {
|
||||
height: 142px;
|
||||
}
|
||||
.notfound .notfound-404 h1 {
|
||||
font-size: 112px;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 497 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 466 KiB |
@@ -1,42 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||
|
||||
<title>404</title>
|
||||
|
||||
<!-- Google font -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700,900" rel="stylesheet">
|
||||
|
||||
<!-- Custom stlylesheet -->
|
||||
<link type="text/css" rel="stylesheet" href="/css/404style.css" />
|
||||
|
||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="notfound">
|
||||
<div class="notfound">
|
||||
<div class="notfound-404">
|
||||
<h1>Oops!</h1>
|
||||
</div>
|
||||
<h2>404 - Page not found</h2>
|
||||
<p>The page you are looking for might have been removed had its name changed or is temporarily unavailable.</p>
|
||||
<a href="https://smpark.in">Home</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body><!-- This templates was made by Colorlib (https://colorlib.com) -->
|
||||
|
||||
</html>
|
||||
@@ -1,109 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Stephen Parkinson</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #121212;
|
||||
color: #ffffff;
|
||||
}
|
||||
header {
|
||||
background-color: #1e1e1e;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #9370db !important;
|
||||
}
|
||||
header h1 {
|
||||
margin: 0;
|
||||
color: #9370db !important;
|
||||
}
|
||||
header img {
|
||||
max-width: 150px;
|
||||
height: auto;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #9370db !important;
|
||||
margin: 10px 0;
|
||||
}
|
||||
a:link,
|
||||
a:visited {
|
||||
color: #9370db !important;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
a:hover {
|
||||
background-color: #9370db !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
a:active {
|
||||
background-color: #9370db !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
section {
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
background-color: #1e1e1e;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
section img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 auto 20px auto;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #9370db !important;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background-color: #1e1e1e;
|
||||
border-top: 2px solid #9370db !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img
|
||||
src="/images/me.jpg"
|
||||
alt="Stephen Parkinson"
|
||||
/>
|
||||
<h1>Stephen Parkinson</h1>
|
||||
<p>Systems Development Engineer</p>
|
||||
</header>
|
||||
<section id="about">
|
||||
<h2>About Me</h2>
|
||||
<p>I'm doing a lot of stuff with computers right now.</p>
|
||||
</section>
|
||||
<section id="image">
|
||||
<img
|
||||
src="images/trees.jpg"
|
||||
alt="Project Image"
|
||||
/>
|
||||
</section>
|
||||
<section id="contact">
|
||||
<h2>Contact</h2>
|
||||
<p>
|
||||
Connect with me on
|
||||
<a
|
||||
href="https://www.linkedin.com/in/stephen-parkinson"
|
||||
target="_blank"
|
||||
>LinkedIn</a
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
Check out my work on
|
||||
<a href="https://github.com/smparkin" target="_blank"
|
||||
>GitHub</a
|
||||
>.
|
||||
</p>
|
||||
</section>
|
||||
<footer>
|
||||
<p>© 2024 Stephen Parkinson. All rights reserved.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,107 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width'>
|
||||
<title>Privacy Policy</title>
|
||||
<style> body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; padding:1em; } </style>
|
||||
</head>
|
||||
<body>
|
||||
<strong>Privacy Policy</strong>
|
||||
<p>
|
||||
Stephen Parkinson built the Monitorr app as
|
||||
a Free app. This SERVICE is provided by
|
||||
Stephen Parkinson at no cost and is intended for use as
|
||||
is.
|
||||
</p> <p>
|
||||
This page is used to inform visitors regarding my
|
||||
policies with the collection, use, and disclosure of Personal
|
||||
Information if anyone decided to use my Service.
|
||||
</p> <p>
|
||||
If you choose to use my Service, then you agree to
|
||||
the collection and use of information in relation to this
|
||||
policy. The Personal Information that I collect is
|
||||
used for providing and improving the Service. I will not use or share your information with
|
||||
anyone except as described in this Privacy Policy.
|
||||
</p> <p>
|
||||
The terms used in this Privacy Policy have the same meanings
|
||||
as in our Terms and Conditions, which are accessible at
|
||||
Monitorr unless otherwise defined in this Privacy Policy.
|
||||
</p> <p><strong>Information Collection and Use</strong></p> <p>
|
||||
For a better experience, while using our Service, I
|
||||
may require you to provide us with certain personally
|
||||
identifiable information. The information that
|
||||
I request will be retained on your device and is not collected by me in any way.
|
||||
</p> <!----> <p><strong>Log Data</strong></p> <p>
|
||||
I want to inform you that whenever you
|
||||
use my Service, in a case of an error in the app
|
||||
I collect data and information (through third-party
|
||||
products) on your phone called Log Data. This Log Data may
|
||||
include information such as your device Internet Protocol
|
||||
(“IP”) address, device name, operating system version, the
|
||||
configuration of the app when utilizing my Service,
|
||||
the time and date of your use of the Service, and other
|
||||
statistics.
|
||||
</p> <p><strong>Cookies</strong></p> <p>
|
||||
Cookies are files with a small amount of data that are
|
||||
commonly used as anonymous unique identifiers. These are sent
|
||||
to your browser from the websites that you visit and are
|
||||
stored on your device's internal memory.
|
||||
</p> <p>
|
||||
This Service does not use these “cookies” explicitly. However,
|
||||
the app may use third-party code and libraries that use
|
||||
“cookies” to collect information and improve their services.
|
||||
You have the option to either accept or refuse these cookies
|
||||
and know when a cookie is being sent to your device. If you
|
||||
choose to refuse our cookies, you may not be able to use some
|
||||
portions of this Service.
|
||||
</p> <p><strong>Service Providers</strong></p> <p>
|
||||
I may employ third-party companies and
|
||||
individuals due to the following reasons:
|
||||
</p> <ul><li>To facilitate our Service;</li> <li>To provide the Service on our behalf;</li> <li>To perform Service-related services; or</li> <li>To assist us in analyzing how our Service is used.</li></ul> <p>
|
||||
I want to inform users of this Service
|
||||
that these third parties have access to their Personal
|
||||
Information. The reason is to perform the tasks assigned to
|
||||
them on our behalf. However, they are obligated not to
|
||||
disclose or use the information for any other purpose.
|
||||
</p> <p><strong>Security</strong></p> <p>
|
||||
I value your trust in providing us your
|
||||
Personal Information, thus we are striving to use commercially
|
||||
acceptable means of protecting it. But remember that no method
|
||||
of transmission over the internet, or method of electronic
|
||||
storage is 100% secure and reliable, and I cannot
|
||||
guarantee its absolute security.
|
||||
</p> <p><strong>Links to Other Sites</strong></p> <p>
|
||||
This Service may contain links to other sites. If you click on
|
||||
a third-party link, you will be directed to that site. Note
|
||||
that these external sites are not operated by me.
|
||||
Therefore, I strongly advise you to review the
|
||||
Privacy Policy of these websites. I have
|
||||
no control over and assume no responsibility for the content,
|
||||
privacy policies, or practices of any third-party sites or
|
||||
services.
|
||||
</p> <p><strong>Children’s Privacy</strong></p> <!----> <div><p>
|
||||
I do not knowingly collect personally
|
||||
identifiable information from children. I
|
||||
encourage all children to never submit any personally
|
||||
identifiable information through
|
||||
the Application and/or Services.
|
||||
I encourage parents and legal guardians to monitor
|
||||
their children's Internet usage and to help enforce this Policy by instructing
|
||||
their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child
|
||||
has provided personally identifiable information to us through the Application and/or Services,
|
||||
please contact us. You must also be at least 16 years of age to consent to the processing
|
||||
of your personally identifiable information in your country (in some countries we may allow your parent
|
||||
or guardian to do so on your behalf).
|
||||
</p></div> <p><strong>Changes to This Privacy Policy</strong></p> <p>
|
||||
I may update our Privacy Policy from
|
||||
time to time. Thus, you are advised to review this page
|
||||
periodically for any changes. I will
|
||||
notify you of any changes by posting the new Privacy Policy on
|
||||
this page.
|
||||
</p> <p>This policy is effective as of 2023-03-12</p> <p><strong>Contact Us</strong></p> <p>
|
||||
If you have any questions or suggestions about my
|
||||
Privacy Policy, do not hesitate to contact me at stephen@smpark.in.
|
||||
</p> <p>This privacy policy page was created at <a href="https://privacypolicytemplate.net" target="_blank" rel="noopener noreferrer">privacypolicytemplate.net </a>and modified/generated by <a href="https://app-privacy-policy-generator.nisrulz.com/" target="_blank" rel="noopener noreferrer">App Privacy Policy Generator</a></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
import Leaf
|
||||
import Vapor
|
||||
|
||||
// Called before your application initializes.
|
||||
public func configure(_ app: Application) throws {
|
||||
//leaf
|
||||
app.views.use(.leaf)
|
||||
|
||||
//Register middleware
|
||||
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
|
||||
app.middleware.use(ErrorMiddleware.default(environment: app.environment))
|
||||
|
||||
//routes
|
||||
try routes(app)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import Vapor
|
||||
|
||||
// Register your application's routes here.
|
||||
public func routes(_ app: Application) throws {
|
||||
app.get("") { req async throws -> View in
|
||||
return try await req.view.render("home")
|
||||
}
|
||||
app.get("index.html") { req async throws -> View in
|
||||
return try await req.view.render("home")
|
||||
}
|
||||
app.get("privacy") { req async throws -> View in
|
||||
return try await req.view.render("privacy")
|
||||
}
|
||||
app.get("welcome") { req async throws -> View in
|
||||
return try await req.view.render("welcome")
|
||||
}
|
||||
app.get("**") { req async throws -> View in
|
||||
return try await req.view.render("404")
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import App
|
||||
import Vapor
|
||||
import Logging
|
||||
|
||||
var env = try Environment.detect()
|
||||
try LoggingSystem.bootstrap(from: &env)
|
||||
let app = try await Application.make(env)
|
||||
|
||||
do {
|
||||
try configure(app)
|
||||
try await app.execute()
|
||||
} catch {
|
||||
app.logger.report(error: error)
|
||||
try? await app.asyncShutdown()
|
||||
throw error
|
||||
}
|
||||
|
||||
try await app.asyncShutdown()
|
||||
@@ -1,12 +0,0 @@
|
||||
import XCTest
|
||||
@testable import App
|
||||
|
||||
class AppTests: XCTestCase {
|
||||
func testStub() throws {
|
||||
XCTAssert(true)
|
||||
}
|
||||
|
||||
static let allTests = [
|
||||
("testStub", testStub),
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#if os(Linux)
|
||||
|
||||
import XCTest
|
||||
@testable import AppTests
|
||||
|
||||
XCTMain([
|
||||
// AppTests
|
||||
testCase(AppTests.allTests),
|
||||
])
|
||||
|
||||
#endif
|
||||