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.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.GET("/project/get/:id", LogRequestMiddleware(api.ProjectGet))
api.router.GET("/project/stats/:id", LogRequestMiddleware(api.ProjectGetStats))

View File

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

View File

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

View File

@ -22,6 +22,16 @@ type GetWorkerResponse struct {
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) {
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) {
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 loglevel;
DROP TYPE IF EXISTS log_level;
CREATE TYPE status as ENUM (
'new',
'failed',
'closed'
'closed',
'timeout'
);
CREATE TYPE loglevel as ENUM (
CREATE TYPE log_level as ENUM (
'fatal', 'panic', 'error', 'warning', 'info', 'debug', 'trace'
);
CREATE TABLE workerIdentity
CREATE TABLE worker_identity
(
id SERIAL PRIMARY KEY,
remote_addr TEXT,
@ -24,6 +26,7 @@ CREATE TABLE workerIdentity
CREATE TABLE worker
(
id TEXT PRIMARY KEY,
alias TEXT DEFAULT NULL,
created INTEGER,
identity INTEGER REFERENCES workerIdentity (id)
);
@ -35,24 +38,35 @@ CREATE TABLE project
name TEXT UNIQUE,
clone_url TEXT,
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
(
id SERIAL PRIMARY KEY,
priority INTEGER DEFAULT 0,
project INTEGER REFERENCES project (id),
assignee TEXT REFERENCES worker (id),
retries INTEGER DEFAULT 0,
max_retries INTEGER,
status Status DEFAULT 'new',
recipe TEXT
id SERIAL PRIMARY KEY,
priority INTEGER DEFAULT 0,
project INTEGER REFERENCES project (id),
assignee TEXT REFERENCES worker (id),
retries INTEGER DEFAULT 0,
max_retries INTEGER,
status Status DEFAULT 'new',
recipe TEXT,
max_assign_time INTEGER DEFAULT 0,
assign_time INTEGER DEFAULT 0
);
CREATE TABLE log_entry
(
level loglevel,
level log_level,
message TEXT,
message_data TEXT,
timestamp INT

View File

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

View File

@ -7,14 +7,16 @@ import (
)
type Task struct {
Id int64 `json:"id"`
Priority int64 `json:"priority"`
Project *Project `json:"project"`
Assignee uuid.UUID `json:"assignee"`
Retries int64 `json:"retries"`
MaxRetries int64 `json:"max_retries"`
Status string `json:"status"`
Recipe string `json:"recipe"`
Id int64 `json:"id"`
Priority int64 `json:"priority"`
Project *Project `json:"project"`
Assignee uuid.UUID `json:"assignee"`
Retries int64 `json:"retries"`
MaxRetries int64 `json:"max_retries"`
Status string `json:"status"`
Recipe string `json:"recipe"`
MaxAssignTime int64 `json:"max_assign_time"`
AssignTime int64 `json:"assign_time"`
}
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()
res, err := db.Exec(`
INSERT INTO task (project, max_retries, recipe, priority)
VALUES ($1,$2,$3,$4)`,
project, task.MaxRetries, task.Recipe, task.Priority)
INSERT INTO task (project, max_retries, recipe, priority, max_assign_time)
VALUES ($1,$2,$3,$4,$5)`,
project, task.MaxRetries, task.Recipe, task.Priority, task.MaxAssignTime)
if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"task": task,
@ -56,6 +58,9 @@ func (database *Database) GetTask(worker *Worker) *Task {
FROM task
INNER JOIN project p on task.project = p.id
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
LIMIT 1
)
@ -134,6 +139,9 @@ func (database *Database) GetTaskFromProject(worker *Worker, projectId int64) *T
FROM task
INNER JOIN project p on task.project = p.id
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
LIMIT 1
)
@ -165,8 +173,9 @@ func scanTask(row *sql.Row) *Task {
task.Project = project
err := row.Scan(&task.Id, &task.Priority, &project.Id, &task.Assignee,
&task.Retries, &task.MaxRetries, &task.Status, &task.Recipe, &project.Id,
&project.Priority, &project.Motd, &project.Name, &project.CloneUrl, &project.GitRepo, &project.Version)
&task.Retries, &task.MaxRetries, &task.Status, &task.Recipe, &task.MaxAssignTime,
&task.AssignTime, &project.Id, &project.Priority, &project.Name,
&project.CloneUrl, &project.GitRepo, &project.Version, &project.Motd, &project.Public)
handleErr(err)
return task

View File

@ -101,3 +101,46 @@ func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 {
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",
Priority: 123,
Motd: "motd",
Public: true,
})
id := resp.Id
@ -56,6 +57,9 @@ func TestCreateGetProject(t *testing.T) {
if getResp.Project.Motd != "motd" {
t.Error()
}
if getResp.Project.Public != true {
t.Error()
}
}
func TestCreateProjectInvalid(t *testing.T) {

View File

@ -129,6 +129,7 @@ func TestCreateGetTask(t *testing.T) {
CloneUrl: "http://github.com/test/test",
GitRepo: "myrepo",
Priority: 999,
Public: true,
})
createTask(api.CreateTaskRequest{
@ -170,6 +171,9 @@ func TestCreateGetTask(t *testing.T) {
if taskResp.Task.Project.CloneUrl != "http://github.com/test/test" {
t.Error()
}
if taskResp.Task.Project.Public != true {
t.Error()
}
}
func createTasks(prefix string) (int64, int64) {
@ -180,6 +184,7 @@ func createTasks(prefix string) (int64, int64) {
CloneUrl: "http://github.com/test/test",
GitRepo: prefix + "low1",
Priority: 1,
Public: true,
})
highP := createProject(api.CreateProjectRequest{
Name: prefix + "high",
@ -187,6 +192,7 @@ func createTasks(prefix string) (int64, int64) {
CloneUrl: "http://github.com/test/test",
GitRepo: prefix + "high1",
Priority: 999,
Public: true,
})
createTask(api.CreateTaskRequest{
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) {
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) {
r := Post("/worker/create", req)
@ -94,3 +166,33 @@ func genWid() *uuid.UUID {
resp, _ := createWorker(api.CreateWorkerRequest{})
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
}

45
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 loglevel;
DROP TYPE IF EXISTS log_level;
CREATE TYPE status as ENUM (
'new',
'failed',
'closed'
'closed',
'timeout'
);
CREATE TYPE loglevel as ENUM (
CREATE TYPE log_level as ENUM (
'fatal', 'panic', 'error', 'warning', 'info', 'debug', 'trace'
);
CREATE TABLE workerIdentity
CREATE TABLE worker_identity
(
id SERIAL PRIMARY KEY,
remote_addr TEXT,
@ -24,6 +26,7 @@ CREATE TABLE workerIdentity
CREATE TABLE worker
(
id TEXT PRIMARY KEY,
alias TEXT DEFAULT NULL,
created INTEGER,
identity INTEGER REFERENCES workerIdentity (id)
);
@ -32,28 +35,38 @@ CREATE TABLE project
(
id SERIAL PRIMARY KEY,
priority INTEGER DEFAULT 0,
motd TEXT DEFAULT '',
name TEXT UNIQUE,
clone_url TEXT,
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
(
id SERIAL PRIMARY KEY,
priority INTEGER DEFAULT 0,
project INTEGER REFERENCES project (id),
assignee TEXT REFERENCES worker (id),
retries INTEGER DEFAULT 0,
max_retries INTEGER,
status Status DEFAULT 'new',
recipe TEXT
id SERIAL PRIMARY KEY,
priority INTEGER DEFAULT 0,
project INTEGER REFERENCES project (id),
assignee TEXT REFERENCES worker (id),
retries INTEGER DEFAULT 0,
max_retries INTEGER,
status Status DEFAULT 'new',
recipe TEXT,
max_assign_time INTEGER DEFAULT 0,
assign_time INTEGER DEFAULT 0
);
CREATE TABLE log_entry
(
level loglevel,
level log_level,
message TEXT,
message_data TEXT,
timestamp INT

View File

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

View File

@ -4,21 +4,30 @@
<mat-card-content>
<form>
<mat-form-field>
<input type="text" matInput [(ngModel)]="project.name" name="name" placeholder="Name">
<mat-form-field appearance="outline">
<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 appearance="outline">
<mat-label>Git clone URL</mat-label>
<input type="text" matInput [(ngModel)]="project.clone_url" name="clone_url"
placeholder="Git clone url">
</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"
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
enabled
<mat-hint align="start">
Changes on the <strong>master</strong> branch will be tracked if webhooks are enabled
</mat-hint>
</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}}">
</form>
</mat-card-content>

View File

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

View File

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