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/ /webapp/node_modules/
/frontend/dist/ /webapp/dist/
clortho.db clortho.db
clortho_test.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 package main
import ( import (
"clortho/apis" "clortho/lib/apis"
"clortho/db" "clortho/lib/db"
"clortho/users" "clortho/lib/users"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"log" "log"
) )

View File

@ -7,6 +7,12 @@ import (
) )
func SetupRouter(r *gin.Engine, authMiddleware gin.HandlerFunc) { 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") private := r.Group("/gui")
// Gets the session from the cookie, and puts it in the current request. // Gets the session from the cookie, and puts it in the current request.
if authMiddleware != nil { if authMiddleware != nil {

View File

@ -1,13 +1,33 @@
package apis package apis
import ( import (
"clortho/lib/db"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"testing" "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) { func TestPingRoute(t *testing.T) {
r := gin.Default() r := gin.Default()
SetupRouter(r, nil) SetupRouter(r, nil)

View File

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

View File

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

View File

@ -1,38 +1,18 @@
package apis package apis
import ( import (
"clortho/db"
"clortho/users"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"strings" "strings"
"testing" "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) { func TestInitAuthEndpoints_authSignin(t *testing.T) {
adminPass, err := users.InitAdminUser() _, err := InitUser("admin", "password")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -40,7 +20,7 @@ func TestInitAuthEndpoints_authSignin(t *testing.T) {
r := gin.Default() r := gin.Default()
SetupRouter(r, nil) SetupRouter(r, nil)
reqBody := loginRequest{Username: "admin", Password: *adminPass} reqBody := loginRequest{Username: "admin", Password: "password"}
strReqBody, _ := json.Marshal(reqBody) strReqBody, _ := json.Marshal(reqBody)
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/gui/auth/signin", strings.NewReader(string(strReqBody))) 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) { func TestInitAuthEndpoints_authSignout(t *testing.T) {
adminPass, err := users.InitAdminUser() _, err := InitUser("admin", "admin")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -61,7 +41,7 @@ func TestInitAuthEndpoints_authSignout(t *testing.T) {
r := gin.Default() r := gin.Default()
SetupRouter(r, nil) SetupRouter(r, nil)
reqBody := loginRequest{Username: "admin", Password: *adminPass} reqBody := loginRequest{Username: "admin", Password: "admin"}
strReqBody, _ := json.Marshal(reqBody) strReqBody, _ := json.Marshal(reqBody)
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/gui/auth/signout", strings.NewReader(string(strReqBody))) req, _ := http.NewRequest("POST", "/gui/auth/signout", strings.NewReader(string(strReqBody)))

View File

@ -1,7 +1,7 @@
package apis package apis
import ( import (
"clortho/users" "clortho/lib/users"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "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 package apis
import ( import (
"clortho/users" "clortho/lib/users"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )

View File

@ -27,14 +27,14 @@ type UserSession struct {
ClorthoModel ClorthoModel
UserID uint `gorm:"not null" json:"-"` UserID uint `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"` User *User `gorm:"foreignKey:UserID" json:"user"`
} }
type Key struct { type Key struct {
ClorthoModel ClorthoModel
UserID uint `gorm:"not null" json:"-"` 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"` Content string `gorm:"not null" json:"-" json:"content"`
} }
@ -67,7 +67,7 @@ type Grant struct {
UserID uint `gorm:"not null" json:"-"` UserID uint `gorm:"not null" json:"-"`
User User `gorm:"foreignKey:UserID" json:"user"` User User `gorm:"foreignKey:UserID" json:"user"`
SystemID uint `gorm:"not null" json:"-"` SystemID uint `gorm:"not null" json:"-"`
System System `gorm:"foreignKey:SystemID" json:"system"` System *System `gorm:"foreignKey:SystemID" json:"system"`
GrantedByID uint `gorm:"not null" json:"-"` GrantedByID uint `gorm:"not null" json:"-"`
GrantedBy User `gorm:"foreignKey:GrantedByID" json:"grantedBy"` 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 package users
import ( import (
"clortho/db" "clortho/lib/db"
"clortho/utils" "clortho/lib/utils"
"crypto/rand" "crypto/rand"
"errors" "errors"
"fmt" "fmt"
@ -64,7 +64,7 @@ func GetUsers() []db.User {
func GetSession(id uint) *db.UserSession { func GetSession(id uint) *db.UserSession {
var session db.UserSession var session db.UserSession
result := db.Connection.First(&session, id) result := db.Connection.Joins("User").First(&session, id)
if result.RowsAffected != 0 { if result.RowsAffected != 0 {
return &session return &session
} else { } else {
@ -151,7 +151,7 @@ func GenerateJwt(sessionId uint) (string, error) {
} }
func NewSession(user db.User) *db.UserSession { func NewSession(user db.User) *db.UserSession {
session := db.UserSession{User: user} session := db.UserSession{User: &user}
db.Connection.Create(&session) db.Connection.Create(&session)
return &session return &session
} }

View File

@ -1,8 +1,8 @@
package users package users
import ( import (
"clortho/db" "clortho/lib/db"
"clortho/utils" "clortho/lib/utils"
"fmt" "fmt"
"os" "os"
"testing" "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,8 +18,11 @@
v-bind="props" v-bind="props"
/> />
</template> </template>
<div> <v-card>
{{ appStore.user.displayName }} <v-card-title v-if="appStore.user">
Hello {{ appStore.user?.displayName }}
</v-card-title>
<v-card-text class="pa-0">
<v-list> <v-list>
<v-list-item <v-list-item
v-for="(item, index) in items" v-for="(item, index) in items"
@ -30,7 +33,8 @@
<v-list-item-title>{{ item.title }}</v-list-item-title> <v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</div> </v-card-text>
</v-card>
</v-menu> </v-menu>
</v-app-bar> </v-app-bar>

View File

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

View File

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

View File

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