Added public project attribute & worker access api endpoints

This commit is contained in:
simon987 2019-01-24 20:39:17 -05:00
parent 1d656099f5
commit f250a2180c
16 changed files with 432 additions and 70 deletions

View File

@ -50,6 +50,9 @@ func New() *WebAPI {
api.router.POST("/worker/create", LogRequestMiddleware(api.WorkerCreate)) api.router.POST("/worker/create", LogRequestMiddleware(api.WorkerCreate))
api.router.GET("/worker/get/:id", LogRequestMiddleware(api.WorkerGet)) api.router.GET("/worker/get/:id", LogRequestMiddleware(api.WorkerGet))
api.router.POST("/access/grant", LogRequestMiddleware(api.WorkerGrantAccess))
api.router.POST("/access/remove", LogRequestMiddleware(api.WorkerRemoveAccess))
api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate)) api.router.POST("/project/create", LogRequestMiddleware(api.ProjectCreate))
api.router.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet)) api.router.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet))
api.router.GET("/project/stats/:id", LogRequestMiddleware(api.ProjectGetStats)) api.router.GET("/project/stats/:id", LogRequestMiddleware(api.ProjectGetStats))

View File

@ -13,6 +13,7 @@ type CreateProjectRequest struct {
Version string `json:"version"` Version string `json:"version"`
Priority int64 `json:"priority"` Priority int64 `json:"priority"`
Motd string `json:"motd"` Motd string `json:"motd"`
Public bool `json:"public"`
} }
type CreateProjectResponse struct { type CreateProjectResponse struct {
@ -51,6 +52,7 @@ func (api *WebAPI) ProjectCreate(r *Request) {
GitRepo: createReq.GitRepo, GitRepo: createReq.GitRepo,
Priority: createReq.Priority, Priority: createReq.Priority,
Motd: createReq.Motd, Motd: createReq.Motd,
Public: createReq.Public,
} }
if isValidProject(project) { if isValidProject(project) {

View File

@ -13,6 +13,7 @@ type CreateTaskRequest struct {
MaxRetries int64 `json:"max_retries"` MaxRetries int64 `json:"max_retries"`
Recipe string `json:"recipe"` Recipe string `json:"recipe"`
Priority int64 `json:"priority"` Priority int64 `json:"priority"`
MaxAssignTime int64 `json:"max_assign_time"`
} }
type ReleaseTaskRequest struct { type ReleaseTaskRequest struct {
@ -46,6 +47,8 @@ func (api *WebAPI) TaskCreate(r *Request) {
MaxRetries: createReq.MaxRetries, MaxRetries: createReq.MaxRetries,
Recipe: createReq.Recipe, Recipe: createReq.Recipe,
Priority: createReq.Priority, Priority: createReq.Priority,
AssignTime: 0,
MaxAssignTime: createReq.MaxAssignTime,
} }
if isTaskValid(task) { if isTaskValid(task) {
@ -99,10 +102,21 @@ func (api *WebAPI) TaskGetFromProject(r *Request) {
handleErr(err, r) handleErr(err, r)
task := api.Database.GetTaskFromProject(worker, int64(project)) task := api.Database.GetTaskFromProject(worker, int64(project))
if task == nil {
r.OkJson(GetTaskResponse{
Ok: false,
Message: "No task available",
})
} else {
r.OkJson(GetTaskResponse{ r.OkJson(GetTaskResponse{
Ok: true, Ok: true,
Task: task, Task: task,
}) })
}
} }
func (api *WebAPI) TaskGet(r *Request) { func (api *WebAPI) TaskGet(r *Request) {

View File

@ -22,6 +22,16 @@ type GetWorkerResponse struct {
Worker *storage.Worker `json:"worker,omitempty"` Worker *storage.Worker `json:"worker,omitempty"`
} }
type WorkerAccessRequest struct {
WorkerId *uuid.UUID `json:"worker_id"`
ProjectId int64 `json:"project_id"`
}
type WorkerAccessResponse struct {
Ok bool `json:"ok"`
Message string `json:"message"`
}
func (api *WebAPI) WorkerCreate(r *Request) { func (api *WebAPI) WorkerCreate(r *Request) {
workerReq := &CreateWorkerRequest{} workerReq := &CreateWorkerRequest{}
@ -86,6 +96,46 @@ func (api *WebAPI) WorkerGet(r *Request) {
} }
} }
func (api *WebAPI) WorkerGrantAccess(r *Request) {
req := &WorkerAccessRequest{}
if r.GetJson(req) {
ok := api.Database.GrantAccess(req.WorkerId, req.ProjectId)
if ok {
r.OkJson(WorkerAccessResponse{
Ok: true,
})
} else {
r.OkJson(WorkerAccessResponse{
Ok: false,
Message: "Worker already has access to this project",
})
}
}
}
func (api *WebAPI) WorkerRemoveAccess(r *Request) {
req := &WorkerAccessRequest{}
if r.GetJson(req) {
ok := api.Database.RemoveAccess(req.WorkerId, req.ProjectId)
if ok {
r.OkJson(WorkerAccessResponse{
Ok: true,
})
} else {
r.OkJson(WorkerAccessResponse{
Ok: false,
Message: "Worker did not have access to this project",
})
}
}
}
func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (uuid.UUID, error) { func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (uuid.UUID, error) {
worker := storage.Worker{ worker := storage.Worker{

View File

@ -1,18 +1,20 @@
DROP TABLE IF EXISTS workeridentity, Worker, Project, Task, log_entry; DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry,
worker_has_access_to_project;
DROP TYPE IF EXISTS status; DROP TYPE IF EXISTS status;
DROP TYPE IF EXISTS loglevel; DROP TYPE IF EXISTS log_level;
CREATE TYPE status as ENUM ( CREATE TYPE status as ENUM (
'new', 'new',
'failed', 'failed',
'closed' 'closed',
'timeout'
); );
CREATE TYPE loglevel as ENUM ( CREATE TYPE log_level as ENUM (
'fatal', 'panic', 'error', 'warning', 'info', 'debug', 'trace' 'fatal', 'panic', 'error', 'warning', 'info', 'debug', 'trace'
); );
CREATE TABLE workerIdentity CREATE TABLE worker_identity
( (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
remote_addr TEXT, remote_addr TEXT,
@ -24,6 +26,7 @@ CREATE TABLE workerIdentity
CREATE TABLE worker CREATE TABLE worker
( (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
alias TEXT DEFAULT NULL,
created INTEGER, created INTEGER,
identity INTEGER REFERENCES workerIdentity (id) identity INTEGER REFERENCES workerIdentity (id)
); );
@ -35,7 +38,16 @@ CREATE TABLE project
name TEXT UNIQUE, name TEXT UNIQUE,
clone_url TEXT, clone_url TEXT,
git_repo TEXT UNIQUE, git_repo TEXT UNIQUE,
version TEXT version TEXT,
motd TEXT,
public boolean
);
CREATE TABLE worker_has_access_to_project
(
worker TEXT REFERENCES worker (id),
project INTEGER REFERENCES project (id),
primary key (worker, project)
); );
CREATE TABLE task CREATE TABLE task
@ -47,12 +59,14 @@ CREATE TABLE task
retries INTEGER DEFAULT 0, retries INTEGER DEFAULT 0,
max_retries INTEGER, max_retries INTEGER,
status Status DEFAULT 'new', status Status DEFAULT 'new',
recipe TEXT recipe TEXT,
max_assign_time INTEGER DEFAULT 0,
assign_time INTEGER DEFAULT 0
); );
CREATE TABLE log_entry CREATE TABLE log_entry
( (
level loglevel, level log_level,
message TEXT, message TEXT,
message_data TEXT, message_data TEXT,
timestamp INT timestamp INT

View File

@ -15,6 +15,7 @@ type Project struct {
GitRepo string `json:"git_repo"` GitRepo string `json:"git_repo"`
Version string `json:"version"` Version string `json:"version"`
Motd string `json:"motd"` Motd string `json:"motd"`
Public bool `json:"public"`
} }
type AssignedTasks struct { type AssignedTasks struct {
@ -39,9 +40,9 @@ func (database *Database) SaveProject(project *Project) (int64, error) {
func saveProject(project *Project, db *sql.DB) (int64, error) { func saveProject(project *Project, db *sql.DB) (int64, error) {
row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, motd) row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, motd, public)
VALUES ($1,$2,$3,$4,$5,$6) RETURNING id`, VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING id`,
project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd) project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd, project.Public)
var id int64 var id int64
err := row.Scan(&id) err := row.Scan(&id)
@ -93,8 +94,8 @@ func getProject(id int64, db *sql.DB) *Project {
func scanProject(row *sql.Row) (*Project, error) { func scanProject(row *sql.Row) (*Project, error) {
project := &Project{} project := &Project{}
err := row.Scan(&project.Id, &project.Priority, &project.Motd, &project.Name, &project.CloneUrl, err := row.Scan(&project.Id, &project.Priority, &project.Name, &project.CloneUrl,
&project.GitRepo, &project.Version) &project.GitRepo, &project.Version, &project.Motd, &project.Public)
return project, err return project, err
} }
@ -120,8 +121,8 @@ func (database *Database) UpdateProject(project *Project) {
db := database.getDB() db := database.getDB()
res, err := db.Exec(`UPDATE project res, err := db.Exec(`UPDATE project
SET (priority, name, clone_url, git_repo, version, motd) = ($1,$2,$3,$4,$5,$6) WHERE id=$7`, SET (priority, name, clone_url, git_repo, version, motd, public) = ($1,$2,$3,$4,$5,$6,$7) WHERE id=$8`,
project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, project.Id) project.Priority, project.Name, project.CloneUrl, project.GitRepo, project.Version, project.Motd, project.Public, project.Id)
handleErr(err) handleErr(err)
rowsAffected, _ := res.RowsAffected() rowsAffected, _ := res.RowsAffected()
@ -156,6 +157,7 @@ func (database *Database) GetProjectStats(id int64) *ProjectStats {
return nil return nil
} }
//todo: only expose worker alias
rows, err := db.Query(`SELECT assignee, COUNT(*) FROM TASK rows, err := db.Query(`SELECT assignee, COUNT(*) FROM TASK
LEFT JOIN worker ON TASK.assignee = worker.id WHERE project=$1 GROUP BY assignee`, id) LEFT JOIN worker ON TASK.assignee = worker.id WHERE project=$1 GROUP BY assignee`, id)
@ -189,7 +191,7 @@ func (database Database) GetAllProjectsStats() *[]ProjectStats {
stats := ProjectStats{} stats := ProjectStats{}
p := &Project{} p := &Project{}
err := rows.Scan(&stats.NewTaskCount, &stats.FailedTaskCount, &stats.ClosedTaskCount, err := rows.Scan(&stats.NewTaskCount, &stats.FailedTaskCount, &stats.ClosedTaskCount,
&p.Id, &p.Priority, &p.Motd, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version) &p.Id, &p.Priority, &p.Motd, &p.Name, &p.CloneUrl, &p.GitRepo, &p.Version, &p.Public)
handleErr(err) handleErr(err)
stats.Project = p stats.Project = p

View File

@ -15,6 +15,8 @@ type Task struct {
MaxRetries int64 `json:"max_retries"` MaxRetries int64 `json:"max_retries"`
Status string `json:"status"` Status string `json:"status"`
Recipe string `json:"recipe"` Recipe string `json:"recipe"`
MaxAssignTime int64 `json:"max_assign_time"`
AssignTime int64 `json:"assign_time"`
} }
func (database *Database) SaveTask(task *Task, project int64) error { func (database *Database) SaveTask(task *Task, project int64) error {
@ -22,9 +24,9 @@ func (database *Database) SaveTask(task *Task, project int64) error {
db := database.getDB() db := database.getDB()
res, err := db.Exec(` res, err := db.Exec(`
INSERT INTO task (project, max_retries, recipe, priority) INSERT INTO task (project, max_retries, recipe, priority, max_assign_time)
VALUES ($1,$2,$3,$4)`, VALUES ($1,$2,$3,$4,$5)`,
project, task.MaxRetries, task.Recipe, task.Priority) project, task.MaxRetries, task.Recipe, task.Priority, task.MaxAssignTime)
if err != nil { if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{ logrus.WithError(err).WithFields(logrus.Fields{
"task": task, "task": task,
@ -56,6 +58,9 @@ func (database *Database) GetTask(worker *Worker) *Task {
FROM task FROM task
INNER JOIN project p on task.project = p.id INNER JOIN project p on task.project = p.id
WHERE assignee IS NULL WHERE assignee IS NULL
AND (p.public OR EXISTS (
SELECT 1 FROM worker_has_access_to_project a WHERE a.worker=$1 AND a.project=p.id
))
ORDER BY p.priority DESC, task.priority DESC ORDER BY p.priority DESC, task.priority DESC
LIMIT 1 LIMIT 1
) )
@ -134,6 +139,9 @@ func (database *Database) GetTaskFromProject(worker *Worker, projectId int64) *T
FROM task FROM task
INNER JOIN project p on task.project = p.id INNER JOIN project p on task.project = p.id
WHERE assignee IS NULL AND p.id=$2 WHERE assignee IS NULL AND p.id=$2
AND (p.public OR EXISTS (
SELECT 1 FROM worker_has_access_to_project a WHERE a.worker=$1 AND a.project=$2
))
ORDER BY task.priority DESC ORDER BY task.priority DESC
LIMIT 1 LIMIT 1
) )
@ -165,8 +173,9 @@ func scanTask(row *sql.Row) *Task {
task.Project = project task.Project = project
err := row.Scan(&task.Id, &task.Priority, &project.Id, &task.Assignee, err := row.Scan(&task.Id, &task.Priority, &project.Id, &task.Assignee,
&task.Retries, &task.MaxRetries, &task.Status, &task.Recipe, &project.Id, &task.Retries, &task.MaxRetries, &task.Status, &task.Recipe, &task.MaxAssignTime,
&project.Priority, &project.Motd, &project.Name, &project.CloneUrl, &project.GitRepo, &project.Version) &task.AssignTime, &project.Id, &project.Priority, &project.Name,
&project.CloneUrl, &project.GitRepo, &project.Version, &project.Motd, &project.Public)
handleErr(err) handleErr(err)
return task return task

View File

@ -101,3 +101,46 @@ func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 {
return rowId return rowId
} }
func (database *Database) GrantAccess(workerId *uuid.UUID, projectId int64) bool {
db := database.getDB()
res, err := db.Exec(`INSERT INTO worker_has_access_to_project (worker, project) VALUES ($1,$2)
ON CONFLICT DO NOTHING`,
workerId, projectId)
if err != nil {
logrus.WithFields(logrus.Fields{
"workerId": workerId,
"projectId": projectId,
}).WithError(err).Warn("Database.GrantAccess INSERT worker_hase_access_to_project")
return false
}
rowsAffected, _ := res.RowsAffected()
logrus.WithFields(logrus.Fields{
"rowsAffected": rowsAffected,
"workerId": workerId,
"projectId": projectId,
}).Trace("Database.GrantAccess INSERT worker_has_access_to_project")
return rowsAffected == 1
}
func (database Database) RemoveAccess(workerId *uuid.UUID, projectId int64) bool {
db := database.getDB()
res, err := db.Exec(`DELETE FROM worker_has_access_to_project WHERE worker=$1 AND project=$2`,
workerId, projectId)
handleErr(err)
rowsAffected, _ := res.RowsAffected()
logrus.WithFields(logrus.Fields{
"rowsAffected": rowsAffected,
"workerId": workerId,
"projectId": projectId,
}).Trace("Database.RemoveAccess DELETE worker_has_access_to_project")
return rowsAffected == 1
}

View File

@ -19,6 +19,7 @@ func TestCreateGetProject(t *testing.T) {
Version: "Test Version", Version: "Test Version",
Priority: 123, Priority: 123,
Motd: "motd", Motd: "motd",
Public: true,
}) })
id := resp.Id id := resp.Id
@ -56,6 +57,9 @@ func TestCreateGetProject(t *testing.T) {
if getResp.Project.Motd != "motd" { if getResp.Project.Motd != "motd" {
t.Error() t.Error()
} }
if getResp.Project.Public != true {
t.Error()
}
} }
func TestCreateProjectInvalid(t *testing.T) { func TestCreateProjectInvalid(t *testing.T) {

View File

@ -129,6 +129,7 @@ func TestCreateGetTask(t *testing.T) {
CloneUrl: "http://github.com/test/test", CloneUrl: "http://github.com/test/test",
GitRepo: "myrepo", GitRepo: "myrepo",
Priority: 999, Priority: 999,
Public: true,
}) })
createTask(api.CreateTaskRequest{ createTask(api.CreateTaskRequest{
@ -170,6 +171,9 @@ func TestCreateGetTask(t *testing.T) {
if taskResp.Task.Project.CloneUrl != "http://github.com/test/test" { if taskResp.Task.Project.CloneUrl != "http://github.com/test/test" {
t.Error() t.Error()
} }
if taskResp.Task.Project.Public != true {
t.Error()
}
} }
func createTasks(prefix string) (int64, int64) { func createTasks(prefix string) (int64, int64) {
@ -180,6 +184,7 @@ func createTasks(prefix string) (int64, int64) {
CloneUrl: "http://github.com/test/test", CloneUrl: "http://github.com/test/test",
GitRepo: prefix + "low1", GitRepo: prefix + "low1",
Priority: 1, Priority: 1,
Public: true,
}) })
highP := createProject(api.CreateProjectRequest{ highP := createProject(api.CreateProjectRequest{
Name: prefix + "high", Name: prefix + "high",
@ -187,6 +192,7 @@ func createTasks(prefix string) (int64, int64) {
CloneUrl: "http://github.com/test/test", CloneUrl: "http://github.com/test/test",
GitRepo: prefix + "high1", GitRepo: prefix + "high1",
Priority: 999, Priority: 999,
Public: true,
}) })
createTask(api.CreateTaskRequest{ createTask(api.CreateTaskRequest{
Project: lowP.Id, Project: lowP.Id,
@ -266,6 +272,86 @@ func TestTaskPriority(t *testing.T) {
} }
} }
func TestTaskNoAccess(t *testing.T) {
wid := genWid()
pid := createProject(api.CreateProjectRequest{
Name: "This is a private proj",
Motd: "private",
Version: "private",
Priority: 1,
CloneUrl: "fjkslejf cesl",
GitRepo: "fffffffff",
Public: false,
}).Id
createResp := createTask(api.CreateTaskRequest{
Project: pid,
Priority: 1,
MaxAssignTime: 10,
MaxRetries: 2,
Recipe: "---",
})
if createResp.Ok != true {
t.Error()
}
grantAccess(wid, pid)
removeAccess(wid, pid)
tResp := getTaskFromProject(pid, wid)
if tResp.Ok != false {
t.Error()
}
if len(tResp.Message) <= 0 {
t.Error()
}
if tResp.Task != nil {
t.Error()
}
}
func TestTaskHasAccess(t *testing.T) {
wid := genWid()
pid := createProject(api.CreateProjectRequest{
Name: "This is a private proj1",
Motd: "private1",
Version: "private1",
Priority: 1,
CloneUrl: "josaeiuf cesl",
GitRepo: "wewwwwwwwwwwwwwwwwwwwwww",
Public: false,
}).Id
createResp := createTask(api.CreateTaskRequest{
Project: pid,
Priority: 1,
MaxAssignTime: 10,
MaxRetries: 2,
Recipe: "---",
})
if createResp.Ok != true {
t.Error()
}
grantAccess(wid, pid)
tResp := getTaskFromProject(pid, wid)
if tResp.Ok != true {
t.Error()
}
if tResp.Task == nil {
t.Error()
}
}
func TestNoMoreTasks(t *testing.T) { func TestNoMoreTasks(t *testing.T) {
wid := genWid() wid := genWid()

View File

@ -66,6 +66,78 @@ func TestGetWorkerInvalid(t *testing.T) {
} }
} }
func TestGrantAccessFailedProjectConstraint(t *testing.T) {
wid := genWid()
resp := grantAccess(wid, 38274593)
if resp.Ok != false {
t.Error()
}
if len(resp.Message) <= 0 {
t.Error()
}
}
func TestRemoveAccessFailedProjectConstraint(t *testing.T) {
wid := genWid()
resp := removeAccess(wid, 38274593)
if resp.Ok != false {
t.Error()
}
if len(resp.Message) <= 0 {
t.Error()
}
}
func TestRemoveAccessFailedWorkerConstraint(t *testing.T) {
pid := createProject(api.CreateProjectRequest{
Priority: 1,
GitRepo: "dfffffffffff",
CloneUrl: "fffffffffff23r",
Version: "f83w9rw",
Motd: "ddddddddd",
Name: "removeaccessfailedworkerconstraint",
Public: true,
}).Id
resp := removeAccess(&uuid.Nil, pid)
if resp.Ok != false {
t.Error()
}
if len(resp.Message) <= 0 {
t.Error()
}
}
func TestGrantAccessFailedWorkerConstraint(t *testing.T) {
pid := createProject(api.CreateProjectRequest{
Priority: 1,
GitRepo: "dfffffffffff1",
CloneUrl: "fffffffffff23r1",
Version: "f83w9rw1",
Motd: "ddddddddd1",
Name: "grantaccessfailedworkerconstraint",
Public: true,
}).Id
resp := removeAccess(&uuid.Nil, pid)
if resp.Ok != false {
t.Error()
}
if len(resp.Message) <= 0 {
t.Error()
}
}
func createWorker(req api.CreateWorkerRequest) (*api.CreateWorkerResponse, *http.Response) { func createWorker(req api.CreateWorkerRequest) (*api.CreateWorkerResponse, *http.Response) {
r := Post("/worker/create", req) r := Post("/worker/create", req)
@ -94,3 +166,33 @@ func genWid() *uuid.UUID {
resp, _ := createWorker(api.CreateWorkerRequest{}) resp, _ := createWorker(api.CreateWorkerRequest{})
return &resp.WorkerId return &resp.WorkerId
} }
func grantAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
r := Post("/access/grant", api.WorkerAccessRequest{
WorkerId: wid,
ProjectId: project,
})
var resp *api.WorkerAccessResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &resp)
handleErr(err)
return resp
}
func removeAccess(wid *uuid.UUID, project int64) *api.WorkerAccessResponse {
r := Post("/access/remove", api.WorkerAccessRequest{
WorkerId: wid,
ProjectId: project,
})
var resp *api.WorkerAccessResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &resp)
handleErr(err)
return resp
}

31
test/schema.sql Normal file → Executable file
View File

@ -1,18 +1,20 @@
DROP TABLE IF EXISTS workeridentity, Worker, Project, Task, log_entry; DROP TABLE IF EXISTS worker_identity, worker, project, task, log_entry,
worker_has_access_to_project;
DROP TYPE IF EXISTS status; DROP TYPE IF EXISTS status;
DROP TYPE IF EXISTS loglevel; DROP TYPE IF EXISTS log_level;
CREATE TYPE status as ENUM ( CREATE TYPE status as ENUM (
'new', 'new',
'failed', 'failed',
'closed' 'closed',
'timeout'
); );
CREATE TYPE loglevel as ENUM ( CREATE TYPE log_level as ENUM (
'fatal', 'panic', 'error', 'warning', 'info', 'debug', 'trace' 'fatal', 'panic', 'error', 'warning', 'info', 'debug', 'trace'
); );
CREATE TABLE workerIdentity CREATE TABLE worker_identity
( (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
remote_addr TEXT, remote_addr TEXT,
@ -24,6 +26,7 @@ CREATE TABLE workerIdentity
CREATE TABLE worker CREATE TABLE worker
( (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
alias TEXT DEFAULT NULL,
created INTEGER, created INTEGER,
identity INTEGER REFERENCES workerIdentity (id) identity INTEGER REFERENCES workerIdentity (id)
); );
@ -32,11 +35,19 @@ CREATE TABLE project
( (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
priority INTEGER DEFAULT 0, priority INTEGER DEFAULT 0,
motd TEXT DEFAULT '',
name TEXT UNIQUE, name TEXT UNIQUE,
clone_url TEXT, clone_url TEXT,
git_repo TEXT UNIQUE, git_repo TEXT UNIQUE,
version TEXT version TEXT,
motd TEXT,
public boolean
);
CREATE TABLE worker_has_access_to_project
(
worker TEXT REFERENCES worker (id),
project INTEGER REFERENCES project (id),
primary key (worker, project)
); );
CREATE TABLE task CREATE TABLE task
@ -48,12 +59,14 @@ CREATE TABLE task
retries INTEGER DEFAULT 0, retries INTEGER DEFAULT 0,
max_retries INTEGER, max_retries INTEGER,
status Status DEFAULT 'new', status Status DEFAULT 'new',
recipe TEXT recipe TEXT,
max_assign_time INTEGER DEFAULT 0,
assign_time INTEGER DEFAULT 0
); );
CREATE TABLE log_entry CREATE TABLE log_entry
( (
level loglevel, level log_level,
message TEXT, message TEXT,
message_data TEXT, message_data TEXT,
timestamp INT timestamp INT

View File

@ -10,12 +10,16 @@ import {
MatAutocompleteModule, MatAutocompleteModule,
MatButtonModule, MatButtonModule,
MatCardModule, MatCardModule,
MatCheckboxModule,
MatDividerModule,
MatExpansionModule, MatExpansionModule,
MatFormFieldModule, MatFormFieldModule,
MatIconModule, MatIconModule,
MatInputModule, MatInputModule,
MatMenuModule, MatMenuModule,
MatPaginatorModule, MatPaginatorModule,
MatSliderModule,
MatSlideToggleModule,
MatSortModule, MatSortModule,
MatTableModule, MatTableModule,
MatToolbarModule, MatToolbarModule,
@ -58,6 +62,11 @@ import {UpdateProjectComponent} from './update-project/update-project.component'
MatTreeModule, MatTreeModule,
BrowserAnimationsModule, BrowserAnimationsModule,
HttpClientModule, HttpClientModule,
MatSliderModule,
MatSlideToggleModule,
MatCheckboxModule,
MatDividerModule
], ],
exports: [], exports: [],
providers: [ providers: [

View File

@ -4,21 +4,30 @@
<mat-card-content> <mat-card-content>
<form> <form>
<mat-form-field> <mat-form-field appearance="outline">
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Name"> <mat-label>Project name</mat-label>
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Project name">
</mat-form-field> </mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Git clone URL</mat-label>
<input type="text" matInput [(ngModel)]="project.clone_url" name="clone_url" <input type="text" matInput [(ngModel)]="project.clone_url" name="clone_url"
placeholder="Git clone url"> placeholder="Git clone url">
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field appearance="outline">
<mat-label>Repository name</mat-label>
<input type="text" matInput [(ngModel)]="project.git_repo" name="clone_url" <input type="text" matInput [(ngModel)]="project.git_repo" name="clone_url"
placeholder='Full repository name (e.g. "simon987/task_tracker")'> placeholder='Full repository name (e.g. "simon987/task_tracker")'>
<mat-hint align="start">Changes on the <strong>master</strong> branch will be tracked if webhooks are <mat-hint align="start">
enabled Changes on the <strong>master</strong> branch will be tracked if webhooks are enabled
</mat-hint> </mat-hint>
</mat-form-field> </mat-form-field>
<mat-checkbox matInput [(ngModel)]="project.public" name="public" stype="padding-top: 1em">Public project
</mat-checkbox>
<input type="hidden" name="version" value="{{project.version}}"> <input type="hidden" name="version" value="{{project.version}}">
</form> </form>
</mat-card-content> </mat-card-content>

View File

@ -11,7 +11,8 @@ export class CreateProjectComponent implements OnInit {
private project = new Project(); private project = new Project();
constructor() { constructor() {
this.project.name = "test" this.project.name = "test";
this.project.public = true;
} }
ngOnInit() { ngOnInit() {

View File

@ -6,4 +6,5 @@ export class Project {
public clone_url: string; public clone_url: string;
public git_repo: string; public git_repo: string;
public version: string; public version: string;
public public: boolean;
} }