Compare commits

...

22 commits

Author SHA1 Message Date
Imbus
8c8e57cb50 Merge branch 'frontend' 2024-03-13 13:42:49 +01:00
Imbus
25fdf3bb9b Nuke dead code 2024-03-13 12:29:15 +01:00
Imbus
c4632104a8 Commenting out trigger related to salts 2024-03-13 11:46:17 +01:00
Imbus
736cebe036 Sql comments and salts table 2024-03-12 20:44:54 +01:00
Imbus
9b67a580da Gluecode for database/handlers 2024-03-12 20:44:40 +01:00
borean
ad4d439887 cleanup 2024-03-08 15:07:25 +01:00
borean
d06122864e pls no error 2024-03-08 15:06:32 +01:00
borean
f8277617a9 Merge remote-tracking branch 'refs/remotes/origin/dev' into dev 2024-03-08 15:02:18 +01:00
borean
6acfdd36b2 cleanup 2024-03-08 14:56:24 +01:00
borean
0b23c5f8f1 Merge branch 'borean-dev' into dev 2024-03-08 14:55:10 +01:00
borean
eec3a45509 Added GetProjectId and AddUserToProject, WIP 2024-03-07 23:24:54 +01:00
borean
5a85f2bf81 first draft of AddTimeReport in db.go 2024-03-07 21:57:27 +01:00
borean
7ed986e4eb update 2024-03-07 20:58:50 +01:00
Imbus
3e000358a7 Formatting and using for-of loop 2024-03-07 11:53:06 +01:00
Imbus
09ec0a0bb0 Merge 2024-03-07 11:49:31 +01:00
Imbus
40f7241550 Formatting and typing 2024-03-07 11:48:34 +01:00
Mattias
25284bb10e More formatting 2024-03-07 11:43:19 +01:00
Mattias
9c824f1d7b Slight formatting 2024-03-07 11:39:29 +01:00
Davenludd
42a0745102 Small additional changes to YourProjectsPage 2024-03-07 10:45:23 +01:00
Mattias
7879394da3 Added a few pages and components to the frontend 2024-03-07 10:05:45 +01:00
borean
ae9ee91bc4 file correction 2024-03-04 19:34:35 +01:00
borean
a22dcb9f4e first push, broken import, WIP 2024-03-04 02:50:02 +01:00
27 changed files with 454 additions and 142 deletions

View file

@ -4,6 +4,7 @@ import (
"embed" "embed"
"os" "os"
"path/filepath" "path/filepath"
"time"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
@ -11,7 +12,9 @@ import (
// Interface for the database // Interface for the database
type Database interface { type Database interface {
// Insert a new user into the database, password should be hashed before calling
AddUser(username string, password string) error AddUser(username string, password string) error
RemoveUser(username string) error RemoveUser(username string) error
PromoteToAdmin(username string) error PromoteToAdmin(username string) error
GetUserId(username string) (int, error) GetUserId(username string) (int, error)
@ -20,6 +23,9 @@ type Database interface {
// AddTimeReport(projectname string, start time.Time, end time.Time) error // AddTimeReport(projectname string, start time.Time, end time.Time) error
// AddUserToProject(username string, projectname string) error // AddUserToProject(username string, projectname string) error
// ChangeUserRole(username string, projectname string, role string) error // ChangeUserRole(username string, projectname string, role string) error
// AddTimeReport(projectname string, start time.Time, end time.Time) error
// AddUserToProject(username string, projectname string) error
// ChangeUserRole(username string, projectname string, role string) error
} }
// This struct is a wrapper type that holds the database connection // This struct is a wrapper type that holds the database connection
@ -34,9 +40,8 @@ var scripts embed.FS
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?" const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addTimeReport = "INSERT INTO activity (report_id, activity_nbr, start_time, end_time, break, comment) VALUES (?, ?, ?, ?, ?, ?)" // WIP
// const addTimeReport = "" const addUserToProject = "INSERT INTO project_member (project_id, user_id, role) VALUES (?, ?, ?)" // WIP
// const addUserToProject = ""
// const changeUserRole = "" // const changeUserRole = ""
// DbConnect connects to the database // DbConnect connects to the database
@ -56,13 +61,27 @@ func DbConnect(dbpath string) Database {
return &Db{db} return &Db{db}
} }
// func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time) error { func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time, breakTime uint32) error { // WIP
_, err := d.Exec(addTimeReport, projectname, 0, start, end, breakTime, false)
return err
}
// } func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
var userid int
userid, err := d.GetUserId(username)
if err != nil {
panic(err)
}
// func (d *Db) AddUserToProject(username string, projectname string) error { var projectid int
projectid, err2 := d.GetProjectId(projectname)
if err2 != nil {
panic(err2)
}
// } _, err3 := d.Exec(addUserToProject, projectid, userid, role)
return err3
}
// func (d *Db) ChangeUserRole(username string, projectname string, role string) error { // func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
@ -87,7 +106,13 @@ func (d *Db) PromoteToAdmin(username string) error {
func (d *Db) GetUserId(username string) (int, error) { func (d *Db) GetUserId(username string) (int, error) {
var id int var id int
err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) // Borde det inte vara "user" i singular
return id, err
}
func (d *Db) GetProjectId(projectname string) (int, error) { // WIP, denna kan vara goof
var id int
err := d.Get(&id, "SELECT id FROM project WHERE project_name = ?", projectname)
return id, err return id, err
} }

