Compare commits

...

35 Commits

Author SHA1 Message Date
479044d4ea Added quickLog™ 2023-07-29 20:41:17 +01:00
fd58f130a0 Add task blanks input fields, actually 2023-07-29 19:34:12 +01:00
4dcf065501 Add task blanks input fields 2023-07-29 19:28:16 +01:00
75413b2c5e Added 'add task' functionality 2023-07-29 19:25:37 +01:00
453f62cfae Displays 'such empty' if no task exists 2023-07-29 19:13:19 +01:00
fde246ecb7 Displays 'such empty' if no task has been completed 2023-07-29 19:11:50 +01:00
46cc0237ce added installation instructions 2023-07-22 21:42:40 +01:00
3506af77bf hid database for privacy 2023-07-22 21:40:52 +01:00
647e02d28e hid database for privacy 2023-07-22 21:38:27 +01:00
5bd5237bfa removed workflows 2023-07-22 21:38:04 +01:00
f829fb08d1 added test gitea action
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 20s
2023-07-22 21:06:25 +01:00
d77439bafd added test gitea action
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1m27s
2023-07-22 20:53:34 +01:00
3110d315e6 added default activities 2023-07-22 11:22:06 +01:00
507f6c932b added default users 2023-07-22 11:20:24 +01:00
58eda6d1ea cleared db 2023-07-22 11:18:57 +01:00
e82adbe5e4 use port supplied to the program by tags 2023-07-22 11:11:20 +01:00
01b3b7b429 redundant variable 2023-07-22 11:04:02 +01:00
38f2f627a8 Tooltips shows if admin tries to get points 2023-07-22 11:03:28 +01:00
feef4c7800 Task buttons dont add points for admins 2023-07-22 11:01:36 +01:00
1d04006335 Stats card oh history screen 2023-07-22 10:58:14 +01:00
80e3d270c8 JS actually sends amount of points when completed 2023-07-22 10:57:52 +01:00
58b3dfe28a Did not update all SQL requests 2023-07-22 09:45:59 +01:00
758c87652c Adds points to user when action completed 2023-07-21 23:02:59 +01:00
0bdaac368c Finished route for getting user points 2023-07-21 22:53:32 +01:00
31913d0b94 Added route for getting the user points stats 2023-07-21 22:41:50 +01:00
fb9e3130e0 Rename types so they make sense 2023-07-21 22:40:16 +01:00
2aaa448331 Added points to the users table 2023-07-21 22:36:41 +01:00
fa8e4486f3 Added points to the users table 2023-07-21 22:33:14 +01:00
20da46a8d7 Added stat placeholder box 2023-07-21 22:27:42 +01:00
4a375149f6 Complete task javascript 2023-07-21 21:49:55 +01:00
b02dfc0dac Tooltip for the + Task button while it does nothing 2023-07-21 21:49:28 +01:00
3e77074bec Tooltip for the + Task button while it does nothing 2023-07-21 21:49:16 +01:00
bf8594a546 Scrolling of content when it overflows 2023-07-21 21:48:48 +01:00
19110e74b4 Bug fix: Login screen be scrolling 2023-07-21 18:57:17 +01:00
840d6e2ff5 Javascript not understanding how arrays work 2023-07-21 18:50:46 +01:00
8 changed files with 277 additions and 74 deletions

3
.gitignore vendored
View File

@@ -126,7 +126,6 @@ dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
@@ -155,5 +154,7 @@ dist
# custom
database/data.db
#
.idea

View File

@@ -1,11 +1,8 @@
# GiacPoints
A system for keeping track of house chores completed and attributing points to the activities.
# Functionality
Webpage - Login, add chores/activities (admin), tick of chores (user), log of completed chores (all)
API layer - /loginUser, /getTasks, /completeTask, /getHistory?filter, /addTask
Backend - DB, user (name password userid role), activities (taskid name points), history (userid taskid time pointsGained)
# Installation
`Note: Linux only`
1. Clone this repo
2. rename `database/example.db` to `database/data.db`
3. Execute `main`

View File

@@ -39,4 +39,29 @@
#taskButton {
padding-left: 0;
}
#historyContent {
height: 75%;
overflow: scroll;
width: 100%;
}
#stats {
height: 25%;
width: 100%;
}
#tasks {
overflow: scroll;
}
pre {
padding: 0;
background: white;
border-radius: 0;
}
#taskCard {
margin-top: 45%;
}

View File

