Skip to content

Commit fead919

Browse files
committed
feat(access-tokens): access token form & repository settings
1 parent 4ee4f6e commit fead919

18 files changed

+253
-76
lines changed

src/api/db/access-token.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { AccessToken } from './model';
2+
3+
export function getAccessTokens(): Promise<any> {
4+
return new Promise((resolve, reject) => {
5+
new AccessToken().fetchAll({ withRelated: ['user'] })
6+
.then(tokens => {
7+
if (!tokens) {
8+
reject(tokens);
9+
}
10+
11+
const result = tokens.toJSON().map(token => {
12+
delete token.token;
13+
delete token.user.password;
14+
return token;
15+
});
16+
17+
resolve(result);
18+
});
19+
});
20+
}
21+
22+
export function insertAccessToken(data: any): Promise<any> {
23+
return new Promise((resolve, reject) => {
24+
new AccessToken().save(data, { method: 'insert' })
25+
.then(token => !token ? reject(token) : resolve(token.toJSON()));
26+
});
27+
}

src/api/db/migrations.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ export function create(): Promise<null> {
3838
t.string('user_avatar_url');
3939
t.string('user_url');
4040
t.string('user_html_url');
41-
t.string('username');
42-
t.string('password');
43-
t.string('access_token');
41+
t.integer('access_tokens_id');
42+
t.foreign('access_tokens_id').references('access_tokens.id');
4443
t.json('data');
4544
t.timestamps();
4645
}))

src/api/db/model.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@ import { Bookshelf } from './config';
44
export class User extends Bookshelf.Model<any> {
55
get tableName() { return 'users'; }
66
get hasTimestamps() { return true; }
7+
access_tokens() { return this.hasMany(AccessToken, 'users_id'); }
8+
}
9+
10+
export class AccessToken extends Bookshelf.Model<any> {
11+
get tableName() { return 'access_tokens'; }
12+
get hasTimestamps() { return true; }
13+
user() { return this.belongsTo(User, 'users_id'); }
714
}
815

916
export class Repository extends Bookshelf.Model<any> {
1017
get tableName() { return 'repositories'; }
1118
get hasTimestamps() { return true; }
1219
builds() { return this.hasMany(Build, 'repositories_id'); }
20+
access_token() { return this.belongsTo(AccessToken, 'access_tokens_id'); }
1321
}
1422

1523
export class Build extends Bookshelf.Model<any> {

src/api/db/repository.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,38 @@ export function getRepository(id: number): Promise<any> {
1111
}
1212
},
1313
'builds.repository',
14-
'builds.jobs'
14+
'builds.jobs.runs',
15+
'access_token.user'
1516
]
1617
} as any)
1718
.then(repo => {
1819
if (!repo) {
1920
reject(repo);
2021
} else {
21-
resolve(repo.toJSON());
22+
repo = repo.toJSON();
23+
repo.builds = repo.builds.map(build => {
24+
build.jobs = build.jobs.map(job => {
25+
if (job.runs.length > 0) {
26+
job.end_time = job.runs[job.runs.length - 1].end_time;
27+
job.start_time = job.runs[job.runs.length - 1].start_time;
28+
job.status = job.runs[job.runs.length - 1].status;
29+
}
30+
31+
return job;
32+
});
33+
34+
return build;
35+
});
36+
37+
resolve(repo);
2238
}
2339
}).catch(err => reject(err));
2440
});
2541
}
2642