View file

@ -1,3 +1,7 @@
-- Id is a surrogate key for in ternal use
-- userId is what is used for external id
-- username is what is used for login
-- password is the hashed password
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE, userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE,
@ -5,5 +9,6 @@ CREATE TABLE IF NOT EXISTS users (
password VARCHAR(255) NOT NULL password VARCHAR(255) NOT NULL
); );
-- Users are commonly searched by username and userId
CREATE INDEX IF NOT EXISTS users_username_index ON users (username); CREATE INDEX IF NOT EXISTS users_username_index ON users (username);
CREATE INDEX IF NOT EXISTS users_userId_index ON users (userId); CREATE INDEX IF NOT EXISTS users_userId_index ON users (userId);

View file

@ -0,0 +1,16 @@
-- It is unclear weather this table will be used
-- Create the table to store hash salts
CREATE TABLE salts (
id INTEGER PRIMARY KEY,
salt TEXT NOT NULL
);
-- Commented out for now, no time for good practices, which is atrocious
-- Create a trigger to automatically generate a salt when inserting a new user record
-- CREATE TRIGGER generate_salt_trigger
-- AFTER INSERT ON users
-- BEGIN
-- INSERT INTO salts (salt) VALUES (randomblob(16));
-- UPDATE users SET salt_id = (SELECT last_insert_rowid()) WHERE id = new.id;
-- END;

View file

