Auth API
http://52.79.115.43:8090/api/collections/users/auth-with-password
Request Params : identity, password (Teddy/sfac12341234)
Response (200) : {
"token": "JWT_TOKEN",
"record": {
"id": "RECORD_ID",
"created": "2022-01-01 01:00:00Z",
"updated": "2022-01-01 23:59:59Z",
"username": String,
"email": String,
"name": String,
"avatar": String,
}
}
class AuthController extends GetxController {
final Rxn<User> _user = Rxn();
Dio dio = Dio();
User? get user => _user.value;
login(String id, String pw) async {
dio.options.baseUrl = 'http://52.79.115.43:8090';
try {
var res = await dio.post(ApiRoutes.authWithPassword, data: {
'identity': id,
'password': pw,
});
if (res.statusCode == 200) {
var user = User.fromMap(res.data['record']);
_user(user);
}
} on DioException catch (e) {
print(e.message);
print(e.requestOptions.path);
}
}
logout() {
_user.value = null;
}
_handleAuthChanged(User? data) {
if (data != null) {
Get.toNamed(AppRoutes.main);
return;
}
// 로그인 페이지로 이동
Get.toNamed(AppRoutes.login);
return;
}
void onInit() {
super.onInit();
ever(_user, _handleAuthChanged);
}
}
class LoginController extends GetxController {
var idController = TextEditingController();
var pwController = TextEditingController();
login() {
Get.find<AuthController>().login(idController.text, pwController.text);
}
}
class MainController extends GetxController {
var pageController = PageController();
RxInt curPage = 0.obs;
onPageTapped(int v) {
pageController.jumpToPage(v);
curPage(v);
}
logout() {
Get.find<AuthController>().logout();
}
}
class User {
String id;
String username;
String email;
String name;
User({
required this.id,
required this.username,
required this.email,
required this.name,
});
Map<String, dynamic> toMap() {
return <String, dynamic>{
'id': id,
'username': username,
'email': email,
'name': name,
};
}
factory User.fromMap(Map<String, dynamic> map) {
return User(
id: map['id'] as String,
username: map['username'] as String,
email: map['email'] as String,
name: map['name'] as String,
);
}
}
class ApiRoutes {
static const String authWithPassword =
'/api/collections/users/auth-with-password';
}
class LoginPage extends GetView<LoginController> {
//GetView<제네릭>을 extends
const LoginPage({super.key});
static const String route = '/login'; //path 정의
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: controller.idController, //컨트롤러 연결
decoration: const InputDecoration(
hintText: 'ID',
),
),
TextField(
controller: controller.pwController,
decoration: const InputDecoration(
hintText: 'PW',
),
),
ElevatedButton(
onPressed: controller.login, //로그인 함수 연결
child: const Text('로그인하기'),
),
],
),
),
),
);
}
}
class MainPage extends GetView<MainController> {
const MainPage({super.key});
static const String route = '/main'; //path 정의
Widget build(BuildContext context) {
var user = Get.find<AuthController>().user!; //로그인해야만 들어올 수 있는 페이지이기 때문에 ! 사용 가능.
return Scaffold(
bottomNavigationBar: Obx(
() => BottomNavigationBar(
currentIndex: controller.curPage.value,
onTap: controller.onPageTapped,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'My'),
],
),
),
body: SafeArea(
child: PageView(
controller: controller.pageController,
children: [
Column(
children: [
Text(
'${user.username}님 안녕하세요.',
style: const TextStyle(
fontSize: 32,
),
),
],
),
Column(
children: [
ListTile(
leading: const CircleAvatar(),
title: Text(user.username),
subtitle: Text(user.email),
),
ListTile(
title: const Text('logout'),
leading: const Icon(Icons.logout),
onTap: controller.logout, //로그아웃 연결
),
],
),
],
),
),
);
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return GetMaterialApp(
theme: ThemeData(useMaterial3: true),
initialBinding: BindingsBuilder(() {
Get.put(AuthController());
Get.lazyPut(() => LoginController());
Get.lazyPut(() => MainController());
}),
getPages: AppPages.pages,
home: Scaffold(
body: Center(
child: TextButton(
onPressed: () => Get.toNamed(LoginPage.route),
child: const Text('Login page'),
),
),
),
);
}
}
// http://52.79.115.43:8090/api/collections/users/auth-with-password
// Method : POST
// data : identity(String), password(String)
// Teddy/sfac12341234
{
"token": "JWT_TOKEN",
"record": {
"id": "RECORD_ID",
"collectionId": "_pb_users_auth_",
"collectionName": "users",
"created": "2022-01-01 01:00:00Z",
"updated": "2022-01-01 23:59:59Z",
"username": "username123",
"verified": false,
"emailVisibility": true,
"email": "test@example.com",
"name": "test",
"avatar": "filename.jpg"
}
}
// Method: GET
// 해당 API는 인증된 사용자만 사용할 수 있기 때문에
// 로그인 시 획득한 Token을 반드시 Request 헤더에 Authorization을 포함시켜야만합니다.
// ignore_for_file: public_member_api_docs, sort_constructors_first, non_constant_identifier_names
class Document {
String title;
String content;
String sec_level;
String? attachment_url;
Document({
required this.title,
required this.content,
required this.sec_level,
this.attachment_url,
});
factory Document.fromMap(Map<String, dynamic> map) {
return Document(
title: map['title'] as String,
content: map['content'] as String,
sec_level: map['sec_level'] as String,
attachment_url:
map['attachment_url'] != '' ? map['attachment_url'] : null,
);
}
}
요구사항이 길기도 하다.. 그치만 과제코드는 의외로 길지 않다. AuthController 강의부분이 선행되어서..
처음에는 readDocuments를 실행해서 데이터를 받도록 하려다가, 리스트를 제공하도록 수정했다. 위젯 자체를 async로 만들고싶지 않아서...
class MainController extends GetxController {
var pageController = PageController();
RxInt curPage = 0.obs;
List<Document>? documents;
Dio dio = Dio();
readDocuments() async {
dio.options.baseUrl = 'http://52.79.115.43:8090';
dio.options.headers['Authorization'] = Get.find<AuthController>().token;
try {
var res = await dio.get(ApiRoutes.authWithDocuments);
if (res.statusCode == 200) {
var data = List<Map<String, dynamic>>.from(res.data['items']);
documents = data.map((e) => Document.fromMap(e)).toList();
}
} on DioException catch (e) {
print(e.message);
print(e.requestOptions.path);
}
}
onPageTapped(int v) {
pageController.jumpToPage(v);
curPage(v);
}
logout() {
Get.find<AuthController>().logout();
}
}
class AuthController extends GetxController {
final Rxn<User> _user = Rxn();
final Rxn _token = Rxn(); //토큰
Dio dio = Dio();
User? get user => _user.value;
String? get token => _token.value; //토큰
login(String id, String pw) async {
dio.options.baseUrl = 'http://52.79.115.43:8090';
try {
var res = await dio.post(ApiRoutes.authWithPassword, data: {
'identity': id,
'password': pw,
});
if (res.statusCode == 200) {
var user = User.fromMap(res.data['record']);
_user(user);
_token(res.data["token"]); //토큰 저장하는 부분 추가
}
} on DioException catch (e) {
print(e.message);
print(e.requestOptions.path);
}
}
...
}
class MainPage extends GetView<MainController> {
const MainPage({super.key});
static const String route = '/main'; //path 정의
Widget build(BuildContext context) {
var user = Get.find<AuthController>().user!;
List<Document>? docs = controller.documents;
return Scaffold(
body: SafeArea(
child: PageView(
controller: controller.pageController,
children: [
Column(
children: [
Text(
'${user.username}님 안녕하세요.',
style: const TextStyle(
fontSize: 32,
),
),
docs != null
? ListView.builder(
shrinkWrap: true,
itemCount: docs.length ?? 0,
itemBuilder: (context, index) {
return Column(
children: [
ListTile(
title: Text(docs[index].title),
subtitle: Text(docs[index].content),
),
(docs[index].attachment_url != '')
? Image.network(docs[index].attachment_url!)
: Container(),
],
);
},
)
: Container()
],
),
],
),
),
);
}
}