@@ -8,27 +8,49 @@
<link rel="stylesheet" href="/frontend/index.css">
</head>
<body>
<script src="/frontend/index.js"></script>
<div id="login" class="flex three">
<div class="fourth two-fifth-1000"></div>
<div class="flex one half fifth-1000" style="height: 100%">
<div class="loginSpacer"></div>
<article class="card" style="padding: 5%">
<label for="name">Name</label> <br>
<input type="text" id="name"> <br>
<label for="password">Password</label> <br>
<input type="password" id="password"> <br>
<input type="submit" value="Login" style="width: 100%" onclick="login()" id="loginSubmit"> <br>
</article>
<div class="loginSpacer"></div>
<div id="login">
<div class="flex three demo" style="height: 100%; width: 100%">
<div class="fourth two-fifth-1000"></div>
<div class="flex one demo half fifth-1000" style="height: 100%">
<div class="loginSpacer"></div>
<article class="card" style="padding: 5%">
<label for="name">Name</label> <br>
<input type="text" id="name"> <br>
<label for="password">Password</label> <br>
<input type="password" id="password"> <br>
<input type="submit" value="Login" style="width: 100%" onclick="login()" id="loginSubmit"> <br>
</article>
<div class="loginSpacer"></div>
</div>
<div class="fourth two-fifth-1000"></div>
</div>
<div class="fourth two-fifth-1000"></div>
</div>
<div id="mainPage" style="display: none;">
<div id="content">
<div id="tasks" class="screen flex two demo" style=""></div>
<div id="history" class="screen" style="display: none;"></div>
<div id="addTask" class="screen" style="display: none;">Add Task</div>
<div id="history" class="screen" style="display: none;">
<div id="stats">
<article class="card">
<header>
<h1>Stats</h1>
<pre id="statData"></pre>
</header>
</article>
</div>
<div id="historyContent"></div>
</div>
<div id="addTask" class="screen" style="display: none;">
<article class="card" id="taskCard">
<header>
<h1>New task</h1> <br>
<label for="newTaskName">Task name</label>
<input type="text" id="newTaskName"> <br>
<label for="newTaskPoints">Task points</label>
<input type="number" id="newTaskPoints" min="1" max="5"> <br>
<input onclick="newTask()" value="Create" type="submit"> <br>
</header>
</article>
</div>
</div>
<div id="menu" class="flex three demo">
<div class="third" id="taskButton">
@@ -38,9 +60,11 @@
<span class="button" onclick="switchScreen('history')">History</span>
</div>
<div class="third" id="addButton">
<span class="button" onclick="switchScreen('add')" >+ Task</span>
<span class="button tooltip-top" onclick="switchScreen('add')" >+ Task</span>
<!-- <span class="button tooltip-top" disabled data-tooltip="In construction">+ Task</span>-->
</div>
</div>
</div>
<script src="/frontend/index.js"></script>
</body>
</html>

View File