@ -11,11 +11,11 @@ import (
// The actual interface that we will use // The actual interface that we will use
type GlobalState interface { type GlobalState interface {
Register(c *fiber.Ctx) error // To register a new user Register(c *fiber.Ctx) error // To register a new user
UserDelete(c *fiber.Ctx) error // To delete a user UserDelete(c *fiber.Ctx) error // To delete a user
Login(c *fiber.Ctx) error // To get the token Login(c *fiber.Ctx) error // To get the token
LoginRenew(c *fiber.Ctx) error // To renew the token LoginRenew(c *fiber.Ctx) error // To renew the token
// CreateProject(c *fiber.Ctx) error // To create a new project CreateProject(c *fiber.Ctx) error // To create a new project
// GetProjects(c *fiber.Ctx) error // To get all projects // GetProjects(c *fiber.Ctx) error // To get all projects
// GetProject(c *fiber.Ctx) error // To get a specific project // GetProject(c *fiber.Ctx) error // To get a specific project
// UpdateProject(c *fiber.Ctx) error // To update a project // UpdateProject(c *fiber.Ctx) error // To update a project
@ -58,7 +58,7 @@ type GState struct {
// @Failure 500 {string} string "Internal server error" // @Failure 500 {string} string "Internal server error"
// @Router /api/register [post] // @Router /api/register [post]
func (gs *GState) Register(c *fiber.Ctx) error { func (gs *GState) Register(c *fiber.Ctx) error {
u := new(types.User) u := new(types.NewUser)
if err := c.BodyParser(u); err != nil { if err := c.BodyParser(u); err != nil {
return c.Status(400).SendString(err.Error()) return c.Status(400).SendString(err.Error())
} }
@ -142,3 +142,24 @@ func (gs *GState) LoginRenew(c *fiber.Ctx) error {
} }
return c.JSON(fiber.Map{"token": t}) return c.JSON(fiber.Map{"token": t})
} }
// CreateProject is a simple handler that creates a new project
func (gs *GState) CreateProject(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
p := new(types.NewProject)
if err := c.BodyParser(p); err != nil {
return c.Status(400).SendString(err.Error())
}
// Get the username from the token and set it as the owner of the project
// This is ugly but
claims := user.Claims.(jwt.MapClaims)
p.Owner = claims["name"].(string)
if err := gs.Db.AddProject(p.Name, p.Description, p.Owner); err != nil {
return c.Status(500).SendString(err.Error())
}
return c.Status(200).SendString("Project added")
}

View file

@ -0,0 +1,21 @@
package types
import (
"time"
)
// Project is a struct that holds the information about a project
type Project struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
Owner string `json:"owner" db:"owner"`
Created time.Time `json:"created" db:"created"`
}
// As it arrives from the client
type NewProject struct {
Name string `json:"name"`
Description string `json:"description"`
Owner string `json:"owner"`
}

View file

@ -16,6 +16,11 @@ func (u *User) ToPublicUser() (*PublicUser, error) {
}, nil }, nil
} }
type NewUser struct {
Username string `json:"username"`
Password string `json:"password"`
}
// PublicUser represents a user that is safe to send over the API (no password) // PublicUser represents a user that is safe to send over the API (no password)
type PublicUser struct { type PublicUser struct {
UserId string `json:"userId"` UserId string `json:"userId"`

View file

@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/x-icon" href="src/assets/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title> <title>TTIME</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View file

@ -3,4 +3,4 @@ export default {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} };

View file

@ -0,0 +1,22 @@
import Header from "./Header";
import Footer from "./Footer";
function BasicWindow({
username,
content,
buttons,
}: {
username: string;
content: React.ReactNode;
buttons: React.ReactNode;
}): JSX.Element {
return (
<div className="font-sans flex flex-col h-screen bg-white border-2 border-black overflow-auto pt-[110px]">
<Header username={username} />
<div className="flex flex-col items-center flex-grow">{content}</div>
<Footer>{buttons}</Footer>
</div>
);
}
export default BasicWindow;

View file

@ -0,0 +1,18 @@
function Button({
text,
onClick,
}: {
text: string;
onClick: () => void;
}): JSX.Element {
return (
<button
onClick={onClick}
className="inline-block py-1 px-8 font-bold bg-orange-500 text-white border-2 border-black rounded-full cursor-pointer mt-5 mb-5 transition-colors duration-10 hover:bg-orange-600 hover:text-gray-300 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 4vh;"
>
{text}
</button>
);
}
export default Button;

View file

@ -0,0 +1,13 @@
import React from "react";
function Footer({ children }: { children: React.ReactNode }): JSX.Element {
return (
<footer className="bg-white">
<div className="flex justify-end items-center h-16 space-x-6 pr-6">
{children}
</div>
</footer>
);
}
export default Footer;

View file

@ -0,0 +1,54 @@
import { useState } from "react";
import { Link } from "react-router-dom";
function Header({ username }: { username: string }): JSX.Element {
const [isOpen, setIsOpen] = useState(false);
const handleLogout = (): void => {
// Add any logout logic here
};
return (
<header
className="fixed top-0 left-0 right-0 border-[1.75px] border-black text-black p-3 pl-5 flex items-center justify-between bg-cover"
style={{ backgroundImage: `url('src/assets/1.jpg')` }}
>
<Link to="/your-projects">
<img
src="/src/assets/TTIMElogo.png"
alt="TTIME Logo"
className="w-11 h-14 cursor-pointer"
/>
</Link>
<div
className="relative"
onMouseEnter={() => {
setIsOpen(true);
}}
onMouseLeave={() => {
setIsOpen(false);
}}
>
<button className="mr-4 underline font-bold text-white">
{username}
</button>
{isOpen && (
<div className="absolute right-0 bg-white border rounded shadow-lg">
<Link to="/">
<button
onClick={handleLogout}
className="block px-2 py-1 text-black hover:bg-gray-200"
>
Logout
</button>
</Link>
</div>
)}
</div>
</header>
);
}
export default Header;

View file

@ -0,0 +1,26 @@
body{
overflow: hidden;
}
@keyframes backgroundTransition {
0% {
background-image: url('src/assets/1.jpg');
animation-timing-function: ease-out;
}
25% {
background-image: url('src/assets/2.jpg');
animation-timing-function: ease-in;
}
50% {
background-image: url('src/assets/3.jpg');
animation-timing-function: ease-out;
}
75% {
background-image: url('src/assets/4.jpg');
animation-timing-function: ease-in;
}
100% {
background-image: url('src/assets/1.jpg');
animation-timing-function: ease-out;
}
}

View file

@ -0,0 +1,78 @@
import Button from "../Components/Button";
import Logo from "/src/assets/TTIMElogo.png";
import "./LoginPage.css";
import { useEffect } from "react";
import { Link } from "react-router-dom";
const PreloadBackgroundAnimation = (): JSX.Element => {
useEffect(() => {
const images = [
"src/assets/1.jpg",
"src/assets/2.jpg",
"src/assets/3.jpg",
"src/assets/4.jpg",
];
// Pre-load images
for (const i of images) {
console.log(i);
}
// Start animation
document.body.style.animation = "backgroundTransition 30s infinite";
}, []);
return <></>;
};
function LoginPage(): JSX.Element {
return (
<>
<PreloadBackgroundAnimation />
<div
className="flex flex-col h-screen w-screen items-center justify-center"
style={{
animation: "backgroundTransition 30s infinite",
backgroundSize: "cover",
backgroundAttachment: "fixed",
}}
>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
<img
src={Logo}
className="logo w-[7vw] mb-10 mt-10"
alt="TTIME Logo"
/>
<h1 className="font-sans mb-4 font-bold text-[25px]">
{" "}
Welcome to TTIME!{" "}
</h1>
<h2 className="font-sans mb-4 text-[15px]">
{" "}
Please log in to continue{" "}
</h2>
<input
className="border-2 border-black mb-3 rounded-lg w-[20vw] p-1"
type="text"
placeholder="Username"
/>
<input
className="border-2 border-black mb-3 rounded-lg w-[20vw] p-1"
type="password"
placeholder="Password"
/>
<Link to="/your-projects">
<Button
text="Login"
onClick={(): void => {
return;
}}
/>
</Link>
</div>
</div>
</>
);
}
export default LoginPage;

View file

@ -0,0 +1,38 @@
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
function PMProjectPage(): JSX.Element {
const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">ProjectNameExample</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]">
<h1 className="font-bold underline text-[30px] cursor-pointer">
Your Time Reports
</h1>
<h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report
</h1>
<h1 className="font-bold underline text-[30px] cursor-pointer">
Statistics
</h1>
<h1 className="font-bold underline text-[30px] cursor-pointer">
Unsigned Time Reports
</h1>
</div>
</>
);
const buttons = (
<>
<Button
text="Back"
onClick={(): void => {
return;
}}
/>
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
}
export default PMProjectPage;

View file

@ -0,0 +1,37 @@
import { Link } from "react-router-dom";
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
function UserProjectPage(): JSX.Element {
const content = (
<>
<Link to="/settingsPage">
<h1 className="font-bold text-[30px] mb-[20px]">ProjectNameExample</h1>
</Link>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]">
<h1 className="font-bold underline text-[30px] cursor-pointer">
Your Time Reports
</h1>
<h1 className="font-bold underline text-[30px] cursor-pointer">
New Time Report
</h1>
</div>
</>
);
const buttons = (
<>
<Link to="/your-projects">
<Button
text="Back"
onClick={(): void => {
return;
}}
/>
</Link>
</>
);
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
}
export default UserProjectPage;

