Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
Andrew Lalis | a7561c32c3 | |
Andrew Lalis | 00cfcda9b8 | |
Andrew Lalis | ba4a0c16d6 |
|
@ -1 +1,2 @@
|
||||||
build/
|
build/
|
||||||
|
cli*
|
52
README.md
52
README.md
|
@ -1,7 +1,53 @@
|
||||||
# Gymboard
|
# Gymboard
|
||||||
Leaderboards for your local community gym.
|
Leaderboards and social lifting for your local community gym.
|
||||||
|
|
||||||
|
Gymboard is a platform for sharing videos of your gym lifts with the world,
|
||||||
|
from your local gym to the world's stage.
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
Gymboard is designed as a sort of hybrid architecture combining the best of
|
||||||
|
microservices and monoliths. Here's a short list of each project that makes
|
||||||
|
up the Gymboard ecosystem, and what they do:
|
||||||
|
|
||||||
|
| Project | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 💻 **gymboard-app** | TS, VueJS, Quasar | A front-end web application for users accessing Gymboard. |
|
||||||
|
| 🧬 **gymboard-api** | Java, Spring | The *main* backend service that the app talks to. Includes auth and most business logic. |
|
||||||
|
| 🔍 **gymboard-search** | Java, Lucene | An indexing and searching service that scrapes data from *gymboard-api* to build Lucene search indexes. |
|
||||||
|
| 🗂 **gymboard-cdn** | Java, Spring | A minimal content-delivery service that manages general file storage, including video processing. |
|
||||||
|
| 🛠 **gymboard-cli** | D | **WIP** command-line-interface for managing all services for development and deployment. |
|
||||||
|
| 📸 **gymboard-uploads** | D, Handy-Httpd | **WIP** dedicated service for video upload processing, to extract functionality from *gymboard-cdn*. |
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
Gymboard is comprised of a variety of components, each in its own directory, and with its own project format. Follow the instructions in the README of the respective project to set that one up.
|
|
||||||
|
|
||||||
A `docker-compose.yml` file is defined in this directory, and it defines a set of services that may be used by one or more services. Install docker on your system if you haven't already, and run `docker-compose up -d` to start the services.
|
Gymboard is comprised of a variety of components, each in its own directory,
|
||||||
|
and with its own project format. Follow the instructions in the README of the
|
||||||
|
respective project to set that one up.
|
||||||
|
|
||||||
|
A `docker-compose.yml` file is defined in this directory, and it defines a set
|
||||||
|
of services that may be used by one or more services. Install docker on your
|
||||||
|
system if you haven't already, and run `docker-compose up -d` to start the
|
||||||
|
services.
|
||||||
|
|
||||||
|
Run `./build-cli.d` to build and prepare a `cli` executable that you can use to
|
||||||
|
run the Gymboard CLI.
|
||||||
|
|
||||||
|
**WIP:**
|
||||||
|
A `build-apps.d` script is available to try and build all projects and collect
|
||||||
|
their artifacts in a `build/` directory for deployment.
|
||||||
|
> Eventually, this functionality will be merged into *gymboard-cli*.
|
||||||
|
|
||||||
|
### Local Environment
|
||||||
|
|
||||||
|
The requirements to develop each project depend of course on the type of
|
||||||
|
project. But in general, the following software recommendations should hold:
|
||||||
|
|
||||||
|
| Type | Requirements |
|
||||||
|
| --- | --- |
|
||||||
|
| **Java** | [Latest LTS version](https://adoptium.net/en-GB/temurin/releases/), latest Maven version |
|
||||||
|
| **VueJS** | Vue 3, with a recent version of NodeJS and NPM or similar. |
|
||||||
|
| **D** | [D toolchain](https://dlang.org/download.html) (compiler + dub) for D version >= 2.103, DMD recommended compiler |
|
||||||
|
|
||||||
|
[Docker](https://www.docker.com/) is recommended for running local dependencies
|
||||||
|
like DB, mail server, message queues, etc.
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* thread.
|
* thread.
|
||||||
*
|
*
|
||||||
* Supply the "clean" command to remove all build artifacts instead of building.
|
* Supply the "clean" command to remove all build artifacts instead of building.
|
||||||
|
*
|
||||||
|
* Eventually, this will be migrated to gymboard-cli.
|
||||||
*/
|
*/
|
||||||
module build_apps;
|
module build_apps;
|
||||||
|
|
||||||
|
@ -20,7 +22,8 @@ enum BUILDS_DIR = "build";
|
||||||
|
|
||||||
int main(string[] args) {
|
int main(string[] args) {
|
||||||
if (args.length > 1 && args[1] == "clean") {
|
if (args.length > 1 && args[1] == "clean") {
|
||||||
rmdirRecurse(BUILDS_DIR);
|
writeln("Cleaning builds");
|
||||||
|
if (exists(BUILDS_DIR)) rmdirRecurse(BUILDS_DIR);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Thread[] buildThreads = [
|
Thread[] buildThreads = [
|
||||||
|
@ -91,8 +94,10 @@ int runBuild(const BuildSpec spec) {
|
||||||
string buildDir = buildPath(BUILDS_DIR, spec.name);
|
string buildDir = buildPath(BUILDS_DIR, spec.name);
|
||||||
if (exists(buildDir)) rmdirRecurse(buildDir);
|
if (exists(buildDir)) rmdirRecurse(buildDir);
|
||||||
mkdirRecurse(buildDir);
|
mkdirRecurse(buildDir);
|
||||||
File buildLogFile = File(buildPath(buildDir, "build.log"), "w");
|
const logFilePath = buildPath(buildDir, "build.log");
|
||||||
File buildErrorLogFile = File(buildPath(buildDir, "build-error.log"), "w");
|
const errorLogFilePath = buildPath(buildDir, "build-error.log");
|
||||||
|
File buildLogFile = File(logFilePath, "w");
|
||||||
|
File buildErrorLogFile = File(errorLogFilePath, "w");
|
||||||
Pid pid = spawnShell(
|
Pid pid = spawnShell(
|
||||||
spec.buildCommand,
|
spec.buildCommand,
|
||||||
std.stdio.stdin,
|
std.stdio.stdin,
|
||||||
|
@ -104,7 +109,14 @@ int runBuild(const BuildSpec spec) {
|
||||||
nativeShell()
|
nativeShell()
|
||||||
);
|
);
|
||||||
int result = wait(pid);
|
int result = wait(pid);
|
||||||
if (result != 0) return result;
|
if (result != 0) {
|
||||||
|
writefln!"Build command failed for build \"%s\". Check %s for more info."(spec.name, errorLogFilePath);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up unused log files.
|
||||||
|
if (getSize(logFilePath) == 0) std.file.remove(logFilePath);
|
||||||
|
if (getSize(errorLogFilePath) == 0) std.file.remove(errorLogFilePath);
|
||||||
|
|
||||||
// Find and extract artifacts.
|
// Find and extract artifacts.
|
||||||
bool artifactFound = false;
|
bool artifactFound = false;
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env rdmd
|
||||||
|
/**
|
||||||
|
* Run this script with `./build.d` to prepare the latest version of the CLI
|
||||||
|
* for use. It compiles the CLI application, and copies a "cli" executable to
|
||||||
|
* the project's root directory for use.
|
||||||
|
*/
|
||||||
|
module build_cli;
|
||||||
|
|
||||||
|
import std.stdio;
|
||||||
|
import std.process;
|
||||||
|
import std.file;
|
||||||
|
import std.path;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
writeln("Building...");
|
||||||
|
const mainDir = getcwd();
|
||||||
|
chdir("gymboard-cli");
|
||||||
|
auto result = executeShell("dub build --build=release");
|
||||||
|
if (result.status != 0) {
|
||||||
|
stderr.writefln!"Build failed: %d"(result.status);
|
||||||
|
stderr.writeln(result.output);
|
||||||
|
return result.status;
|
||||||
|
}
|
||||||
|
string finalPath = buildPath("..", "cli");
|
||||||
|
if (exists(finalPath)) std.file.remove(finalPath);
|
||||||
|
version (Posix) {
|
||||||
|
string sourceExecutable = "gymboard-cli";
|
||||||
|
}
|
||||||
|
version (Windows) {
|
||||||
|
string sourceExecutable = "gymboard-cli.exe";
|
||||||
|
}
|
||||||
|
std.file.copy(sourceExecutable, finalPath);
|
||||||
|
version (Posix) {
|
||||||
|
result = executeShell("chmod +x " ~ finalPath);
|
||||||
|
if (result.status != 0) {
|
||||||
|
stderr.writefln!"Failed to enable executable permission: %d"(result.status);
|
||||||
|
return result.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln("Done! Run ./cli to start using Gymboard CLI.");
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
import cli;
|
import cli;
|
||||||
|
import command;
|
||||||
import services;
|
import services;
|
||||||
|
|
||||||
import consolecolors;
|
import consolecolors;
|
||||||
|
@ -9,11 +10,12 @@ void main() {
|
||||||
ServiceManager serviceManager = new ServiceManager();
|
ServiceManager serviceManager = new ServiceManager();
|
||||||
CliHandler cliHandler = new CliHandler();
|
CliHandler cliHandler = new CliHandler();
|
||||||
cliHandler.register("service", new ServiceCommand(serviceManager));
|
cliHandler.register("service", new ServiceCommand(serviceManager));
|
||||||
cwriteln("Gymboard CLI: Type <cyan>help</cyan> for more information. Type <red>exit</red> to exit the CLI.");
|
cwriteln("\n<blue>Gymboard CLI</blue>: <grey>Command-line interface for managing Gymboard services.</grey>");
|
||||||
while (!cliHandler.shouldExit) {
|
cwriteln(" Type <cyan>help</cyan> for more information.\n Type <red>exit</red> to exit the CLI.\n");
|
||||||
|
while (!cliHandler.isExitRequested) {
|
||||||
cwrite("> ".blue);
|
cwrite("> ".blue);
|
||||||
cliHandler.readAndHandleCommand();
|
cliHandler.readAndHandleCommand();
|
||||||
}
|
}
|
||||||
serviceManager.stopAll();
|
serviceManager.stopAll();
|
||||||
cwriteln("Goodbye!".green);
|
cwriteln("Goodbye!".blue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,45 +7,7 @@ import std.typecons;
|
||||||
|
|
||||||
import consolecolors;
|
import consolecolors;
|
||||||
|
|
||||||
interface CliCommand {
|
import command.base;
|
||||||
void handle(string[] args);
|
|
||||||
}
|
|
||||||
|
|
||||||
class CliHandler {
|
|
||||||
private CliCommand[string] commands;
|
|
||||||
public bool shouldExit = false;
|
|
||||||
|
|
||||||
public void register(string name, CliCommand command) {
|
|
||||||
this.commands[name] = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void readAndHandleCommand() {
|
|
||||||
string[] commandAndArgs = readln().strip().split!isWhite();
|
|
||||||
if (commandAndArgs.length == 0) return;
|
|
||||||
string command = commandAndArgs[0].toLower();
|
|
||||||
if (command == "help") {
|
|
||||||
showHelp();
|
|
||||||
} else if (command == "exit") {
|
|
||||||
shouldExit = true;
|
|
||||||
} else if (command in commands) {
|
|
||||||
commands[command].handle(commandAndArgs.length > 1 ? commandAndArgs[1 .. $] : []);
|
|
||||||
} else {
|
|
||||||
cwritefln("Unknown command: %s".red, command.orange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void showHelp() {
|
|
||||||
writeln(q"HELP
|
|
||||||
Gymboard CLI: A tool for streamlining development.
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
|
|
||||||
help Shows this message.
|
|
||||||
exit Exits the CLI, stopping any running services.
|
|
||||||
HELP");
|
|
||||||
}
|
|
||||||
|
|
||||||
import services;
|
import services;
|
||||||
|
|
||||||
class ServiceCommand : CliCommand {
|
class ServiceCommand : CliCommand {
|
||||||
|
@ -86,6 +48,14 @@ class ServiceCommand : CliCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string name() const {
|
||||||
|
return "Service";
|
||||||
|
}
|
||||||
|
|
||||||
|
string description() const {
|
||||||
|
return "bleh";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that a service command contains as its second argument a valid
|
* Validates that a service command contains as its second argument a valid
|
||||||
* service name.
|
* service name.
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
module command.base;
|
||||||
|
|
||||||
|
import consolecolors;
|
||||||
|
import std.stdio;
|
||||||
|
import std.string;
|
||||||
|
import std.uni;
|
||||||
|
|
||||||
|
interface CliCommand {
|
||||||
|
void handle(string[] args);
|
||||||
|
string name() const;
|
||||||
|
string description() const;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CliHandler {
|
||||||
|
private CliCommand[string] commands;
|
||||||
|
private bool exitRequested = false;
|
||||||
|
|
||||||
|
this() {
|
||||||
|
commands["help"] = new HelpCommand(this);
|
||||||
|
commands["exit"] = new ExitCommand(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void register(string name, CliCommand command) {
|
||||||
|
this.commands[name.strip().toLower()] = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
void readAndHandleCommand() {
|
||||||
|
string[] commandAndArgs = readln().strip().split!isWhite();
|
||||||
|
if (commandAndArgs.length == 0) return;
|
||||||
|
string command = commandAndArgs[0].toLower();
|
||||||
|
if (command in commands) {
|
||||||
|
commands[command].handle(commandAndArgs.length > 1 ? commandAndArgs[1 .. $] : []);
|
||||||
|
} else {
|
||||||
|
cwritefln("Unknown command: %s".red, command.orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setExitRequested() {
|
||||||
|
this.exitRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isExitRequested() const {
|
||||||
|
return this.exitRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
CliCommand[string] getCommands() {
|
||||||
|
return this.commands;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HelpCommand : CliCommand {
|
||||||
|
private CliHandler handler;
|
||||||
|
|
||||||
|
this(CliHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle(string[] args) {
|
||||||
|
import std.algorithm;
|
||||||
|
|
||||||
|
cwriteln("<blue>Gymboard CLI Help</blue>: <grey>Information about how to use this program.</grey>");
|
||||||
|
|
||||||
|
string[] commandNames = this.handler.getCommands().keys;
|
||||||
|
sort(commandNames);
|
||||||
|
uint longestCommandNameLength = commandNames.map!(n => cast(uint) n.length).maxElement;
|
||||||
|
|
||||||
|
foreach (name; commandNames) {
|
||||||
|
CliCommand command = this.handler.getCommands()[name];
|
||||||
|
|
||||||
|
string formattedName = cyan(leftJustify(name, longestCommandNameLength + 1, ' '));
|
||||||
|
cwriteln(formattedName, command.description().grey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string name() const {
|
||||||
|
return "help";
|
||||||
|
}
|
||||||
|
|
||||||
|
string description() const {
|
||||||
|
return "Shows help information.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExitCommand : CliCommand {
|
||||||
|
private CliHandler handler;
|
||||||
|
|
||||||
|
this(CliHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle(string[] args) {
|
||||||
|
this.handler.setExitRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
string name() const {
|
||||||
|
return "exit";
|
||||||
|
}
|
||||||
|
|
||||||
|
string description() const {
|
||||||
|
return "Exits the CLI, gracefully stopping any services or running jobs.";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module command;
|
||||||
|
|
||||||
|
public import command.base;
|
Loading…
Reference in New Issue