sync
This commit is contained in:
@@ -4,5 +4,6 @@ go 1.24.2
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/neo4j/neo4j-go-driver/v5 v5.28.0 // indirect
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/neo4j/neo4j-go-driver/v5 v5.28.0 h1:chDT68PHNa8JZRmjSkGzAbk1weLWo4rMtDvccvpobg0=
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.0 h1:chDT68PHNa8JZRmjSkGzAbk1weLWo4rMtDvccvpobg0=
|
||||||
github.com/neo4j/neo4j-go-driver/v5 v5.28.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
||||||
|
|||||||
+13
-5
@@ -14,6 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ func serve() {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
driver_ctx := context.Background()
|
driver_ctx := context.Background()
|
||||||
driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687", neo4j.BasicAuth("neo4j", "jzLbsy2C23WHzQ-", ""))
|
driver, err := neo4j.NewDriverWithContext(os.Getenv("NEO4J_BOLT"), neo4j.BasicAuth("neo4j", os.Getenv("NEO4J_PASSWORD"), ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -43,18 +44,19 @@ func serve() {
|
|||||||
|
|
||||||
err = driver.VerifyConnectivity(driver_ctx)
|
err = driver.VerifyConnectivity(driver_ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println("Error connecting to neo4j.")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("neo4j connection established.")
|
fmt.Println("neo4j connection established.")
|
||||||
|
|
||||||
rsrc_endpoints := []string{
|
rsrc_endpoints := []string{
|
||||||
|
"/auth",
|
||||||
"/users",
|
"/users",
|
||||||
"/users",
|
"/users",
|
||||||
"/users/{user_id}",
|
"/users/{user_id}",
|
||||||
"/users/{user_id}",
|
"/users/{user_id}",
|
||||||
"/users/{user_id}",
|
"/users/{user_id}",
|
||||||
"/users/{user_id}",
|
|
||||||
"/users/{user_id}/posts",
|
"/users/{user_id}/posts",
|
||||||
"/users/{user_id}/spaces",
|
"/users/{user_id}/spaces",
|
||||||
"/spaces",
|
"/spaces",
|
||||||
@@ -74,9 +76,9 @@ func serve() {
|
|||||||
|
|
||||||
methods := []string{
|
methods := []string{
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
http.MethodGet,
|
|
||||||
http.MethodGet,
|
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodGet,
|
||||||
http.MethodPatch,
|
http.MethodPatch,
|
||||||
http.MethodDelete,
|
http.MethodDelete,
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
@@ -97,10 +99,10 @@ func serve() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
functions := []func(http.ResponseWriter, *http.Request){
|
functions := []func(http.ResponseWriter, *http.Request){
|
||||||
|
auth_user(driver, driver_ctx),
|
||||||
create_new_user(driver, driver_ctx),
|
create_new_user(driver, driver_ctx),
|
||||||
retrieve_all_users(driver, driver_ctx),
|
retrieve_all_users(driver, driver_ctx),
|
||||||
retrieve_user(driver, driver_ctx),
|
retrieve_user(driver, driver_ctx),
|
||||||
auth_user(driver, driver_ctx),
|
|
||||||
update_user(driver, driver_ctx),
|
update_user(driver, driver_ctx),
|
||||||
delete_user(driver, driver_ctx),
|
delete_user(driver, driver_ctx),
|
||||||
retrieve_users_posts(driver, driver_ctx),
|
retrieve_users_posts(driver, driver_ctx),
|
||||||
@@ -168,5 +170,11 @@ func serve() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
err := godotenv.Load("../.env")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error loading .env.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
serve()
|
serve()
|
||||||
}
|
}
|
||||||
|
|||||||
+106
-6
@@ -3,25 +3,113 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
// "fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
//"io"
|
//"io"
|
||||||
// "log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
// "os"
|
// "os"
|
||||||
// "os/signal"
|
// "os/signal"
|
||||||
// "reflect"
|
// "reflect"
|
||||||
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// "sync"
|
// "sync"
|
||||||
// "time"
|
// "time"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func id_exists(id int, driver neo4j.DriverWithContext, driver_ctx context.Context) bool {
|
||||||
|
result, _ := neo4j.ExecuteQuery(driver_ctx, driver,
|
||||||
|
"MATCH (u:User) WHERE id(u) = $id RETURN count(u)",
|
||||||
|
map[string]any{
|
||||||
|
"id": id,
|
||||||
|
}, neo4j.EagerResultTransformer,
|
||||||
|
neo4j.ExecuteQueryWithDatabase("neo4j"))
|
||||||
|
|
||||||
|
if len(result.Records) > 0 {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func create_new_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
func create_new_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:2025")
|
||||||
|
|
||||||
|
err := r.ParseMultipartForm(10 << 20) // Limit to 10MB
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to parse form.")
|
||||||
|
}
|
||||||
|
|
||||||
|
username := r.FormValue("username")
|
||||||
|
email := r.FormValue("email")
|
||||||
|
password_hash := r.FormValue("password_hash")
|
||||||
|
|
||||||
|
id := rand.Intn(999999999999) + 1
|
||||||
|
for id_exists(id, driver, driver_ctx) {
|
||||||
|
id = rand.Intn(999999999999) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
profile_picture_file, header, err := r.FormFile("profile_picture")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error retrieving file from form.")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer profile_picture_file.Close()
|
||||||
|
filename_parts := strings.Split(header.Filename, ".")
|
||||||
|
ext := filename_parts[len(filename_parts)-1]
|
||||||
|
|
||||||
|
dst, err := os.Create(os.Getenv("PROFILE_IMAGES_FQ_PATH") + strconv.Itoa(id) + ext)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error saving profile picture.")
|
||||||
|
}
|
||||||
|
defer dst.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dst, profile_picture_file)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error writing file to filesystem.")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := neo4j.ExecuteQuery(driver_ctx, driver,
|
||||||
|
"CREATE (u:User {id: $id, username: $username, email: $email, password_hash: $password_hash, profile_picture: $profile_picture, created_at: datetime()})-[:USER_TYPE]->(:Role {name: 'User'}) RETURN elementId(u) AS id",
|
||||||
|
map[string]any{
|
||||||
|
"id": id,
|
||||||
|
"username": username,
|
||||||
|
"email": email,
|
||||||
|
"password_hash": password_hash,
|
||||||
|
"profile_picture": strconv.Itoa(id) + ext,
|
||||||
|
}, neo4j.EagerResultTransformer,
|
||||||
|
neo4j.ExecuteQueryWithDatabase("neo4j"))
|
||||||
|
|
||||||
|
if len(result.Records) > 0 {
|
||||||
|
ret := struct {
|
||||||
|
Auth bool `json:"auth"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
}{
|
||||||
|
Auth: true,
|
||||||
|
Id: strconv.Itoa(id),
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(ret)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintf(w, string(json))
|
||||||
|
log.Println("User ID:", id, "Action: Signup")
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func retrieve_all_users(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
func retrieve_all_users(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
@@ -32,15 +120,16 @@ func retrieve_all_users(driver neo4j.DriverWithContext, driver_ctx context.Conte
|
|||||||
|
|
||||||
func retrieve_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
func retrieve_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
user_id, _ := strconv.Atoi(mux.Vars(r)["user_id"])
|
id, _ := strconv.Atoi(mux.Vars(r)["user_id"])
|
||||||
|
|
||||||
result, _ := neo4j.ExecuteQuery(driver_ctx, driver,
|
result, _ := neo4j.ExecuteQuery(driver_ctx, driver,
|
||||||
"MATCH (u:User) WHERE id(u) = $user_id RETURN u.username AS username, u.email AS email, u.profile_picture AS profile_picture",
|
"MATCH (u:User) WHERE id(u) = $user_id RETURN u.username AS username, u.email AS email, u.profile_picture AS profile_picture",
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"user_id": user_id,
|
"user_id": id,
|
||||||
}, neo4j.EagerResultTransformer,
|
}, neo4j.EagerResultTransformer,
|
||||||
neo4j.ExecuteQueryWithDatabase("neo4j"))
|
neo4j.ExecuteQueryWithDatabase("neo4j"))
|
||||||
|
|
||||||
|
if len(result.Records) > 0 {
|
||||||
record := result.Records[0]
|
record := result.Records[0]
|
||||||
vals := record.AsMap()
|
vals := record.AsMap()
|
||||||
user := struct {
|
user := struct {
|
||||||
@@ -58,12 +147,20 @@ func retrieve_user(driver neo4j.DriverWithContext, driver_ctx context.Context) h
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
send_response(w, json)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintf(w, string(json))
|
||||||
|
log.Println("User ID:", id, "Action: Retrieval")
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func auth_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
func auth_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:2025")
|
||||||
|
|
||||||
username := r.FormValue("username")
|
username := r.FormValue("username")
|
||||||
password_hash := r.FormValue("password_hash")
|
password_hash := r.FormValue("password_hash")
|
||||||
|
|
||||||
@@ -96,7 +193,10 @@ func auth_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
send_response(w, json)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintf(w, string(json))
|
||||||
|
log.Println("User ID:", id, "Action: Login")
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ if (isset($_SESSION['username'])) {
|
|||||||
$nav .= '
|
$nav .= '
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="js/nav.js" defer></script>
|
||||||
</nav>
|
</nav>
|
||||||
';
|
';
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ function login_form($error_msg = '')
|
|||||||
$form = '
|
$form = '
|
||||||
<div class="form_card">
|
<div class="form_card">
|
||||||
<h3 class="underline">Log In</h3>
|
<h3 class="underline">Log In</h3>
|
||||||
<form method="post">
|
<form method="post" id="login_form">
|
||||||
<input type="hidden" name="form_id" value="login_form">
|
<input type="hidden" name="form_id" value="login_form">
|
||||||
|
<input type="hidden" name="password_hash" value="" id="password_hash">
|
||||||
|
|
||||||
<label for="username">Username / Email</label>
|
<label for="username">Username / Email</label>
|
||||||
<input id="username" name="username" spellcheck="false" required>
|
<input id="username" name="username" spellcheck="false" required autofocus>
|
||||||
|
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<input id="password" name="password" type="password" spellcheck="false" required>
|
<input id="password" name="password" type="password" spellcheck="false" required>
|
||||||
@@ -21,6 +22,8 @@ function login_form($error_msg = '')
|
|||||||
<input class="button" type="submit" value="Log In">
|
<input class="button" type="submit" value="Log In">
|
||||||
' . $error_msg . '
|
' . $error_msg . '
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<script src="js/login.js" defer></script>
|
||||||
</div>
|
</div>
|
||||||
';
|
';
|
||||||
|
|
||||||
|
|||||||
@@ -172,3 +172,37 @@
|
|||||||
.modal_activation_area:hover {
|
.modal_activation_area:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/********** Animations **********/
|
||||||
|
@view-transition {
|
||||||
|
navigation: auto;
|
||||||
|
animation-duration: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-old(root) {
|
||||||
|
animation: fade-out 750ms forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(root) {
|
||||||
|
animation: fade-in 750ms forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-out {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ if (isset($_POST['ajax_id'])) {
|
|||||||
case 'todo':
|
case 'todo':
|
||||||
handle_todo();
|
handle_todo();
|
||||||
break;
|
break;
|
||||||
|
case 'login':
|
||||||
|
handle_login();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$response = [
|
$response = [
|
||||||
@@ -15,6 +18,22 @@ if (isset($_POST['ajax_id'])) {
|
|||||||
echo json_encode($response);
|
echo json_encode($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handle_login()
|
||||||
|
{
|
||||||
|
$response = ['log' => []];
|
||||||
|
|
||||||
|
if (isset($_POST['user_id']) && isset($_POST['username']) && isset($_POST['role'])) {
|
||||||
|
$response['log'][] = 'Required parameters met. Logging user in.';
|
||||||
|
$_SESSION['user_id'] = $_POST['user_id'];
|
||||||
|
$_SESSION['username'] = $_POST['username'];
|
||||||
|
$_SESSION['role_id'] = $_POST['role'];
|
||||||
|
|
||||||
|
$response['redirect'] = 'articles.php?view=list';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
}
|
||||||
|
|
||||||
function handle_todo()
|
function handle_todo()
|
||||||
{
|
{
|
||||||
include 'functions/todo_functions.php';
|
include 'functions/todo_functions.php';
|
||||||
|
|||||||
@@ -40,38 +40,6 @@ function logout()
|
|||||||
header('Location: .');
|
header('Location: .');
|
||||||
}
|
}
|
||||||
|
|
||||||
function login($username, $password, $stay_logged_in)
|
|
||||||
{
|
|
||||||
$password_hash = hash('sha256', $password);
|
|
||||||
|
|
||||||
$user = exec_stmt('SELECT user_id, username, role_id FROM user WHERE username = ? OR email = ? AND password_hash = ?', 'sss', $username, $username, $password_hash)->fetch_assoc();
|
|
||||||
|
|
||||||
if ($user == null) {
|
|
||||||
$error_msg = '
|
|
||||||
<div class="error">
|
|
||||||
Username and/or Password are invalid!
|
|
||||||
</div>
|
|
||||||
';
|
|
||||||
|
|
||||||
return $error_msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
$_SESSION['user_id'] = $user['user_id'];
|
|
||||||
$_SESSION['username'] = $user['username'];
|
|
||||||
$_SESSION['role_id'] = $user['role_id'];
|
|
||||||
|
|
||||||
if ($stay_logged_in) {
|
|
||||||
$token = bin2hex(random_bytes(64));
|
|
||||||
// echo $token . '<br>';
|
|
||||||
// echo strlen($token);
|
|
||||||
setcookie('remember_user', $token, time() + 60 * 60 * 24 * 30, '/', '', true, true);
|
|
||||||
|
|
||||||
exec_stmt('INSERT INTO remember_user (token, user_id, remote_addr, http_forward) VALUES (?, ?, ?, ?)', 'siss', $token, $user['user_id'], hash('sha256', $_SERVER['REMOTE_ADDR']), (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? hash('sha256', $_SERVER['HTTP_X_FORWARDED_FOR']) : null));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_user($username, $email, $password, $verify_password, $role_id, $upload = null, $x = null, $y = null, $crop_width = null, $honeypot = null)
|
function create_user($username, $email, $password, $verify_password, $role_id, $upload = null, $x = null, $y = null, $crop_width = null, $honeypot = null)
|
||||||
{
|
{
|
||||||
if (!empty($honeypot)) {
|
if (!empty($honeypot)) {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
header("Content-Security-Policy: default-src 'self'; font-src 'self' https://www.nerdfonts.com/assets/fonts/Symbols-2048-em%20Nerd%20Font%20Complete.woff2; style-src 'self' 'unsafe-inline' https://nerdfonts.com/assets/css/webfont.css https://www.nerdfonts.com/assets/css/webfont.css; script-src 'self' 'unsafe-inline'; img-src 'self';");
|
header("Content-Security-Policy: default-src 'self' localhost:7477; font-src 'self' https://www.nerdfonts.com/assets/fonts/Symbols-2048-em%20Nerd%20Font%20Complete.woff2; style-src 'self' 'unsafe-inline' https://nerdfonts.com/assets/css/webfont.css https://www.nerdfonts.com/assets/css/webfont.css; script-src 'self' 'unsafe-inline'; img-src 'self';");
|
||||||
|
|
||||||
// Initialize Composer.
|
// Initialize Composer.
|
||||||
require_once 'vendor/autoload.php';
|
require_once 'vendor/autoload.php';
|
||||||
|
|||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
const form = document.querySelector('#login_form');
|
||||||
|
const username_field = form.querySelector('#username');
|
||||||
|
const password_field = form.querySelector('#password');
|
||||||
|
const password_hash_field = form.querySelector('#password_hash');
|
||||||
|
const submit_button = form.querySelector('input[type="submit"]');
|
||||||
|
|
||||||
|
submit_button.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const password_hash = await hashString(password_field.value);
|
||||||
|
const urlParams = new URLSearchParams(document.location.search);
|
||||||
|
const redirect = decodeURI(urlParams.get('redirect'));
|
||||||
|
requestAuth(username_field.value, password_hash, redirect);
|
||||||
|
});
|
||||||
|
|
||||||
|
function requestAuth(username, password_hash, redirect) {
|
||||||
|
fetch('http://localhost:7477/auth', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: 'username=' + username + '&password_hash=' + password_hash,
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
console.log('Login Failed!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json(); // Parse response body as JSON
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
notifyServer(data["id"], username, 1, redirect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyServer(id, username, role, redirect) {
|
||||||
|
fetch('director.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: 'ajax_id=login&user_id=' + id + '&username=' + username + '&role=' + role,
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if ('startViewTransition' in document) {
|
||||||
|
document.startViewTransition(() => {
|
||||||
|
if (redirect == null) {
|
||||||
|
window.location.href = 'articles.php?view=list&sort=new';
|
||||||
|
} else {
|
||||||
|
window.location.href = redirect;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback for browsers that don't support View Transitions
|
||||||
|
if (redirect == null) {
|
||||||
|
window.location.href = 'articles.php?view=list&sort=new';
|
||||||
|
} else {
|
||||||
|
window.location.href = redirect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hashString(str) {
|
||||||
|
const encoded = new TextEncoder().encode(str);
|
||||||
|
const digest = await crypto.subtle.digest('SHA-256', encoded);
|
||||||
|
const hashArray = Array.from(new Uint8Array(digest));
|
||||||
|
const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
||||||
|
return hashHex;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const nav = document.querySelector('nav');
|
||||||
|
const nav_links = nav.querySelectorAll('ul li a');
|
||||||
|
const theme_toggle = nav.querySelector('button');
|
||||||
|
|
||||||
|
nav_links.forEach((el) => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
el.preventDefault();
|
||||||
|
if ('startViewTransition' in document) {
|
||||||
|
document.startViewTransition(() => {
|
||||||
|
window.location.href = el.getAttribute('href');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.location.href = el.getAttribute('href');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
+2
-63
@@ -15,72 +15,11 @@
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var todos = document.getElementsByClassName('todo_item');
|
var todos = document.getElementsByClassName('todo_item');
|
||||||
var action_items = (document.getElementById('todo_actions')).querySelectorAll('button');
|
//var todos = document.querySelectorAll('todo_item');
|
||||||
|
|
||||||
// General Modal handling
|
|
||||||
function handle_modal(modal) {
|
|
||||||
console.log('handling modal');
|
|
||||||
let modal_close = modal.querySelector('.modal_close');
|
|
||||||
let submit_button = modal.querySelector('input[type="submit"]');
|
|
||||||
modal.style.display = 'block';
|
|
||||||
|
|
||||||
modal_close.addEventListener('click', function() {
|
|
||||||
modal.style.display = "none";
|
|
||||||
});
|
|
||||||
|
|
||||||
submit_button.addEventListener('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
let todo = {
|
|
||||||
'title': modal.querySelector('#title').value,
|
|
||||||
'description': modal.querySelector('#description'),
|
|
||||||
'priority': modal.querySelector('#priority'),
|
|
||||||
'due_date': modal.querySelector('#due_date'),
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch('director.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: 'ajax_id=todo&action_id=create_update_todo&todo_id=' + this.todo_id + '&todo=' + encodeURIComponent(JSON.stringify(todo)),
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
return response.json(); // Or handle other response formats
|
|
||||||
})
|
|
||||||
.then(responseData => {
|
|
||||||
console.log('Response received:', responseData);
|
|
||||||
// Handle the server's response
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < action_items.length; i++) {
|
|
||||||
let action = action_items[i];
|
|
||||||
|
|
||||||
action.addEventListener('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
switch (action.value) {
|
|
||||||
case 'new':
|
|
||||||
let new_todo_item = document.querySelector('#new_todo_item');
|
|
||||||
handle_modal(new_todo_item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < todos.length; i++) {
|
for (let i = 0; i < todos.length; i++) {
|
||||||
let item = todos[i];
|
var item = todos[i];
|
||||||
|
|
||||||
// Detailed todo item view via a modal
|
|
||||||
let modal_button = item.querySelector('.modal_activation_area');
|
|
||||||
let modal = item.querySelector('.modal');
|
|
||||||
modal_button.addEventListener('click', () => handle_modal(modal));
|
|
||||||
|
|
||||||
// Checkboxes & AJAX for updating the DB
|
|
||||||
var checkbox = item.querySelector('input[type="checkbox"]');
|
var checkbox = item.querySelector('input[type="checkbox"]');
|
||||||
checkbox.todo_id = item.querySelector('input[name="todo_id"]').value;
|
checkbox.todo_id = item.querySelector('input[name="todo_id"]').value;
|
||||||
checkbox.is_checked = checkbox.checked;
|
checkbox.is_checked = checkbox.checked;
|
||||||
|
|||||||
Reference in New Issue
Block a user