Initial Commit.

This commit is contained in:
Yohan Boujon 2024-03-10 11:47:49 +01:00
commit 3e123e58f2
24 changed files with 1827 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.vscode

3
README.md Normal file
View file

@ -0,0 +1,3 @@
#Etheryo > Blog
The blog part of the etheryo website.

5
backend/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.cache
build
.vscode
compile_commands.json
res/.env

83
backend/CMakeLists.txt Normal file
View file

@ -0,0 +1,83 @@
# Project Setup
cmake_minimum_required(VERSION 3.21)
project(
projet
VERSION 0.0.1
DESCRIPTION "api for etheryo blog"
HOMEPAGE_URL "https://www.etheryo.fr/"
LANGUAGES CXX C
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(TARGET app)
# CPM Setup
set(CPM_DOWNLOAD_VERSION 0.38.7)
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION}))
message(STATUS "Downloading CPM.cmake")
file(DOWNLOAD https://github.com/TheLartians/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake ${CPM_DOWNLOAD_LOCATION})
endif()
include(${CPM_DOWNLOAD_LOCATION})
# Source files
set(SOURCES "${PROJECT_SOURCE_DIR}/src")
add_executable(${TARGET}
${SOURCES}/dotenv.cpp
${SOURCES}/json.cpp
${SOURCES}/main.cpp
)
# Add Dependencies
find_package(Boost 1.64 COMPONENTS system date_time REQUIRED)
# Crow CPP
CPMAddPackage(
NAME crowcpp
GITHUB_REPOSITORY CrowCpp/Crow
GIT_TAG v1.1.0
)
if(crowcpp_ADDED)
target_include_directories(${TARGET} PUBLIC "${crowcpp_SOURCE_DIR}/include")
endif()
# Lib Pqxx
CPMAddPackage(
NAME libpqxx
GITHUB_REPOSITORY jtv/libpqxx
GIT_TAG 7.9.0
)
if(libpqxx_ADDED)
target_include_directories(${TARGET} PUBLIC "${libpqxx_SOURCE_DIR}/include")
target_include_directories(${TARGET} PUBLIC "${libpqxx_BINARY_DIR}/include")
target_link_libraries(${TARGET} PRIVATE pqxx)
endif()
# Copying the ressource folder to the build
add_custom_target(CopyRes ALL
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/res
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/res
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/res
${CMAKE_BINARY_DIR}/res
COMMENT "Copying and deleting resources to build directory"
VERBATIM
)
add_dependencies(${TARGET} CopyRes)
# Including the include folder
target_include_directories(${TARGET} PUBLIC "${PROJECT_SOURCE_DIR}/include")
# Compilation depending on the platform
if(MSVC)
target_compile_options(${TARGET} PUBLIC /W3 /WX /DEBUG )
elseif(${CMAKE_CXX_COMPILER} STREQUAL "/usr/bin/x86_64-w64-mingw32-g++")
target_compile_options(${TARGET} PUBLIC -Wall -Wextra -Wpedantic -Werror -static-libgcc -static-libstdc++)
else()
target_compile_options(${TARGET} PUBLIC -Wall -Wextra -Wpedantic -Werror -Wno-unused-but-set-variable)
endif()

7
backend/README.md Normal file
View file

@ -0,0 +1,7 @@
first install the following librairies :
```
dnf install boost-* asio-devel libpq-devel
```
```
pacman -S boost asio libpq
```

View file

@ -0,0 +1,19 @@
#ifndef _HEADER_ETHERYOBLOG_DOTENV
#define _HEADER_ETHERYOBLOG_DOTENV
#include <string>
#include <unordered_map>
namespace Etheryo {
class DotEnv {
public:
DotEnv();
int readFile(const std::string& path);
const std::string& operator[](const std::string& rvalue);
private:
std::unordered_map<std::string, std::string> _fileData;
};
}
#endif // _HEADER_ETHERYOBLOG_DOTENV

29
backend/include/json.hpp Normal file
View file

@ -0,0 +1,29 @@
#ifndef _HEADER_ETHERYOBLOG_JSON
#define _HEADER_ETHERYOBLOG_JSON
#include <string>
#include <unordered_map>
#include <vector>
namespace Etheryo {
typedef std::unordered_map<std::string, std::string> Data;
typedef std::vector<Data> Json;
class JsonHandler {
public:
JsonHandler();
void clear();
void add(const std::vector<std::string>& names);
std::string& operator[](const std::string& rvalue);
void push();
std::string to_str();
private:
Data _dataBuffer;
Json _buffer;
};
}
#endif // _HEADER_ETHERYOBLOG_JSON

115
backend/sql/etheryo.sql Normal file
View file

@ -0,0 +1,115 @@
CREATE TABLE "langs" (
"id" integer UNIQUE PRIMARY KEY NOT NULL,
"lang" text NOT NULL,
"icon_alpha" varchar(2) NOT NULL
);
CREATE TABLE "authors" (
"id" integer UNIQUE PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"username" text UNIQUE NOT NULL,
"password" text NOT NULL,
"created_at" time DEFAULT (now()),
"profile_picture" text,
"bio" text
);
CREATE TABLE "author_links" (
"author_id" integer NOT NULL,
"link_name" text NOT NULL,
"link_url" text NOT NULL
);
CREATE TABLE "post" (
"id" integer UNIQUE PRIMARY KEY NOT NULL,
"slug" text UNIQUE NOT NULL,
"author_id" integer,
"lang_id" integer,
"title" varchar,
"picture" text,
"date" time DEFAULT (now()),
"body" text,
"views" integer DEFAULT 0,
"likes" integer DEFAULT 0,
"dislikes" integer DEFAULT 0,
"shares" integer DEFAULT 0
);
CREATE TABLE "post_likes" (
"id" integer UNIQUE PRIMARY KEY NOT NULL,
"post_id" integer,
"like_status" bool NOT NULL,
"ip_address" inet NOT NULL
);
CREATE TABLE "category" (
"id" integer UNIQUE PRIMARY KEY NOT NULL,
"slug" text NOT NULL,
"icon" text,
"type_icon" text
);
CREATE TABLE "category_text" (
"category_id" integer NOT NULL,
"lang_id" integer NOT NULL,
"name" text NOT NULL
);
CREATE TABLE "post_categories" (
"category_id" integer NOT NULL,
"post_id" integer NOT NULL
);
CREATE TABLE "author_categories" (
"category_id" integer NOT NULL,
"author_id" integer NOT NULL
);
CREATE TABLE "comment" (
"id" integer UNIQUE PRIMARY KEY NOT NULL,
"post_id" integer NOT NULL,
"comment_id" integer,
"date" time DEFAULT (now()),
"user" text,
"body" text
);
CREATE TABLE "comment_likes" (
"id" integer UNIQUE PRIMARY KEY NOT NULL,
"comment_id" integer,
"like_status" bool NOT NULL,
"ip_address" inet NOT NULL
);
CREATE TABLE "flagged_comment" (
"comment_id" integer,
"flag_count" integer DEFAULT 1
);
ALTER TABLE "author_links" ADD FOREIGN KEY ("author_id") REFERENCES "authors" ("id");
ALTER TABLE "post" ADD FOREIGN KEY ("author_id") REFERENCES "authors" ("id");
ALTER TABLE "post" ADD FOREIGN KEY ("lang_id") REFERENCES "langs" ("id");
ALTER TABLE "post_likes" ADD FOREIGN KEY ("post_id") REFERENCES "post" ("id");
ALTER TABLE "category_text" ADD FOREIGN KEY ("lang_id") REFERENCES "langs" ("id");
ALTER TABLE "category_text" ADD FOREIGN KEY ("category_id") REFERENCES "category" ("id");
ALTER TABLE "post_categories" ADD FOREIGN KEY ("category_id") REFERENCES "category" ("id");
ALTER TABLE "post_categories" ADD FOREIGN KEY ("post_id") REFERENCES "post" ("id");
ALTER TABLE "author_categories" ADD FOREIGN KEY ("category_id") REFERENCES "category" ("id");
ALTER TABLE "author_categories" ADD FOREIGN KEY ("author_id") REFERENCES "authors" ("id");
ALTER TABLE "comment" ADD FOREIGN KEY ("post_id") REFERENCES "post" ("id");
ALTER TABLE "comment" ADD FOREIGN KEY ("comment_id") REFERENCES "comment" ("id");
ALTER TABLE "comment_likes" ADD FOREIGN KEY ("comment_id") REFERENCES "comment" ("id");
ALTER TABLE "flagged_comment" ADD FOREIGN KEY ("comment_id") REFERENCES "comment" ("id");

48
backend/src/dotenv.cpp Normal file
View file

@ -0,0 +1,48 @@
#include "dotenv.hpp"
#include <algorithm>
#include <fstream>
using namespace Etheryo;
DotEnv::DotEnv()
: _fileData({})
{
}
int DotEnv::readFile(const std::string& path)
{
std::ifstream file;
std::string line;
file.open(path);
if (!file)
return 1;
while (getline(file, line)) {
const size_t pos = line.find("=");
// Commentary detected, ignoring line
if (line.find("#") != std::string::npos)
continue;
// If could not find '=', skip the line
if (pos == std::string::npos)
continue;
// Gathering name
const std::string name = line.substr(0, pos);
// Gathering value and removing quotes
std::string value = line.substr(pos + 1, line.size());
auto deleteQuotes = std::remove(value.begin(), value.end(), '"');
value.erase(deleteQuotes, value.end());
// Adding the name and value to the map
_fileData.emplace(name, value);
}
return 0;
}
const std::string& DotEnv::operator[](const std::string& rvalue)
{
return _fileData[rvalue];
}

52
backend/src/json.cpp Normal file
View file

@ -0,0 +1,52 @@
#include "json.hpp"
using namespace Etheryo;
JsonHandler::JsonHandler()
: _dataBuffer({})
, _buffer({})
{
}
void JsonHandler::clear()
{
_buffer.clear();
}
void JsonHandler::add(const std::vector<std::string>& names)
{
for (const auto& n : names)
_dataBuffer.emplace(n, "");
}
std::string& JsonHandler::operator[](const std::string& rvalue)
{
return _dataBuffer[rvalue];
}
void JsonHandler::push()
{
_buffer.emplace_back(_dataBuffer);
}
std::string JsonHandler::to_str()
{
std::string str = "[";
size_t map_size(0), map_index(0), vec_index(0);
for (const auto& map_data : _buffer) {
vec_index++;
str.push_back('{');
map_size = map_data.size();
for (auto it = map_data.begin(); it != map_data.end(); it++) {
map_index++;
str += ("\"" + it->first + "\": \"" + it->second + (map_index >= map_size ? "\"" : "\","));
}
map_index = 0;
str.push_back('}');
if (vec_index < _buffer.size())
str.push_back(',');
}
str.push_back(']');
return str;
}

49
backend/src/main.cpp Normal file
View file

@ -0,0 +1,49 @@
#include <string>
#include "crow.h"
#include <pqxx/pqxx>
#include "dotenv.hpp"
#include "json.hpp"
pqxx::connection* globalConnection(nullptr);
crow::response test(void);
Etheryo::JsonHandler category;
int main()
{
// Init Json Objects
category.add({ "id", "slug", "icon", "type_icon" });
// Init Postgresql + DotEnv
Etheryo::DotEnv dotenvParser;
dotenvParser.readFile("res/.env");
globalConnection = new pqxx::connection(dotenvParser["DATABASE_URL"]);
std::cout << "Connected to " << globalConnection->dbname() << '\n';
// Init Crow App
crow::SimpleApp app;
CROW_ROUTE(app, "/")
(test);
app.port(8000).multithreaded().run();
globalConnection->close();
}
crow::response test(void)
{
category.clear();
pqxx::work worker { *globalConnection };
auto result = worker.query<int, std::string, std::string, std::string>("select c.id, c.slug, c.icon, c.type_icon FROM category c ORDER BY c.id;");
for (auto [id, slug, icon, type_icon] : result) {
category["id"] = std::to_string(id);
category["slug"] = slug;
category["icon"] = icon;
category["type_icon"] = type_icon;
category.push();
}
auto response = crow::response { "application/json", category.to_str() };
response.add_header("Access-Control-Allow-Origin", "*");
response.add_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
return response;
}

10
frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
frontend/.npmrc Normal file
View file

@ -0,0 +1 @@
engine-strict=true

4
frontend/.prettierignore Normal file
View file

@ -0,0 +1,4 @@
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

8
frontend/.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

22
frontend/README.md Normal file
View file

@ -0,0 +1,22 @@
# Etheryo Blog Frontend
*Powered by the Svelte Framework*
## Developing
Just run this command after installing npm.
```bash
npm install
npm run dev
```
## Building/Running Production
To create production and running it.
```bash
npm install
npm run build
node build
```

1267
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

22
frontend/package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "blog-etheryo",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --check .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.7",
"vite": "^5.0.3"
},
"type": "module"
}

12
frontend/src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1,16 @@
export async function load_post() {
try {
const response = await fetch('http://127.0.0.1:8000');
if (!response.ok) {
return {status: -1};
}
const data = response.status == 200 ? await response.json() : [];
return {
data: data, status: response.status
}
} catch (err) {
return {status: -1};
}
}

View file

@ -0,0 +1,31 @@
<script>
import { onMount } from 'svelte';
import { load_post } from '$lib/js/apicall.js'
$: isLoaded = false;
$: hasLoadFailed = false;
let data;
onMount(async () => {
const loaded_post = await load_post();
if(loaded_post.status == 200)
{
data = loaded_post.data;
isLoaded = true;
}
else
hasLoadFailed = true;
});
</script>
<div>
<h1>Etheryo Blog</h1>
{#if isLoaded}
{#each data as d}
<p>id: {d.id}</p>
{/each}
{:else if hasLoadFailed}
<p>Loading failed :c</p>
{:else}
<p>Loading...</p>
{/if}
</div>

BIN
frontend/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

13
frontend/svelte.config.js Normal file
View file

@ -0,0 +1,13 @@
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

10
frontend/vite.config.js Normal file
View file

@ -0,0 +1,10 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
server: {
host: '0.0.0.0',
port: 5125,
},
});