This commit is contained in:
Maxime Duchene-Savard 2025-04-14 23:50:22 -04:00
parent b57cb700ec
commit ea2bb235a2
60 changed files with 194 additions and 70 deletions

4
.gitignore vendored
View File

@ -1,4 +1,4 @@
/frontend/node_modules/
/frontend/dist/
/webapp/node_modules/
/webapp/dist/
clortho.db
clortho_test.db

15
.idea/dataSources.xml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="clortho" uuid="b0ddef5e-e0d8-4b5a-94f1-84ba070ba818">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/clortho.db</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -1,9 +1,9 @@
package main
import (
"clortho/apis"
"clortho/db"
"clortho/users"
"clortho/lib/apis"
"clortho/lib/db"
"clortho/lib/users"
"github.com/gin-gonic/gin"
"log"
)

View File

@ -7,6 +7,12 @@ import (
)
func SetupRouter(r *gin.Engine, authMiddleware gin.HandlerFunc) {
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"error": "Resource not found",
})
})
private := r.Group("/gui")
// Gets the session from the cookie, and puts it in the current request.
if authMiddleware != nil {

View File

@ -1,13 +1,33 @@
package apis
import (
"clortho/lib/db"
"fmt"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"os"
"testing"
)
func TestMain(m *testing.M) {
// Set up test database file
os.Setenv("CLORTHO_DB_FILE", "clortho_test.db")
// Global setup
fmt.Println("Setting up resources...")
db.InitDb()
defer db.ResetDb()
exitCode := m.Run() // Run all tests
// Global teardown
fmt.Println("Cleaning up resources...")
os.Exit(exitCode)
}
func TestPingRoute(t *testing.T) {
r := gin.Default()
SetupRouter(r, nil)

View File

@ -1,8 +1,8 @@
package apis
import (
"clortho/db"
"clortho/users"
"clortho/lib/db"
"clortho/lib/users"
"github.com/gin-gonic/gin"
)

View File

@ -1,8 +1,8 @@
package apis
import (
"clortho/db"
"clortho/users"
"clortho/lib/db"
"clortho/lib/users"
"github.com/gin-gonic/gin"
)

View File

@ -1,38 +1,18 @@
package apis
import (
"clortho/db"
"clortho/users"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)
func TestMain(m *testing.M) {
// Set up test database file
os.Setenv("CLORTHO_DB_FILE", "clortho_test.db")
// Global setup
fmt.Println("Setting up resources...")
db.InitDb()
defer db.ResetDb()
exitCode := m.Run() // Run all tests
// Global teardown
fmt.Println("Cleaning up resources...")
os.Exit(exitCode)
}
func TestInitAuthEndpoints_authSignin(t *testing.T) {
adminPass, err := users.InitAdminUser()
_, err := InitUser("admin", "password")
if err != nil {
t.Fatal(err)
}
@ -40,7 +20,7 @@ func TestInitAuthEndpoints_authSignin(t *testing.T) {
r := gin.Default()
SetupRouter(r, nil)
reqBody := loginRequest{Username: "admin", Password: *adminPass}
reqBody := loginRequest{Username: "admin", Password: "password"}
strReqBody, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/gui/auth/signin", strings.NewReader(string(strReqBody)))
@ -53,7 +33,7 @@ func TestInitAuthEndpoints_authSignin(t *testing.T) {
}
func TestInitAuthEndpoints_authSignout(t *testing.T) {
adminPass, err := users.InitAdminUser()
_, err := InitUser("admin", "admin")
if err != nil {
t.Fatal(err)
}
@ -61,7 +41,7 @@ func TestInitAuthEndpoints_authSignout(t *testing.T) {
r := gin.Default()
SetupRouter(r, nil)
reqBody := loginRequest{Username: "admin", Password: *adminPass}
reqBody := loginRequest{Username: "admin", Password: "admin"}
strReqBody, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/gui/auth/signout", strings.NewReader(string(strReqBody)))

View File

@ -1,7 +1,7 @@
package apis
import (
"clortho/users"
"clortho/lib/users"
"github.com/gin-gonic/gin"
"net/http"
)

View File

@ -0,0 +1,18 @@
package apis
import (
"clortho/lib/users"
"github.com/gin-gonic/gin"
)
func InitSystemsEndpoints(r *gin.RouterGroup) {
group := r.Group("/users")
group.Use(LoggedInMiddleware())
group.GET("/", getUsers)
group.GET("/me", getMe)
}
func getServers(c *gin.Context) {
userList := users.GetUsers()
c.JSON(200, &userList)
}

View File

@ -0,0 +1,32 @@
package apis
import (
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestInitSystemsEndpoints_getSystems(t *testing.T) {
_, err := InitUser("admin", "password")
if err != nil {
t.Fatal(err)
}
r := gin.Default()
SetupRouter(r, nil)
reqBody := loginRequest{Username: "admin", Password: "password"}
strReqBody, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/gui/auth/signin", strings.NewReader(string(strReqBody)))
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.JSONEq(t, `{"valid": true}`, w.Body.String())
setCookie := w.Header().Get("Set-Cookie")
assert.True(t, strings.Contains(setCookie, "CLORTHO_AUTH="))
}

View File

@ -1,7 +1,7 @@
package apis
import (
"clortho/users"
"clortho/lib/users"
"github.com/gin-gonic/gin"
)

View File

@ -26,15 +26,15 @@ type User struct {
type UserSession struct {
ClorthoModel
UserID uint `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
UserID uint `gorm:"not null" json:"-"`
User *User `gorm:"foreignKey:UserID" json:"user"`
}
type Key struct {
ClorthoModel
UserID uint `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
User *User `gorm:"foreignKey:UserID" json:"user"`
Content string `gorm:"not null" json:"-" json:"content"`
}
@ -64,10 +64,10 @@ type SystemGroup struct {
type Grant struct {
ClorthoModel
UserID uint `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
SystemID uint `gorm:"not null" json:"-"`
System System `gorm:"foreignKey:SystemID" json:"system"`
GrantedByID uint `gorm:"not null" json:"-"`
GrantedBy User `gorm:"foreignKey:GrantedByID" json:"grantedBy"`
UserID uint `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"`
SystemID uint `gorm:"not null" json:"-"`
System *System `gorm:"foreignKey:SystemID" json:"system"`
GrantedByID uint `gorm:"not null" json:"-"`
GrantedBy *User `gorm:"foreignKey:GrantedByID" json:"grantedBy"`
}

18
lib/systems/systems.go Normal file
View File

@ -0,0 +1,18 @@
package systems
import "clortho/lib/db"
func CreateSystem(name string) (*db.System, error) {
system := db.System{Name: name}
result := db.Connection.Create(&system)
if result.Error != nil {
return nil, result.Error
}
return &system, nil
}
func GetSystems() []db.System {
var systems []db.System
db.Connection.Find(&systems)
return systems
}

View File

@ -0,0 +1,24 @@
package systems
import (
"clortho/lib/db"
"fmt"
"os"
"testing"
)
func TestMain(m *testing.M) {
// Global setup
fmt.Println("Setting up resources...")
db.InitDb()
exitCode := m.Run() // Run all tests
// Global teardown
fmt.Println("Cleaning up resources...")
db.ResetDb()
os.Exit(exitCode)
}

View File

@ -1,8 +1,8 @@
package users
import (
"clortho/db"
"clortho/utils"
"clortho/lib/db"
"clortho/lib/utils"
"crypto/rand"
"errors"
"fmt"
@ -64,7 +64,7 @@ func GetUsers() []db.User {
func GetSession(id uint) *db.UserSession {
var session db.UserSession
result := db.Connection.First(&session, id)
result := db.Connection.Joins("User").First(&session, id)
if result.RowsAffected != 0 {
return &session
} else {
@ -151,7 +151,7 @@ func GenerateJwt(sessionId uint) (string, error) {
}
func NewSession(user db.User) *db.UserSession {
session := db.UserSession{User: user}
session := db.UserSession{User: &user}
db.Connection.Create(&session)
return &session
}

View File

@ -1,8 +1,8 @@
package users
import (
"clortho/db"
"clortho/utils"
"clortho/lib/db"
"clortho/lib/utils"
"fmt"
"os"
"testing"

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 526 B

After

Width:  |  Height:  |  Size: 526 B

View File

@ -18,19 +18,23 @@
v-bind="props"
/>
</template>
<div>
{{ appStore.user.displayName }}
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
:value="index"
:to="item.link"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</div>
<v-card>
<v-card-title v-if="appStore.user">
Hello {{ appStore.user?.displayName }}
</v-card-title>
<v-card-text class="pa-0">
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
:value="index"
:to="item.link"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-menu>
</v-app-bar>

View File

@ -66,6 +66,12 @@
<script lang="ts" setup>
import {ref} from 'vue'
definePage({
meta: {
public: true
}
})
const router = useRouter()
const valid = ref(false)
const username = ref('')
@ -90,7 +96,7 @@ async function login() {
// For now, we'll just simulate a successful login
console.log('Login attempted with:', {username: username.value, password: password.value})
const res = await fetch("/gui/auth/login", {
const res = await fetch("/gui/auth/signin", {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@ -2,12 +2,13 @@
</template>
<script setup lang="ts">
const router = useRouter();
onMounted(async () => {
await fetch('/gui/auth/signout', {
method: 'POST',
})
const router = useRouter();
await router.replace("/")
})
</script>

View File

@ -3,9 +3,9 @@
</template>
<script lang="ts" setup>
/**
* @route
* @meta public true
* @meta title "Home"
*/
definePage({
meta: {
public: true
}
})
</script>