@@ -1,3 +1,7 @@
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function login() {
// tell the user the app is loading
document.getElementById("loginSubmit").value = "Loading";
@@ -30,11 +34,12 @@ async function login() {
// store user data in session storage
let data = await response.json();
sessionStorage.setItem("name", data.name);
sessionStorage.setItem("role", data.role);
localStorage.setItem("uid", data.uid);
localStorage.setItem("name", data.name);
localStorage.setItem("role", data.role);
// if role is admin, keep auto styling
if (sessionStorage.getItem("role") !== "admin") {
if (localStorage.getItem("role") !== "admin") {
document.getElementById("addButton").setAttribute("style", "display: none;");
document.getElementById("taskButton").classList.value = "half";
document.getElementById("historyButton").classList.value = "half";
@@ -47,42 +52,102 @@ async function login() {
async function populateScreen() {
// get task data
let taskPage = document.getElementById("tasks");
taskPage.innerHTML = "";
let response = await fetch("/getTasks");
let tasksRaw = await response.json();
let tasks = tasksRaw.tasks;
// get historical data
let historyPage = document.getElementById("history");
let historyPage = document.getElementById("historyContent");
historyPage.innerHTML = "";
response = await fetch("/getHistory");
let historyRaw = await response.json();
let history = historyRaw.history;
for (let i = 0; i < tasks.length; i++) {
taskPage.innerHTML += `
<div class="half">
<article class="card">
<header>
<p>${tasks[i].name}</p>
<p>${tasks[i].points}</p>
</header>
</article>
</div>`
// get points data
let statsPage = document.getElementById("statData");
statsPage.innerText = "";
response = await fetch("/getUserPoints");
let statsRaw = await response.json();
let stats = statsRaw.userPoints;
// if it is a user make the tasks add points
// or if admin make it do nothing
if (localStorage.getItem("role") === "user") {
try {
for (let i = 0; i < tasks.length; i++) {
taskPage.innerHTML += `
<div id="${tasks[i].tid}" class="half" style="height: fit-content">
<div class="button" onclick="completeTask(${tasks[i].tid})">
<p id="taskName">${tasks[i].name}</p>
<p id="${tasks[i].tid}-p">${tasks[i].points} points</p>
</div>
</div>`
}
} catch (e) {
taskPage.innerHTML +=`<sub>such empty</sub>`
}
} else if (localStorage.getItem("role") === "admin") {
try {
for (let i = 0; i < tasks.length; i++) {
taskPage.innerHTML += `
<div id="${tasks[i].tid}" class="half" style="height: fit-content">
<div class="button" disabled data-tooltip="Admins can't get points">
<p id="taskName">${tasks[i].name}</p>
<p id="${tasks[i].tid}-p">${tasks[i].points} points</p>
</div>
</div>`
}
} catch (e) {
taskPage.innerHTML +=`<sub>such empty</sub>`
}
}
for (let i = history.length; i > 0; i-=1) {
try {
for (let i = (history.length -1); i >= 0; i-=1) {
historyPage.innerHTML += `
<div class="full">
<article class="card">
<header>
<p>User: ${history[i].user}</p>
<p>Task: ${history[i].task}</p>
<p>Time: ${history[i].time}</p>
<p>Points gained: ${history[i].pointsGained}</p>
</header>
</article>
</div>`
}
} catch (e) {
historyPage.innerHTML += `
<div class="full">
<article class="card">
<header>
<p>User: ${history[i].user}</p>
<p>Task: ${history[i].task}</p>
<p>Time: ${history[i].time}</p>
<p>Points gained: ${history[i].pointsGained}</p>
</header>
</article>
</div>`
<sub>Such empty</sub>`
}
for (let i = 0; i < stats.length; i++) {
// put user points in box
statsPage.innerText += `${stats[i].name}: ${stats[i].points} \n`
}
}
function switchScreen(button) {
async function completeTask(taskID) {
let points = document.getElementById(`${taskID}-p`).innerText.split(" ")[0];
let time = new Date().toISOString().split(".")[0];
let uid = localStorage.getItem("uid");
await fetch("/completeTask", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
uid: Number(uid),
tid: Number(taskID),
time: time,
pointsGained: Number(points)
})
});
}
async function switchScreen(button) {
await populateScreen();
document.getElementById("tasks").setAttribute("style", "display: none;");
document.getElementById("history").setAttribute("style", "display: none;");
document.getElementById("addTask").setAttribute("style", "display: none;");
@@ -94,4 +159,42 @@ function switchScreen(button) {
} else if (button === "add") {
document.getElementById("addTask").setAttribute("style", "");
}
}
}
async function newTask() {
let points = document.getElementById(`newTaskPoints`);
let name = document.getElementById(`newTaskName`);
await fetch("/addTask", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
tid: 0,
name: name.value,
points: Number(points.value),
})
});
points.value = "";
name.value = "";
}
async function fastLog() {
sleep(500);
if (localStorage.getItem("uid") != null && localStorage.getItem("name") != null && localStorage.getItem("role") != null) {
document.getElementById("login").setAttribute("style", "display: none;");
document.getElementById("mainPage").setAttribute("style", "");
if (localStorage.getItem("role") !== "admin") {
document.getElementById("addButton").setAttribute("style", "display: none;");
document.getElementById("taskButton").classList.value = "half";
document.getElementById("historyButton").classList.value = "half";
document.getElementById("menu").classList.value = "flex two";
}
await populateScreen();
} else {
// do nothing
}
}
fastLog();

BIN
main

Binary file not shown.

95
main.go
View File

@@ -2,6 +2,7 @@ package main
import (
"database/sql"
"flag"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
"net/http"
@@ -17,11 +18,12 @@ type loginInput struct {
Password string `json:"password"`
}
type loginOutput struct {
type userData struct {
UID int `json:"uid"`
Name string `json:"name"`
Password string `json:"password"`
Role string `json:"role"`
Points int `json:"points"`
}
type task struct {
@@ -34,14 +36,14 @@ type taskArray struct {
Tasks []task `json:"tasks"`
}
type historyReqData struct {
type newHistoryData struct {
UID int `json:"uid"`
TID int `json:"tid"`
Time string `json:"time"`
PointsGained int `json:"pointsGained"`
}
type historyResData struct {
type historyData struct {
User string `json:"user"`
Task string `json:"task"`
Time string `json:"time"`
@@ -49,32 +51,41 @@ type historyResData struct {
}
type historyArray struct {
History []historyResData `json:"history"`
History []historyData `json:"history"`
}
type userPointsData struct {
Name string `json:"name"`
Points int `json:"points"`
}
type userPointsArray struct {
UserPoints []userPointsData `json:"userPoints"`
}
// log the user into their account
func login(c *gin.Context) {
// get the username and password from the request
var userData loginInput
if err := c.BindJSON(&userData); err != nil {
c.IndentedJSON(http.StatusBadRequest, userData)
var requestedUser loginInput
if err := c.BindJSON(&requestedUser); err != nil {
c.IndentedJSON(http.StatusBadRequest, requestedUser)
return
}
// check for user using given credentials
stmt, err := db.Prepare("SELECT * FROM users WHERE name=?")
checkErr(err)
defer stmt.Close()
var user loginOutput
err = stmt.QueryRow(userData.Name).Scan(&user.UID, &user.Name, &user.Password, &user.Role)
var user userData
err = stmt.QueryRow(requestedUser.Name).Scan(&user.UID, &user.Name, &user.Password, &user.Role, &user.Points)
if err != nil {
// search failed user not real
c.IndentedJSON(http.StatusNotFound, userData)
c.IndentedJSON(http.StatusNotFound, requestedUser)
panic(err)
return
}
if user.Password != userData.Password {
if user.Password != requestedUser.Password {
// user not real
c.IndentedJSON(http.StatusNotFound, userData)
c.IndentedJSON(http.StatusNotFound, requestedUser)
panic(err)
return
} else {
@@ -107,7 +118,7 @@ func getTasks(c *gin.Context) {
func completeTask(c *gin.Context) {
// get the task data from the request
var completedTask historyReqData
var completedTask newHistoryData
if err := c.BindJSON(&completedTask); err != nil {
c.IndentedJSON(http.StatusBadRequest, completedTask)
return
@@ -123,17 +134,30 @@ func completeTask(c *gin.Context) {
c.IndentedJSON(http.StatusNotModified, completedTask)
return
}
// add points to user table
stmt, err = db.Prepare("UPDATE users SET points = points + ? WHERE uid=?")
if err != nil {
c.IndentedJSON(http.StatusNotModified, completedTask)
return
}
_, err = stmt.Exec(completedTask.PointsGained, completedTask.UID)
if err != nil {
c.IndentedJSON(http.StatusNotModified, completedTask)
return
}
c.IndentedJSON(http.StatusOK, completedTask)
}
func getHistory(c *gin.Context) {
// get the log of past points gained
var history []historyReqData
// get array of all historyReqData
var history []newHistoryData
// get array of all history Data
rows, err := db.Query("SELECT * FROM history")
checkErr(err)
for rows.Next() {
var tempHistoryData historyReqData
var tempHistoryData newHistoryData
err = rows.Scan(&tempHistoryData.UID, &tempHistoryData.TID, &tempHistoryData.Time, &tempHistoryData.PointsGained)
if err != nil {
c.IndentedJSON(http.StatusNotFound, history)
@@ -143,15 +167,15 @@ func getHistory(c *gin.Context) {
}
rows.Close()
var historyRes []historyResData
var historyRes []historyData
// make the data human-readable
for i := 0; i < len(history); i++ {
var tempHistory historyResData
var tempHistory historyData
// get the username
var tempUser loginOutput
var tempUser userData
stmt, err := db.Prepare("SELECT * FROM users WHERE uid=?")
checkErr(err)
err = stmt.QueryRow(history[i].UID).Scan(&tempUser.UID, &tempUser.Name, &tempUser.Password, &tempUser.Role)
err = stmt.QueryRow(history[i].UID).Scan(&tempUser.UID, &tempUser.Name, &tempUser.Password, &tempUser.Role, &tempUser.Points)
checkErr(err)
tempHistory.User = tempUser.Name
stmt.Close()
@@ -197,7 +221,35 @@ func addTask(c *gin.Context) {
c.IndentedJSON(http.StatusOK, newTask)
}
func getUserPoints(c *gin.Context) {
// get the names of all users
var allUsers []userPointsData
rows, err := db.Query("SELECT name, points FROM users WHERE role='user'")
checkErr(err)
// put them in an array of users
for rows.Next() {
var tempUser userPointsData
err = rows.Scan(&tempUser.Name, &tempUser.Points)
if err != nil {
c.IndentedJSON(http.StatusNotFound, allUsers)
return
}
allUsers = append(allUsers, tempUser)
}
rows.Close()
// return to the requester
var jsonUserPoints userPointsArray
jsonUserPoints.UserPoints = allUsers
c.IndentedJSON(http.StatusOK, jsonUserPoints)
}
func main() {
var port string
flag.StringVar(&port, "port", "localhost:8080", "define the port you want the program to use to serve")
flag.Parse()
//release mode
//gin.SetMode(gin.ReleaseMode)
router := gin.Default()
@@ -208,6 +260,7 @@ func main() {
router.POST("/addTask", addTask)
router.GET("/getTasks", getTasks)
router.GET("/getHistory", getHistory)
router.GET("/getUserPoints", getUserPoints)
// page routes
router.LoadHTMLGlob("frontend/*")
@@ -216,7 +269,7 @@ func main() {
})
router.Static("/frontend", "./frontend")
router.Run("localhost:8080")
router.Run(port)
}
func checkErr(err error) {