2743
export function getRepositoryOnly(id: number): Promise<any> {
2844
return new Promise((resolve, reject) => {
29-
new Repository({ id: id }).fetch().then(repo => {
45+
new Repository({ id: id }).fetch({ withRelated: ['access_token.user'] }).then(repo => {
3046
if (!repo) {
3147
reject(repo);
3248
} else {
@@ -51,9 +67,13 @@ export function getRepositoryBadge(id: number): Promise<string> {
5167
resolve('unknown');
5268
} else {
5369
repo = repo.toJSON();
54-
let status = 'queued';
70+
let status = 'unknown';
71+
72+
if (repo.builds[0] && repo.builds[0].runs[0]) {
73+
if (repo.builds[0].runs[0].job_runs.findIndex(run => run.status === 'queued') !== -1) {
74+
status = 'queued';
75+
}
5576

56-
if (repo.builds[0] && repo.builds[0].runs[0]) {
5777
if (repo.builds[0].runs[0].job_runs.findIndex(run => run.status === 'failed') !== -1) {
5878
status = 'failing';
5979
}
@@ -107,8 +127,8 @@ export function getRepositories(userId: string, keyword: string): Promise<any[]>
107127

108128
export function getRepositoryId(owner: string, repository: string): Promise<any> {
109129
return new Promise((resolve, reject) => {
110-
new Repository().query(q => q.where('full_name', `${owner}/${repository}`))
111-
.fetch().then(repo => !repo ? reject() : resolve(repo.toJSON().id));
130+
new Repository().query(q => q.where('full_name', `${owner}/${repository}`)).fetch()
131+
.then(repo => !repo ? reject() : resolve(repo.toJSON().id));
112132
});
113133
}
114134

@@ -124,6 +144,13 @@ export function addRepository(data: any): Promise<any> {
124144
});
125145
}
126146

147+
export function saveRepositorySettings(data: any): Promise<any> {
148+
return new Promise((resolve, reject) => {
149+
new Repository({ id: data.id }).save(data, { method: 'update', require: false })
150+
.then(repo => !repo ? reject(repo) : resolve(repo.toJSON()));
151+
});
152+
}
153+
127154
export function updateRepository(data: any): Promise<boolean> {
128155
return new Promise((resolve, reject) => {
129156
new Repository().where({ github_id: data.github_id })

src/api/db/user.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@ import { generatePassword, comparePassword, generateJwt } from '../security';
33

44
export function getUser(id: number): Promise<any> {
55
return new Promise((resolve, reject) => {
6-
new User({ id: id }).fetch().then(user => {
7-
if (!user) {
8-
reject(user);
9-
} else {
10-
resolve(user.toJSON());
11-
}
12-
});
6+
new User({ id: id }).fetch({ withRelated: ['access_tokens'] })
7+
.then(user => {
8+
if (!user) {
9+
reject(user);
10+
} else {
11+
let result = user.toJSON();
12+
result.access_tokens = result.access_tokens.map(token => {
13+
delete token.token;
14+
return token;
15+
});
16+
17+
resolve(result);
18+
}
19+
});
1320
});
1421
}
1522

src/api/server-routes.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ import {
2121
getRepositories,
2222
getRepository,
2323
getRepositoryBadge,
24-
getRepositoryId
24+
getRepositoryId,
25+
saveRepositorySettings
2526
} from './db/repository';
2627
import { getBuilds, getBuild } from './db/build';
2728
import { getJob } from './db/job';
29+
import { insertAccessToken, getAccessTokens } from './db/access-token';
2830
import { imageExists } from './docker';
2931

3032
export function webRoutes(): express.Router {
@@ -120,6 +122,24 @@ export function userRoutes(): express.Router {
120122
.catch(err => res.status(400).json({ err: err }));
121123
});
122124

125+
router.post('/add-token', (req: express.Request, res: express.Response) => {
126+
insertAccessToken(req.body)
127+
.then(() => res.status(200).json({ data: true }))
128+
.catch(() => res.status(200).json({ data: false }));
129+
});
130+
131+
return router;
132+
}
133+
134+
export function tokenRoutes(): express.Router {
135+
const router = express.Router();
136+
137+
router.get('/', (req: express.Request, res: express.Response) => {
138+
getAccessTokens()
139+
.then(tokens => res.status(200).json({ data: tokens }))
140+
.catch(() => res.status(200).json({ data: false }));
141+
});
142+
123143
return router;
124144
}
125145

@@ -144,6 +164,12 @@ export function repositoryRoutes(): express.Router {
144164
}).catch(err => res.status(200).json({ status: false }));
145165
});
146166

167+
router.post('/save', (req: express.Request, res: express.Response) => {
168+
saveRepositorySettings(req.body)
169+
.then(() => res.status(200).json({ data: true }))
170+
.catch(() => res.status(200).json({ data: false }));
171+
});
172+
147173
return router;
148174
}
149175

@@ -160,12 +186,12 @@ export function badgeRoutes(): express.Router {
160186

161187
router.get('/:owner/:repository', (req: express.Request, res: express.Response) => {
162188
getRepositoryId(req.params.owner, req.params.repository)
163-
.then(id => getRepositoryBadge(id))
164-
.then(status => {
165-
res.writeHead(200, {'Content-Type': 'image/svg+xml'});
166-
res.write(generateBadgeHtml(status));
167-
res.end();
168-
});
189+
.then(id => getRepositoryBadge(id))
190+
.then(status => {
191+
res.writeHead(200, {'Content-Type': 'image/svg+xml'});
192+
res.write(generateBadgeHtml(status));
193+
res.end();
194+
});
169195
});
170196

171197
return router;

src/api/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export class ExpressServer implements IExpressServer {
2929
app.use('/webhooks', webhooks);
3030
app.use('/api/setup', routes.setupRoutes());
3131
app.use('/api/user', routes.userRoutes());
32+
app.use('/api/tokens', routes.tokenRoutes());
3233
app.use('/api/repositories', routes.repositoryRoutes());
3334
app.use('/api/builds', routes.buildRoutes());
3435
app.use('/api/jobs', routes.jobRoutes());

src/api/utils.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,20 +131,22 @@ export function generateBadgeHtml(status: string): string {
131131
background = '#ffd43b';
132132
} else if (status === 'queued') {
133133
background = '#ffd43b';
134+
} else if (status === 'unknown') {
135+
background = '#3A7EE1';
134136
} else {
135137
background = '#39B54A';
136138
}
137139

138140
return `
139-
<svg xmlns="http://www.w3.org/2000/svg" width="97" height="20" style="shape-rendering:
141+
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="20" style="shape-rendering:
140142
geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd;
141143
clip-rule:evenodd">
142144
<linearGradient id="b" x2="0" y2="100%">
143145
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
144146
<stop offset="1" stop-opacity=".1"/>
145147
</linearGradient>
146148
<mask id="a">
147-
<rect width="97" height="20" rx="3" fill="#fff"/>
149+
<rect width="110" height="20" rx="3" fill="#fff"/>
148150
</mask>
149151
<g mask="url(#a)">
150152
<path fill="#333" d="M0 0h53v20H0z"/>
@@ -171,7 +173,7 @@ c-0.02-0.32,0.12-0.66-0.02-0.98c-0.75-0.04-1.5,
171173
c0.46-0.82,1.25-1.45,2.16-1.67c0.7-0.06,1.41-0.01,2.1-0.02c-0.01-1.6,
172174
0-3.2-0.01-4.81C18.09,13.33,18.42,12.27,19.18,11.57z"/>
173175
</g>
174-
<g fill="#fff" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="9">
176+
<g fill="#fff" font-family="Verdana,Geneva,sans-serif" font-size="9">
175177
<text x="22" y="15" fill="#010101" fill-opacity=".3">build</text>
176178
<text x="22" y="14">build</text>
177179
<text x="58" y="15" fill="#010101" fill-opacity=".3">` + status + `</text>

src/app/components/app-repository/app-repository.component.html

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,23 @@ <h1>{{ repo?.full_name }}</h1>
6464
</div>
6565

6666
<div class="column is-12" *ngIf="tab === 'settings'">
67-
<div class="columns is-multiline">
67+
<div class="columns">
6868
<div class="column is-12">
69-
<h2>Settings</h2>
70-
</div>
71-
<div class="column is-12">
72-
<form class="form" #repoForm="ngForm" (ngSubmit)="saveRepoSettings($event)">
73-
74-
</form>
69+
<div class="settings-container">
70+
<h2>Settings</h2>
71+
<form class="form" #repoForm="ngForm" (ngSubmit)="saveRepoSettings($event)">
72+
<div class="form-field">
73+
<label class="form-label">Access Token</label>
74+
<select class="form-input form-select" [(ngModel)]="form.access_tokens_id" name="access_tokens_id">
75+
<option *ngFor="let t of tokens" [value]="t.id">{{ t.user.fullname }}`s {{ t.description }}</option>
76+
</select>
77+
</div>
78+
<input type="hidden" name="id" [(ngModel)]="form.id">
79+
<div class="form-field">
80+
<button type="submit" class="button green float-right" [class.is-loading]="saving" [disabled]="!repoForm.form.valid">Save Repository Settings</button>
81+
</div>
82+
</form>
83+
</div>
7584
</div>
7685
</div>
7786
</div>

src/app/components/app-repository/app-repository.component.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import { ApiService } from '../../services/api.service';
55
import { ConfigService } from '../../services/config.service';
66
import { Subscription } from 'rxjs/Subscription';
77
import { format, distanceInWordsToNow } from 'date-fns';
8+
import 'rxjs/add/operator/delay';
9+
10+
export interface IRepoForm {
11+
id: number;
12+
access_tokens_id: any;
13+
}
814

915
@Component({
1016
selector: 'app-repository',
@@ -18,6 +24,9 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
1824
repo: any;
1925
url: string;
2026
statusBadge: string;
27+
tokens: any[];
28+
saving: boolean;
29+
form: IRepoForm;
2130

2231
constructor(
2332
private route: ActivatedRoute,
@@ -30,7 +39,7 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
3039
}
3140

3241
ngOnInit() {
33-
this.tab = 'settings';
42+
this.tab = 'builds';
3443
this.url = this.config.url;
3544

3645
this.route.params.subscribe(params => {
@@ -40,6 +49,7 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
4049
} else {
4150
this.fetch();
4251
this.fetchBadge();
52+
this.fetchTokens();
4353
}
4454
});
4555

@@ -94,6 +104,7 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
94104
fetch(): void {
95105
this.api.getRepository(this.id).subscribe(event => {
96106
this.repo = event;
107+
this.form = { id: parseInt(this.id, 10), access_tokens_id: event.access_tokens_id };
97108
this.loading = false;
98109
this.updateJobs();
99110
setInterval(() => this.updateJobs(), 1000);
@@ -108,6 +119,12 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
108119
});
109120
}
110121

122+
fetchTokens(): void {
123+
this.api.getAllTokens().subscribe(tokens => {
124+
this.tokens = tokens;
125+
});
126+
}
127+
111128
updateJobs(): void {
112129
let currentTime = new Date().getTime() - this.socketService.timeSyncDiff;
113130

@@ -155,4 +172,16 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
155172
gotoBuild(buildId: number) {
156173
this.router.navigate(['build', buildId]);
157174
}
175+
176+
saveRepoSettings(e: MouseEvent): void {
177+
this.saving = true;
178+
179+
this.api.saveRepositorySettings(this.form)
180+
.delay(1000)
181+
.subscribe(saved => {
182+
if (saved) {
183+
this.saving = false;
184+
}
185+
});
186+
}
158187
}

0 commit comments

Comments
 (0)