View file

@ -0,0 +1,31 @@
import { Link } from "react-router-dom";
import BasicWindow from "../Components/BasicWindow";
function YourProjectsPage(): JSX.Element {
const content = (
<>
<h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-between min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10px] p-[30px]">
<Link to="/project">
<h1 className="underline text-[24px] cursor-pointer font-bold">
ProjectNameExample
</h1>
</Link>
<h1 className="underline text-[24px] cursor-pointer font-bold">
ProjectNameExample
</h1>
<h1 className="underline text-[24px] cursor-pointer font-bold">
ProjectNameExample
</h1>
<h1 className="underline text-[24px] cursor-pointer font-bold">
ProjectNameExample
</h1>
</div>
</>
);
const buttons = <></>;
return <BasicWindow username="Admin" content={content} buttons={buttons} />;
}
export default YourProjectsPage;

BIN
frontend/src/assets/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

BIN
frontend/src/assets/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

BIN
frontend/src/assets/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 KiB

BIN
frontend/src/assets/4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -6,117 +6,3 @@
* We are using tailwind, so do not add any custom CSS here. * We are using tailwind, so do not add any custom CSS here.
* Most of this is going to get cleaned up eventually. * Most of this is going to get cleaned up eventually.
*/ */
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
a {
display: inline-block;
}
.logo {
transition: filter 0.25s;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View file

@ -1,19 +1,24 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import SettingsPage from "./Pages/Settings.tsx";
import HomePage from "./Pages/Home.tsx";
import "./index.css"; import "./index.css";
import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { createBrowserRouter, RouterProvider } from "react-router-dom";
import LoginPage from "./Pages/LoginPage.tsx";
import YourProjectsPage from "./Pages/YourProjectsPage.tsx";
import UserProjectPage from "./Pages/UserPages/UserProjectPage.tsx";
// This is where the routes are mounted // This is where the routes are mounted
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: "/", path: "/",
element: <HomePage />, element: <LoginPage />,
}, },
{ {
path: "/settings", path: "/your-projects",
element: <SettingsPage />, element: <YourProjectsPage />,
},
{
path: "/project",
element: <UserProjectPage />,
}, },
]); ]);

View file

@ -1,13 +1,9 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: { theme: {
extend: {}, extend: {},
}, },
plugins: [], plugins: [],
} };

15
go.work.sum Normal file
View file

@ -0,0 +1,15 @@
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=