diff --git a/.gitignore b/.gitignore index 2b3e9d8..015dd71 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,8 @@ dist .vscode-test # yarn v2 +.yarnrc +.yarn/releases .yarn/cache .yarn/unplugged .yarn/build-state.yml @@ -134,3 +136,5 @@ dist # Mac specific files .DS_Store + +to-be-deleted diff --git a/Dockerfile b/Dockerfile index 9acf17c..76817c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,17 +5,25 @@ WORKDIR /usr/src/app # Install app dependencies COPY package*.json ./ -COPY .npmrc ./.npmrc -RUN npm install +COPY .npmrc ./ +COPY yarn.lock ./ +RUN yarn install --frozen-lockfile # Build and test COPY tsconfig.json ./ COPY ./src ./src -RUN npm run build --if-present +COPY gulpfile.mjs ./ +RUN yarn build RUN npm run test --if-present # Remove source files RUN rm -r ./src -EXPOSE 3000 -CMD [ "npm", "start" ] \ No newline at end of file +ARG DOMAIN=example.com +ENV DOMAIN=$DOMAIN + +ARG PORT=3000 +ENV PORT=$PORT + +EXPOSE $PORT +CMD [ "yarn", "start" ] \ No newline at end of file diff --git a/gulpfile.mjs b/gulpfile.mjs new file mode 100644 index 0000000..7942927 --- /dev/null +++ b/gulpfile.mjs @@ -0,0 +1,102 @@ +import fse from 'fs-extra'; +import path from 'path'; +import gulp from "gulp"; +import ts from "gulp-typescript"; +//import * as dartSass from 'sass'; +//import gulpSass from 'gulp-sass'; +//import webpack from 'webpack'; +//import webpackStream from 'webpack-stream'; +import { deleteAsync } from 'del'; + +//const sass = gulpSass(dartSass); + +var tsProject = ts.createProject("tsconfig.json"); + +// Task which would delete the old dist directory if present +gulp.task("build-clean", function () { + return deleteAsync(["./dist"]); +}); + +// Task which would transpile typescript to javascript +gulp.task("typescript", function () { + return tsProject.src().pipe(tsProject()).js.pipe(gulp.dest("dist")); +}); + +/*let webpackConfig = { + mode: 'development', //(PRODUCTION ? 'production' : 'development'), + module: { + rules: [ + { + test: /\.js$/, + use: { + loader: 'babel-loader', + options: { + presets: [ "@babel/preset-env" ], + compact: false + } + } + } + ] + }//, + //devtool: !PRODUCTION && 'source-map' +}*/ + +// Task which will copy the assets from the static JavaScript directory to the dist directory +/*gulp.task("compile-foundation-js", function () { + return gulp.src('./src/static/js/foundation/** /*.js').pipe(webpackStream(webpackConfig, webpack)).pipe(gulp.dest("./dist/static/js/foundation")); +});*/ + +// Task to compile the sass files to css files +/*gulp.task("sass", function () { + let includePaths = []; + + if(fse.existsSync('node_modules/foundation-sites/scss') && fse.existsSync('node_modules/motion-ui/src')) { + includePaths = [ + 'node_modules/foundation-sites/scss', + 'node_modules/motion-ui/src' + ]; + } + else if(fse.existsSync('.yarn/cache')) { + // We're using Yarn PnP, so we need to find the paths to the foundation-sites and motion-ui packages + const packages = fse.readdirSync('.yarn/cache'); + + const foundationSitesYarnPnPPath = path.join('.yarn/cache', packages.find(p => p.startsWith('foundation-sites-npm-') && p.endsWith('.zip')), 'node_modules/foundation-sites/scss'); + const motionUIYarnPnPPath = path.join('.yarn/cache', packages.find(p => p.startsWith('motion-ui-npm-') && p.endsWith('.zip')), 'node_modules/motion-ui/src'); + + includePaths = [ + foundationSitesYarnPnPPath, + motionUIYarnPnPPath + ]; + } + + return gulp.src("./src/static/scss/** /*.scss").pipe(sass({ includePaths: includePaths })).pipe(gulp.dest("./dist/static/css")); +});*/ + +//gulp.task('pre-compile', gulp.parallel('compile-foundation-js', 'sass')); + +// Task which would just create a copy of the current views directory in dist directory +gulp.task("views", function () { + return gulp.src("./src/pages/**/*.ejs").pipe(gulp.dest("./dist/pages")); +}); + +// Task which will copy the assets from the static JavaScript directory to the dist directory +gulp.task("assets-js", function () { + return gulp.src(['./src/static/js/**/*.js', '!./src/static/js/foundation/**/*.js']).pipe(gulp.dest("./dist/static/js")); +}); + +// Task which will copy the assets from the static image directory to the dist directory +gulp.task("assets-img", function () { + return gulp.src("./src/static/img/**/*", { encoding: false }).pipe(gulp.dest("./dist/static/img")); +}); + +// Task which will copy the assets from the static CSS directory to the dist directory +gulp.task("assets-css", function () { + return gulp.src("./src/static/css/*").pipe(gulp.dest("./dist/static/css")); +}); + +gulp.task("assets", gulp.parallel("assets-js", "assets-img", "assets-css")); + +// The default task which runs at start of the gulpfile.js +gulp.task("default", gulp.series("build-clean", "typescript", /*"pre-compile",*/ "views", "assets"), () => { + console.log("Done"); +}); \ No newline at end of file diff --git a/package.json b/package.json index 1196152..972af2f 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,38 @@ { - "name": "my-express-app", + "name": "ba-website", "version": "1.0.0", "description": "A simple Express app", "main": "server.js", "scripts": { "start": "node dist/server.js", - "build": "tsc && cp -R src/views dist/views && cp -R src/public dist/public", + "build": "gulp", "dev": "nodemon src/server.ts" }, "dependencies": { "@BridgemanAccessible/ba-auth": "^1.0.0", + "@BridgemanAccessible/ba-web-framework": "^1.0.0", + "@BridgemanAccessible/listmonk-node-client": "^1.0.0", "ejs": "^3.1.6", "express": "^4.17.1", + "fs-extra": "^11.3.0", "mime": "^3.0.0" }, "devDependencies": { "@types/express": "^4.17.13", + "@types/fs-extra": "^11.0.4", + "@types/gulp": "^4.0.17", "@types/mime": "^3.0.1", "@types/node": "20.3.1", + "del": "^8.0.0", + "gulp": "^5.0.0", + "gulp-sass": "^6.0.0", + "gulp-typescript": "^6.0.0-alpha.1", "nodemon": "^2.0.13", + "sass": "^1.85.0", "ts-node": "^10.2.1", - "typescript": "^4.4.3" - } + "typescript": "^5.4.2", + "webpack": "^5.98.0", + "webpack-stream": "^7.0.0" + }, + "packageManager": "yarn@1.22.22" } diff --git a/src/views/about.ejs b/src/pages/about.ejs similarity index 97% rename from src/views/about.ejs rename to src/pages/about.ejs index be0fcef..dcf3238 100644 --- a/src/views/about.ejs +++ b/src/pages/about.ejs @@ -1,8 +1,3 @@ -<%- include('header.ejs', { - extra_css: [ 'about.css' ], - active_page: 'about' -}) %> -
+ For over five years, Bridgeman Accessible has been the trusted partner in providing top-notch IT support for a wide range of events, from intimate consultations to large-scale webinars with over 100 attendees. + Whether your event requires single-camera setups or complex multi-camera arrangements, our expertise ensures seamless technology integration tailored to your needs. +
++ Our founder, equipped with a degree in computer science and a robust IT support background, began by volunteering tech support for events within the disability advocacy community. + This dedication quickly earned us a reputation for excellence, with organizations like the Council of Canadians with Disabilities (CCD), the National Educational Association of Disabled Students (NEADS), and the Vision Impaired Resource Network (VIRN) consistently relying on our services. +
++ As our experience grew, so did the demand for our services. + We have expanded beyond professional events to include personal occasions, such as weddings and special celebrations. + Friends, family, and clients alike trust us to ensure their events run smoothly, whether online, hybrid, or in-person. +
++ Bridgeman Accessible leverages the latest technology to provide reliable event support. + Our toolkit includes industry-leading solutions like Zoom, Microsoft Teams, Rode wireless microphones, the RODECaster Pro 2 sound production board, and the GoStream Deck Pro Kit, among others. + We tailor our equipment and services to meet the unique requirements of your event, ensuring flawless execution every time. +
+
+ © 2023 Bridgeman Accessible. All rights reserved.
\ No newline at end of file diff --git a/src/pages/includes/header.ejs b/src/pages/includes/header.ejs new file mode 100644 index 0000000..af0c0f8 --- /dev/null +++ b/src/pages/includes/header.ejs @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/src/pages/index.ejs b/src/pages/index.ejs new file mode 100644 index 0000000..e4c8523 --- /dev/null +++ b/src/pages/index.ejs @@ -0,0 +1,92 @@ +
+ + Does this sound like you? +
+ Overwhelmed by the thought of creating an accessible digital or hybrid event? + We can help! + We specialize in providing top-notch IT support for a wide range of events, from intimate consultations to larger-scale webinars with 100s of attendees. +
+
+ + Does this sound like you? +
+ We work closely with our clients on some of the most cutting edge and innovative technologies to help make sure their products and services have a clear path to become accessible for people living with disabilities. + But not just a bridge good for today but tomorrow and into the future as well. +
+
+ + Does this sound like you? +
+ We leverage the most modern technology, practices and techniques in combination with our own resourcefulness, ingenuity and creativity to create truly innovative solutions. + These solutions aim to address the most pressing problems for modern professionals living with disabilities. +
+
++ We run almost all our workloads on our owned and operated "bare metal" servers. + In particular, we bought 2 used servers: +
+ The used Dell PowerEdge R730 we bought came with it's 8 drive bays full with 2 TB hard drives. + We run this using RAID 5, which means that we have 14 TB of storage and can have a single drive fail without losing any data. +
++ The Dell PowerEdge R730xd we bought unfortunately came with no drives (not having experience with buying servers and the difference between "drives" and "drive bays"). + So, we bought 4 1TB Samsung SSDs to fill in 4 of it's 24 drive bays. Which we also learned we had to buy caddys for (and did). +
+ ++ We house our servers on rails inside a small 12U Sysracks server cabinet in our office. + This cabinet came with a built in fan, a lockable front door (lockable but removable side panels) and a power management unit. +
+ ++ We pay our ISP for a 1.5 Gbps connection, which is run through our ISP provided modem/router (which is in Bridge Mode). + The ISP modem/router is then run to a TP-Link adapter (because we have a Powerline network connection to our office using another TP-Link adapter) + We have to do this, this way because we lack the physical ability to run cabling through the physical structure. + The TP-Link adapter in our office is then run to a Netgear Nighthawk router which creates vnets within our office. + Which, for the purposes of this, is feed to a Netgear 1Gbps 8 port switch inside of our server cabinet. + Which finally runs from the switch directly to our servers. +
+ ++ We have a Tripplite 1500VA UPS that we use to power our servers for a short time in the event of a power outage. + We have it plugged into the wall and then have our servers having one power supply plugged into it (the servers have two power supplies). + We have the other power supply plugged into the power management unit in our server cabinet. Which the power management unit is plugged into the wall. + We choose to set it up this way because we felt it was the architecture that best fit our risk profile. + That is, we attempt to ensure that our servers would stay up (at least for a time) in the event of a power outage or UPS failure. +
+ ++ Amongst other reasons we choose to buy Dell servers because we wanted access via Integrated Dell Remote Access Controller (IDRAC). + We use IDRAC to monitor and control our servers on a hardware level remotely. + Though, we do have access to many of the same features/metrics through software having IDRAC provides us a piece of mind backup just in case. + We have it set up so that we can ONLY access the IDRAC interface from our office network or VPN. +
+ ++ We run Proxmox Virtual Environment (PVE) which is based on Debian Linux on both of our servers. + This allows us to easily run and manage virtual workflows to maximize our hardware utilization. + We also have them connected as a cluster within the PVE interface so that we can: +
+ We run a Kubernetes cluster on top of our Proxmox cluster. + We use this to run many (most) of our workloads. + In particular, any workloads that can be containerized we try to (and/or aspire to) run on our Kubernetes cluster. +
++ At time of writing, we run one control node and four worker nodes. +
+ ++ We run a Ceph cluster on top of Kubernetes using Rook. + We use this to store our persistent data. + In particular, this includes storage for databases and other data that needs to be stored more permanently (across pod restarts/deployments). +
+ ++ We run a private container registry on top of Kubernetes using Harbor. + We use this to store our container images. + In particular, this includes images for our workloads that we deploy to our Kubernetes cluster. +
+ ++ We run a PostgreSQL cluster on top of Kubernetes using CloudNativePG. + We use this to store our relational data. + In particular, this provides us another level of redundancy and reliability for our data. +
+ ++ We run a private NPM registry on top of Kubernetes using Verdaccio. + We use this to store our NPM packages. + In particular, this includes packages we tend to use with Node and other JavaScript based projects. +
+ ++ We run a Prometheus/Grafana stack on top of Kubernetes. + We use this to monitor our cluster and services. + In particular, we use this to monitor the health of our cluster and services. +
+ ++ We run a Matomo server on top of Kubernetes. + We use this to track web traffic and usage statistics of our websites. +
+ ++ We run an Nginx Ingress controller alongside MetalLB on top of Kubernetes. + We use this to route traffic to our services deployed on our Kubernetes cluster. + The NGNIX Ingress controller is used to route traffic to our services and MetalLB is used, in our case, to obtain a network accessible IP address for the NGINX Ingress controller. +
+ ++ We run two Bind DNS servers (a primary and secondary) outside our Kubernetes cluster. + We use this to resolve DNS queries for our local corporate network. +
+ ++ We run a Pi-hole server outside our Kubernetes cluster. + We use this to provide DHCP and filter DNS queries for our local corporate network. + It's worth noting that we generally prefer to use static DHCP leases over static IP addresses as it provides us more flexibility and easy oversight. + That is, we can go to Pi-hole and see a single table with all the static leases rather than relying on a spreadsheet or other documentation to keep track of assigned static IPs +
+ ++ We run a Nginx Proxy Manager (NPM) server outside of our Kubernetes cluster on a separate server/computer from the rest of our infrastructure. + We use this to have more granular control of how (particularly public) traffic is routed within our network/to our services. + Moreover, this usually handles TLS/SSL termination for our public services and getting and maintaining certificates through Let's Encrypt. +
+ ++ We run an Uptime Kuma server outside of our Kubernetes cluster on a separate server/computer from the rest of our infrastructure. + We use this to monitor the uptime of/access to our public services. +
+ ++ We use No-IP to provide us with a Dynamic DNS (DDNS) service for a small cost. + That is, because our ISP wouldn't provide us a static IP address with the speed we wanted we use a DDNS service to have a name that resolves to our dynamic IP address. + What this means practically, is we have a piece of software (provided by No-IP) running on one of our computers/servers that updates the No-IP record if our public IP address ever changes. + We then use this in CNAME records for our public domain name so that our services can be publicly available without having to worry if our IP address ever changes. + Though for the A records at the root of our domains we had to develop a script that did similar/the same as the No-IP software. +
+ ++ We use GoDaddy to provide us with public DNS services for a cost. + That is, we use GoDaddy to manage our public domain names and the DNS records associated with them. +
+ ++ We use a weird mix of services for email. + Largely this is split between our self-hosted Mailcow server and Microsoft 365. + Our Mailcow server is used is used for internal email and access for our services. + While Microsoft 365 is mostly used for external email. +
+ ++ We use No-IP's alternate port SMTP service for a small cost. + That is, because our ISP blocks outbound traffic on port 25 we use a service that provides us with an alternate port to send email. + We then configure this in our Mailcow server to send email out to the internet. +
+ ++ We use FreePBX to provide us with phone services. + This is a self-hosted solution that we host outside our Kubernetes cluster. +
+ ++ We use Nextcloud All-In-One (AIO) to provide us with groupware services. + This is a self-hosted solution that we host outside our Kubernetes cluster (though aspirational would like to move onto Kubernetes). + This provides us with file storage, calendar, contacts, and other groupware services. +
+ ++ We use Hmmepage to provide a custom built dashboard of our services for internal users. + This is a simple HTML page that we host on our Kubernetes cluster. + It provides us with a quick links and an overview of the status of our services. +
+ +| Item | +Cost | +Notes | +
|---|---|---|
| Dell PowerEdge R730 | +$1,140.14 | +Used server from Amazon.ca | +
| Dell PowerEdge R730xd | +$1,180.52 | +Used server from Amazon.ca | +
| Dell Server Rack Rails (× 2) | +$288.96 | +Used from Amazon.ca | +
| 12 pack 2.5'' Drive Caddy (× 2) | +$239.40 | +From Amazon.ca | +
| 1TB Samsung SSDs (× 4) | +$582.36 | +From Amazon.ca | +
| 12U Sysracks server cabinet | +$547.70 | +From Amazon.ca | +
| TP-Link adapter | +$291.18 | +From Amazon.ca | +
| Netgear Nighthawk router | +$146.44 | +Used from individual | +
| Netgear 1Gbps 8 port switch | +$58.98 | +Used from individual | +
| Tripplite 1500VA UPS | +$517.87 | +From Amazon.ca | +
| Power | +$597.00/year | +Approximate | +
| Internet (from ISP) | +$1,464.96/month | +1.5Gbps speed | +
| DDNS (from No-IP) | +$99.99/year | +Dynamic DNS service | +
| Public DNS (from GoDaddy) | +$401.39/year | +DNS (for multiple domains) + M365 | +
| Alternate Port SMTP (from No-IP) | +$39.99/year | +Alternate port SMTP service | +
| Fixed One Time/Capital | +$4,993.63 | +|
| Variable (Yearly Total) | +$2,603.33 | +Or ~$216.95 per month | +
This is the content for the first section. It can contain multiple paragraphs and other elements.
+More interesting content for the second section goes here.
++ We provide a variety of self-serve tools that can be used to help make technology (or people's use of technology) more accessible. + These tools are designed to be user-friendly and can be used by individuals or organizations without the need for specialized training or expertise. +
++ We provide technical support for situations where there are disability/accessibility specific requirements or concerns. +
++ We specialize in helping facilitate any technical needs for accessibility features such as CART & captions, ASL/LSQ, and more. + Our services are available for both online and hybrid events, and may be available for in-person events on a case by case basis. + Noting, our office is located in Winnipeg, Manitoba, Canada and we are not able to travel outside of the city for in-person events at this time. +
+ +We can help clients to remediate (make accessible) any media that they have created or are using.
+We can help clients to remediate (make accessible) any documents that they have created or are using.
++ Our past work has focused on PDFs, Word documents, PowerPoints and Excel spreadsheets. + We are open to working with other document types as well, but with other formats we may not be able to provide the same level of support. +
++ We do offer some limited website testing and advising services (that don't include development). + Though this is somewhat limited (at least as A La Carte) compared to our many, many competitors in this area. +
++ We work very closely with or alongside developers to make software and hardware accessible. + Our team has extensive experience in not only accessibility (which many of our compeitors do as well) but in software development and engineering as well. + Which means we provide you the uniquely specific experience and guidance that goes so much deeper than "you should do X because of Y which makes it more accessible" but the + "your app is doing X which isn't accessible because of Y but if you look at line N in file M of your code you can do Z to fix it". +
++ This comes with the caveat that we are often more interested in the how than the what. + Which means our clients are expected to have a good idea of what they want to build and what solutions may or may not work for them. + We can help with this to an extent but we are not a design or content creation agency and we do not provide design or content development services. + We have often had partners like disability specific organizations that can help with accessible design and content creation and we are happy to connect you and work with them as appropriate. +
++ In specific cases where the product or service will provide a meaningful and significant contribution to advancing the accessibility of technology we also offer co-development not only on accessibility but on the product or service in general. + When applicable, we don't just bring accessibility into the development but also development expertise in areas such as infrastructure, security, testing and more. +
++ We provide IT support to disability-specific organizations or organiztions that work significanlty with people living with disabilities and help them become more digitally literate and efficient. + Our team has experience working with a variety of organizations and can provide customized solutions to meet their unique needs. +
+This is the content for the first section. It can contain multiple paragraphs and other elements.
+More interesting content for the second section goes here.
++ Your data is stored on Bridgeman Accessible servers which reside in our office in Winnipeg, MB (Canada) and will only be used for distributing Bridgeman Accessible information unless given express permission. +
+
Social Media Management (Mixpost)
++ We use Mixpost to manage our social media accounts. + Mixpost is a self-hosted solution that we host on our Kubernetes cluster. +
+