diff --git a/admin/admin.py b/admin/admin.py index 57bee49..d5f82be 100644 --- a/admin/admin.py +++ b/admin/admin.py @@ -301,7 +301,7 @@ commands = [ "check_services", "add_news", "del_news", - "logs" + "logs", ] if __name__ == "__main__": diff --git a/api/database/database.go b/api/database/database.go index d9cb1de..7607f44 100644 --- a/api/database/database.go +++ b/api/database/database.go @@ -17,6 +17,18 @@ func (db *Type) Load() (err error) { return fmt.Errorf("cannot access the database: %s", err.Error()) } + // see database/visitor.go + _, err = db.sql.Exec(` + CREATE TABLE IF NOT EXISTS visitor_count( + id TEXT NOT NULL UNIQUE, + count INTEGER NOT NULL + ); + `) + + if err != nil { + return fmt.Errorf("failed to create the visitor_count table: %s", err.Error()) + } + // see database/service.go _, err = db.sql.Exec(` CREATE TABLE IF NOT EXISTS services( diff --git a/api/database/visitor.go b/api/database/visitor.go new file mode 100644 index 0000000..13a937b --- /dev/null +++ b/api/database/visitor.go @@ -0,0 +1,48 @@ +package database + +import ( + "database/sql" +) + +func (db *Type) VisitorGet() (uint64, error) { + var ( + row *sql.Row + count uint64 + err error + ) + + if row = db.sql.QueryRow("SELECT count FROM visitor_count WHERE id = 0"); row == nil { + return 0, nil + } + + if err = row.Scan(&count); err != nil && err != sql.ErrNoRows { + return 0, err + } + + if err == sql.ErrNoRows { + return 0, nil + } + + return count, nil +} + +func (db *Type) VisitorIncrement() (err error) { + if _, err = db.sql.Exec("UPDATE visitor_count SET count = count + 1 WHERE id = 0"); err != nil && err != sql.ErrNoRows { + return err + } + + // TODO: err is always nil even if there is no rows for some reason, check sql.Result instead + + if err == sql.ErrNoRows { + _, err = db.sql.Exec( + `INSERT INTO visitor_count( + id, count + ) values(?, ?)`, + 0, 0, + ) + + return err + } + + return nil +} diff --git a/api/main.go b/api/main.go index bd92130..3c8caff 100644 --- a/api/main.go +++ b/api/main.go @@ -89,6 +89,7 @@ func main() { // v1 user routes v1.Get("/services", routes.GET_Services) + v1.Get("/visitor", routes.GET_Visitor) v1.Get("/news/:lang", routes.GET_News) // v1 admin routes diff --git a/api/routes/visitor.go b/api/routes/visitor.go new file mode 100644 index 0000000..67865a9 --- /dev/null +++ b/api/routes/visitor.go @@ -0,0 +1,50 @@ +package routes + +import ( + "github.com/gofiber/fiber/v2" + "github.com/ngn13/website/api/database" + "github.com/ngn13/website/api/util" +) + +const LAST_ADDRS_MAX = 30 + +var last_addrs []string + +func GET_Visitor(c *fiber.Ctx) error { + var ( + err error + count uint64 + ) + + db := c.Locals("database").(*database.Type) + new_addr := util.GetSHA1(util.IP(c)) + + for _, addr := range last_addrs { + if new_addr == addr { + if count, err = db.VisitorGet(); err != nil { + return util.ErrInternal(c, err) + } + + return util.JSON(c, 200, fiber.Map{ + "result": count, + }) + } + } + + if err = db.VisitorIncrement(); err != nil { + return util.ErrInternal(c, err) + } + + if count, err = db.VisitorGet(); err != nil { + return util.ErrInternal(c, err) + } + + if len(last_addrs) > LAST_ADDRS_MAX { + last_addrs = append(last_addrs[:0], last_addrs[1:]...) + last_addrs = append(last_addrs, new_addr) + } + + return util.JSON(c, 200, fiber.Map{ + "result": count, + }) +} diff --git a/app/package.json b/app/package.json index 67c5ca6..75a6a10 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "version": "6.0", "private": true, "scripts": { - "dev": "VITE_API_URL=http://127.0.0.1:7001 VITE_FRONTEND_URL=http://localhost:5173 vite dev", + "dev": "VITE_BUG_REPORT_URL=https://github.com/ngn13/website/issues VITE_API_URL=http://127.0.0.1:7001 VITE_FRONTEND_URL=http://localhost:5173 vite dev", "build": "vite build", "preview": "vite preview --host", "lint": "prettier --check .", diff --git a/app/src/lib/api.js b/app/src/lib/api.js index f108136..ed51572 100644 --- a/app/src/lib/api.js +++ b/app/src/lib/api.js @@ -1,21 +1,33 @@ +import { urljoin } from "$lib/util.js"; + const version = "v1"; -const url = new URL(version + "/", import.meta.env.VITE_API_URL).href; +const url = urljoin(import.meta.env.VITE_API_URL, version); -function join(path) { - if (null === path || path === "") return url; +function api_url(path = null, query = {}) { + return urljoin(url, path, query); +} - if (path[0] === "/") path = path.slice(1); +function check_err(json) { + if (!("error" in json)) throw new Error('API response is missing the "error" key'); - return new URL(path, url).href; + if (json["error"] != "") throw new Error(`API returned an error: ${json["error"]}`); + + if (!("result" in json)) throw new Error('API response is missing the "result" key'); +} + +async function GET(fetch, url) { + const res = await fetch(url); + const json = await res.json(); + check_err(json); + return json["result"]; +} + +async function visitor(fetch) { + return GET(fetch, api_url("/visitor")); } async function services(fetch) { - const res = await fetch(join("/services")); - const json = await res.json(); - - if (!("result" in json)) return []; - - return json.result; + return GET(fetch, api_url("/services")); } -export { version, join, services }; +export { version, api_url, visitor, services }; diff --git a/app/src/lib/content.svelte b/app/src/lib/content.svelte deleted file mode 100644 index 3d2cae5..0000000 --- a/app/src/lib/content.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - -
- -
- - diff --git a/app/src/lib/error.svelte b/app/src/lib/error.svelte new file mode 100644 index 0000000..64ffef5 --- /dev/null +++ b/app/src/lib/error.svelte @@ -0,0 +1,61 @@ + + +
+

{$_("error.title")}

+ + {#if error === ""} + Unknown error + {:else} + {error} + {/if} + + + {$_("error.report")} + + +
+ + diff --git a/app/src/lib/footer.svelte b/app/src/lib/footer.svelte index c535879..d737d9a 100644 --- a/app/src/lib/footer.svelte +++ b/app/src/lib/footer.svelte @@ -1,13 +1,15 @@